mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 08:33:43 -05:00
feat(ext/ffi): support passing and returning bigints (#14523)
This commit is contained in:
parent
2769d60250
commit
8113fac939
6 changed files with 120 additions and 54 deletions
12
cli/dts/lib.deno.unstable.d.ts
vendored
12
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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) })
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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\
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
Loading…
Reference in a new issue