diff options
author | Elias Sjögreen <eliassjogreen1@gmail.com> | 2022-06-08 13:13:10 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-08 16:43:10 +0530 |
commit | 8113fac939c06b0d71a22d008c060bed3cb47d72 (patch) | |
tree | cd9734046f414b344d4b8000c125f8b682768177 | |
parent | 2769d602506af1953312b28580506fca3fcbe030 (diff) |
feat(ext/ffi): support passing and returning bigints (#14523)
-rw-r--r-- | cli/dts/lib.deno.unstable.d.ts | 12 | ||||
-rw-r--r-- | ext/ffi/00_ffi.js | 77 | ||||
-rw-r--r-- | ext/ffi/lib.rs | 45 | ||||
-rw-r--r-- | test_ffi/tests/ffi_types.ts | 24 | ||||
-rw-r--r-- | test_ffi/tests/integration_tests.rs | 8 | ||||
-rw-r--r-- | test_ffi/tests/test.js | 8 |
6 files changed, 120 insertions, 54 deletions
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index b8eb886e4..12f53b784 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -372,17 +372,25 @@ declare namespace Deno { } /** All possible number types interfacing with foreign functions */ - type StaticNativeNumberType = Exclude<NativeType, "void" | "pointer">; + type StaticNativeNumberType = Exclude< + NativeType, + "void" | "pointer" | StaticNativeBigIntType + >; + + /** All possible bigint types interfacing with foreign functions */ + type StaticNativeBigIntType = "u64" | "i64" | "usize" | "isize"; /** Infers a foreign function return type */ type StaticForeignFunctionResult<T extends NativeType> = T extends "void" ? void + : T extends StaticNativeBigIntType ? bigint : T extends StaticNativeNumberType ? number : T extends "pointer" ? UnsafePointer : never; type StaticForeignFunctionParameter<T> = T extends "void" ? void - : T extends StaticNativeNumberType ? number + : T extends StaticNativeNumberType | StaticNativeBigIntType + ? number | bigint : T extends "pointer" ? Deno.UnsafePointer | Deno.TypedArray | null : unknown; diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index 3debeef14..fb4fcbfb8 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -14,12 +14,12 @@ TypeError, } = window.__bootstrap.primordials; - function unpackU64([hi, lo]) { - return BigInt(hi) << 32n | BigInt(lo); + function pack64(value) { + return [Number(value >> 32n) >>> 0, Number(value & 0xFFFFFFFFn)]; } - function packU64(value) { - return [Number(value >> 32n), Number(value & 0xFFFFFFFFn)]; + function unpackU64([hi, lo]) { + return BigInt(hi) << 32n | BigInt(lo); } function unpackI64([hi, lo]) { @@ -37,77 +37,77 @@ getUint8(offset = 0) { return core.opSync( "op_ffi_read_u8", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getInt8(offset = 0) { return core.opSync( "op_ffi_read_i8", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getUint16(offset = 0) { return core.opSync( "op_ffi_read_u16", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getInt16(offset = 0) { return core.opSync( "op_ffi_read_i16", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getUint32(offset = 0) { return core.opSync( "op_ffi_read_u32", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getInt32(offset = 0) { return core.opSync( "op_ffi_read_i32", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getBigUint64(offset = 0) { return unpackU64(core.opSync( "op_ffi_read_u64", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), )); } getBigInt64(offset = 0) { return unpackI64(core.opSync( "op_ffi_read_u64", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), )); } getFloat32(offset = 0) { return core.opSync( "op_ffi_read_f32", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getFloat64(offset = 0) { return core.opSync( "op_ffi_read_f64", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } getCString(offset = 0) { return core.opSync( "op_ffi_cstr_read", - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), ); } @@ -119,7 +119,7 @@ copyInto(destination, offset = 0) { core.opSync("op_ffi_buf_copy_into", [ - packU64(this.pointer.value + BigInt(offset)), + pack64(this.pointer.value + BigInt(offset)), destination, destination.byteLength, ]); @@ -161,7 +161,7 @@ parameters.push(buffers.length); buffers.push(arg); } else if (ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)) { - parameters.push(packU64(arg.value)); + parameters.push(pack64(arg.value)); buffers.push(undefined); } else if (arg === null) { parameters.push(null); @@ -171,6 +171,14 @@ "Invalid ffi arg value, expected TypedArray, UnsafePointer or null", ); } + } else if (typeof arg === "bigint") { + if (arg > 0xffffffffffffffffn) { + throw new TypeError( + "Invalid ffi arg value, it needs to be less than 0xffffffffffffffff", + ); + } + + parameters.push(pack64(arg)); } else { parameters.push(arg); } @@ -179,6 +187,23 @@ return { parameters, buffers }; } + function unpackResult(type, result) { + switch (type) { + case "pointer": + return new UnsafePointer(unpackU64(result)); + case "u64": + return unpackU64(result); + case "i64": + return unpackI64(result); + case "usize": + return unpackU64(result); + case "isize": + return unpackI64(result); + default: + return result; + } + } + class UnsafeFnPointer { pointer; definition; @@ -195,7 +220,7 @@ ); if (this.definition.nonblocking) { const promise = core.opAsync("op_ffi_call_ptr_nonblocking", { - pointer: packU64(this.pointer.value), + pointer: pack64(this.pointer.value), def: this.definition, parameters, buffers, @@ -208,7 +233,7 @@ return promise; } else { const result = core.opSync("op_ffi_call_ptr", { - pointer: packU64(this.pointer.value), + pointer: pack64(this.pointer.value), def: this.definition, parameters, buffers, @@ -268,8 +293,10 @@ ); continue; } + const isNonBlocking = symbols[symbol].nonblocking; const types = symbols[symbol].parameters; + const resultType = symbols[symbol].result; const fn = (...args) => { const { parameters, buffers } = prepareArgs(types, args); @@ -282,10 +309,8 @@ buffers, }); - if (symbols[symbol].result === "pointer") { - return promise.then((value) => - new UnsafePointer(unpackU64(value)) - ); + if (resultType === "pointer") { + return promise.then((result) => unpackResult(resultType, result)); } return promise; @@ -297,11 +322,7 @@ buffers, }); - if (symbols[symbol].result === "pointer") { - return new UnsafePointer(unpackU64(result)); - } - - return result; + return unpackResult(resultType, result); } }; diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 01aa17698..8a1a75b1d 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -311,6 +311,11 @@ impl NativeValue { } fn value_as_uint<T: TryFrom<u64>>(value: Value) -> Result<T, AnyError> { + if value.is_array() { + let value = U32x2::try_from(value)?; + return T::try_from(u64::from(value)).map_err(|_| type_error(format!("Found U32x2 FFI argument but it could not be converted to an unsigned integer, got {:?}", value))); + } + match value.as_u64().and_then(|v| T::try_from(v).ok()) { Some(value) => Ok(value), None => Err(type_error(format!( @@ -321,6 +326,11 @@ fn value_as_uint<T: TryFrom<u64>>(value: Value) -> Result<T, AnyError> { } fn value_as_int<T: TryFrom<i64>>(value: Value) -> Result<T, AnyError> { + if value.is_array() { + let value = U32x2::try_from(value)?; + return T::try_from(u64::from(value) as i64).map_err(|_| type_error(format!("Found U32x2 FFI argument but it could not be converted to a signed integer, got {:?}", value))); + } + match value.as_i64().and_then(|v| T::try_from(v).ok()) { Some(value) => Ok(value), None => Err(type_error(format!( @@ -359,6 +369,25 @@ impl From<U32x2> for u64 { } } +impl TryFrom<Value> for U32x2 { + type Error = AnyError; + + fn try_from(value: Value) -> Result<Self, Self::Error> { + if let Some(value) = value.as_array() { + if let Some(hi) = value[0].as_u64() { + if let Some(lo) = value[1].as_u64() { + return Ok(U32x2(hi as u32, lo as u32)); + } + } + } + + Err(type_error(format!( + "Expected FFI argument to be a signed integer, but got {:?}", + value + ))) + } +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct ForeignFunction { @@ -627,16 +656,24 @@ fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> { json!(unsafe { symbol.cif.call::<i32>(symbol.ptr, &call_args) }) } NativeType::U64 => { - json!(unsafe { symbol.cif.call::<u64>(symbol.ptr, &call_args) }) + json!(U32x2::from(unsafe { + symbol.cif.call::<u64>(symbol.ptr, &call_args) + })) } NativeType::I64 => { - json!(unsafe { symbol.cif.call::<i64>(symbol.ptr, &call_args) }) + json!(U32x2::from(unsafe { + symbol.cif.call::<i64>(symbol.ptr, &call_args) + } as u64)) } NativeType::USize => { - json!(unsafe { symbol.cif.call::<usize>(symbol.ptr, &call_args) }) + json!(U32x2::from(unsafe { + symbol.cif.call::<usize>(symbol.ptr, &call_args) + } as u64)) } NativeType::ISize => { - json!(unsafe { symbol.cif.call::<isize>(symbol.ptr, &call_args) }) + json!(U32x2::from(unsafe { + symbol.cif.call::<isize>(symbol.ptr, &call_args) + } as u64)) } NativeType::F32 => { json!(unsafe { symbol.cif.call::<f32>(symbol.ptr, &call_args) }) diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts index 0aab6a8fa..9ad51e67c 100644 --- a/test_ffi/tests/ffi_types.ts +++ b/test_ffi/tests/ffi_types.ts @@ -45,7 +45,7 @@ const remote = Deno.dlopen( remote.symbols.method1(0); // @ts-expect-error: Invalid return type <number> remote.symbols.method1(0, 0); -<void> remote.symbols.method1(0, 0); +<void> remote.symbols.method1(0n, 0n); // @ts-expect-error: Invalid argument remote.symbols.method2(null); @@ -53,11 +53,11 @@ remote.symbols.method2(void 0); // @ts-expect-error: Invalid argument remote.symbols.method3(null); -remote.symbols.method3(0); +remote.symbols.method3(0n); // @ts-expect-error: Invalid argument remote.symbols.method4(null); -remote.symbols.method4(0); +remote.symbols.method4(0n); // @ts-expect-error: Invalid argument remote.symbols.method5(null); @@ -73,7 +73,7 @@ remote.symbols.method7(0); // @ts-expect-error: Invalid argument remote.symbols.method8(null); -remote.symbols.method8(0); +remote.symbols.method8(0n); // @ts-expect-error: Invalid argument remote.symbols.method9(null); @@ -89,7 +89,7 @@ remote.symbols.method11(0); // @ts-expect-error: Invalid argument remote.symbols.method12(null); -remote.symbols.method12(0); +remote.symbols.method12(0n); // @ts-expect-error: Invalid argument remote.symbols.method13(null); @@ -107,12 +107,12 @@ remote.symbols.method15({} as Deno.UnsafePointer); const result = remote.symbols.method16(); // @ts-expect-error: Invalid argument let r_0: string = result; -let r_1: number = result; +let r_1: number | bigint = result; const result2 = remote.symbols.method17(); // @ts-expect-error: Invalid argument result2.then((_0: string) => {}); -result2.then((_1: number) => {}); +result2.then((_1: number | bigint) => {}); const result3 = remote.symbols.method18(); // @ts-expect-error: Invalid argument @@ -138,16 +138,16 @@ fnptr.call(0, null); // @ts-expect-error: Invalid member type const static1_wrong: null = remote.symbols.static1; -const static1_right: number = remote.symbols.static1; +const static1_right: bigint = remote.symbols.static1; // @ts-expect-error: Invalid member type const static2_wrong: null = remote.symbols.static2; const static2_right: Deno.UnsafePointer = remote.symbols.static2; // @ts-expect-error: Invalid member type const static3_wrong: null = remote.symbols.static3; -const static3_right: number = remote.symbols.static3; +const static3_right: bigint = remote.symbols.static3; // @ts-expect-error: Invalid member type const static4_wrong: null = remote.symbols.static4; -const static4_right: number = remote.symbols.static4; +const static4_right: bigint = remote.symbols.static4; // @ts-expect-error: Invalid member type const static5_wrong: null = remote.symbols.static5; const static5_right: number = remote.symbols.static5; @@ -159,7 +159,7 @@ const static7_wrong: null = remote.symbols.static7; const static7_right: number = remote.symbols.static7; // @ts-expect-error: Invalid member type const static8_wrong: null = remote.symbols.static8; -const static8_right: number = remote.symbols.static8; +const static8_right: bigint = remote.symbols.static8; // @ts-expect-error: Invalid member type const static9_wrong: null = remote.symbols.static9; const static9_right: number = remote.symbols.static9; @@ -171,7 +171,7 @@ const static11_wrong: null = remote.symbols.static11; const static11_right: number = remote.symbols.static11; // @ts-expect-error: Invalid member type const static12_wrong: null = remote.symbols.static12; -const static12_right: number = remote.symbols.static12; +const static12_right: bigint = remote.symbols.static12; // @ts-expect-error: Invalid member type const static13_wrong: null = remote.symbols.static13; const static13_right: number = remote.symbols.static13; diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index fea5b5fbd..5b9bb2fc2 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -59,10 +59,10 @@ fn basic() { true\n\ 579\n\ 579\n\ - 579\n\ - 579\n\ - 579\n\ - 579\n\ + 8589934590n\n\ + -8589934590n\n\ + 8589934590n\n\ + -8589934590n\n\ 579.9119873046875\n\ 579.912\n\ After sleep_blocking\n\ diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index 943f86ae8..b89dca648 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -145,10 +145,10 @@ assertThrows( "Expected FFI argument to be an unsigned integer, but got Null", ); console.log(dylib.symbols.add_i32(123, 456)); -console.log(dylib.symbols.add_u64(123, 456)); -console.log(dylib.symbols.add_i64(123, 456)); -console.log(dylib.symbols.add_usize(123, 456)); -console.log(dylib.symbols.add_isize(123, 456)); +console.log(dylib.symbols.add_u64(0xffffffffn, 0xffffffffn)); +console.log(dylib.symbols.add_i64(-0xffffffffn, -0xffffffffn)); +console.log(dylib.symbols.add_usize(0xffffffffn, 0xffffffffn)); +console.log(dylib.symbols.add_isize(-0xffffffffn, -0xffffffffn)); console.log(dylib.symbols.add_f32(123.123, 456.789)); console.log(dylib.symbols.add_f64(123.123, 456.789)); |