summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/dts/lib.deno.unstable.d.ts94
-rw-r--r--cli/tests/testdata/unstable_ffi_10.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_11.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_12.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_13.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_14.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_15.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_2.js13
-rw-r--r--cli/tests/testdata/unstable_ffi_3.js13
-rw-r--r--cli/tests/testdata/unstable_ffi_5.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_6.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_7.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_8.js2
-rw-r--r--cli/tests/testdata/unstable_ffi_9.js2
-rw-r--r--ext/ffi/00_ffi.js367
-rw-r--r--ext/ffi/lib.rs1228
-rw-r--r--runtime/js/90_deno_ns.js1
-rw-r--r--test_ffi/src/lib.rs225
-rw-r--r--test_ffi/tests/bench.js503
-rw-r--r--test_ffi/tests/ffi_types.ts132
-rw-r--r--test_ffi/tests/integration_tests.rs16
-rw-r--r--test_ffi/tests/test.js205
22 files changed, 2331 insertions, 488 deletions
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 7ebd80422..3482d1c80 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -346,9 +346,14 @@ declare namespace Deno {
| "f64"
| "pointer";
+ type NativeParameterType =
+ | NativeType
+ | NativeCallbackType;
+
/** A foreign function as defined by its parameter and result types */
export interface ForeignFunction<
- Parameters extends readonly NativeType[] = readonly NativeType[],
+ Parameters extends readonly NativeParameterType[] =
+ readonly NativeParameterType[],
Result extends NativeType = NativeType,
NonBlocking extends boolean = boolean,
> {
@@ -391,11 +396,17 @@ declare namespace Deno {
type StaticForeignFunctionParameter<T> = T extends "void" ? void
: T extends StaticNativeNumberType | StaticNativeBigIntType
? number | bigint
- : T extends "pointer" ? Deno.UnsafePointer | Deno.TypedArray | null
+ : T extends "pointer" ? UnsafePointer | TypedArray | null
+ : T extends NativeCallbackType<
+ infer U extends readonly NativeType[],
+ infer V extends NativeParameterType
+ > ? UnsafeCallback<U, V> | UnsafePointer | null
: unknown;
/** Infers a foreign function parameter list. */
- type StaticForeignFunctionParameters<T extends readonly NativeType[]> = [
+ type StaticForeignFunctionParameters<
+ T extends readonly NativeParameterType[],
+ > = [
...{
[K in keyof T]: StaticForeignFunctionParameter<T[K]>;
},
@@ -513,6 +524,83 @@ declare namespace Deno {
>;
}
+ export interface UnsafeCallbackDefinition<
+ Parameters extends readonly NativeType[] = readonly NativeType[],
+ Result extends NativeParameterType = NativeParameterType,
+ > {
+ parameters: Parameters;
+ result: Result;
+ }
+
+ interface NativeCallbackType<
+ Parameters extends readonly NativeType[] = readonly NativeType[],
+ Result extends NativeParameterType = NativeParameterType,
+ > {
+ readonly function: UnsafeCallbackDefinition<Parameters, Result>;
+ }
+
+ type UnsafeCallbackParameters<T extends readonly NativeType[]> = T extends []
+ ? []
+ : T extends
+ readonly [infer U extends NativeType, ...(infer V extends NativeType[])]
+ ? [
+ UnsafeCallbackParameter<U>,
+ ...UnsafeCallbackParameters<V>,
+ ]
+ : never;
+
+ type UnsafeCallbackParameter<T extends NativeType> = T extends
+ StaticNativeBigIntType ? bigint
+ : T extends StaticNativeNumberType ? number
+ : T extends "pointer" ? UnsafePointer
+ : never;
+
+ type UnsafeCallbackResult<T extends NativeParameterType> = T extends "void"
+ ? void
+ : T extends StaticNativeBigIntType ? number | bigint
+ : T extends StaticNativeNumberType ? number
+ : T extends "pointer" ? UnsafePointer | TypedArray | null
+ : T extends NativeCallbackType<
+ infer U extends readonly NativeType[],
+ infer V extends NativeParameterType
+ > ? UnsafeCallback<U, V> | UnsafePointer | null
+ : never;
+
+ type UnsafeCallbackFunction<
+ Parameters extends readonly NativeType[] = readonly NativeType[],
+ Result extends NativeParameterType = NativeParameterType,
+ > = Result extends NativeParameterType
+ ? Parameters extends readonly [] ? () => UnsafeCallbackResult<Result>
+ : Parameters extends readonly NativeType[] ? (
+ ...args: UnsafeCallbackParameters<Parameters>
+ ) => UnsafeCallbackResult<Result>
+ : never
+ : never;
+
+ /**
+ * **UNSTABLE**: Unsafe and new API, beware!
+ *
+ * An unsafe function pointer for passing JavaScript functions
+ * as C function pointers to ffi calls.
+ *
+ * The function pointer remains valid until the `close()` method is called.
+ */
+ export class UnsafeCallback<
+ Parameters extends readonly NativeType[] = readonly NativeType[],
+ Result extends NativeParameterType = NativeParameterType,
+ > {
+ constructor(
+ definition: UnsafeCallbackDefinition<Parameters, Result>,
+ callback: UnsafeCallbackFunction<Parameters, Result>,
+ );
+
+ pointer: UnsafePointer;
+ definition: UnsafeCallbackDefinition<Parameters, Result>;
+ callback: UnsafeCallbackFunction<Parameters, Result>;
+
+ close(): void;
+ }
+
/** A dynamic library resource */
export interface DynamicLibrary<S extends ForeignLibraryInterface> {
/** All of the registered library along with functions for calling them */
diff --git a/cli/tests/testdata/unstable_ffi_10.js b/cli/tests/testdata/unstable_ffi_10.js
index c7cafd3ea..e8fc9b3f0 100644
--- a/cli/tests/testdata/unstable_ffi_10.js
+++ b/cli/tests/testdata/unstable_ffi_10.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_i16", [0, 0]);
+Deno.core.opSync("op_ffi_read_i16", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_11.js b/cli/tests/testdata/unstable_ffi_11.js
index 37bd75cc9..77c86109a 100644
--- a/cli/tests/testdata/unstable_ffi_11.js
+++ b/cli/tests/testdata/unstable_ffi_11.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_u32", [0, 0]);
+Deno.core.opSync("op_ffi_read_u32", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_12.js b/cli/tests/testdata/unstable_ffi_12.js
index b05f92d39..65934a82f 100644
--- a/cli/tests/testdata/unstable_ffi_12.js
+++ b/cli/tests/testdata/unstable_ffi_12.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_i32", [0, 0]);
+Deno.core.opSync("op_ffi_read_i32", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_13.js b/cli/tests/testdata/unstable_ffi_13.js
index a83b8dc18..0ab43781b 100644
--- a/cli/tests/testdata/unstable_ffi_13.js
+++ b/cli/tests/testdata/unstable_ffi_13.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_u64", [0, 0]);
+Deno.core.opSync("op_ffi_read_u64", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_14.js b/cli/tests/testdata/unstable_ffi_14.js
index b39b99da5..b65a50a20 100644
--- a/cli/tests/testdata/unstable_ffi_14.js
+++ b/cli/tests/testdata/unstable_ffi_14.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_f32", [0, 0]);
+Deno.core.opSync("op_ffi_read_f32", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_15.js b/cli/tests/testdata/unstable_ffi_15.js
index afd49b722..de9f29168 100644
--- a/cli/tests/testdata/unstable_ffi_15.js
+++ b/cli/tests/testdata/unstable_ffi_15.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_f64", [0, 0]);
+Deno.core.opSync("op_ffi_read_f64", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_2.js b/cli/tests/testdata/unstable_ffi_2.js
index de392fa7d..fe3d9d709 100644
--- a/cli/tests/testdata/unstable_ffi_2.js
+++ b/cli/tests/testdata/unstable_ffi_2.js
@@ -1,10 +1,5 @@
-Deno.core.opSync("op_ffi_call_ptr", {
- pointer: [0, 0],
- def: {
- name: null,
- parameters: [],
- result: "void",
- },
+Deno.core.opSync("op_ffi_call_ptr", 0n, {
+ name: null,
parameters: [],
- buffers: [],
-});
+ result: "void",
+}, []);
diff --git a/cli/tests/testdata/unstable_ffi_3.js b/cli/tests/testdata/unstable_ffi_3.js
index 4924d9d67..a8f7f4180 100644
--- a/cli/tests/testdata/unstable_ffi_3.js
+++ b/cli/tests/testdata/unstable_ffi_3.js
@@ -1,10 +1,5 @@
-Deno.core.opAsync("op_ffi_call_ptr_nonblocking", {
- pointer: [0, 0],
- def: {
- name: null,
- parameters: [],
- result: "void",
- },
+Deno.core.opAsync("op_ffi_call_ptr_nonblocking", 0n, {
+ name: null,
parameters: [],
- buffers: [],
-});
+ result: "void",
+}, []);
diff --git a/cli/tests/testdata/unstable_ffi_5.js b/cli/tests/testdata/unstable_ffi_5.js
index 447ff5842..dc494023d 100644
--- a/cli/tests/testdata/unstable_ffi_5.js
+++ b/cli/tests/testdata/unstable_ffi_5.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_buf_copy_into", [[0, 0], new Uint8Array(0), 0]);
+Deno.core.opSync("op_ffi_buf_copy_into", 0n, new Uint8Array(0), 0);
diff --git a/cli/tests/testdata/unstable_ffi_6.js b/cli/tests/testdata/unstable_ffi_6.js
index cc791b8f0..c66681225 100644
--- a/cli/tests/testdata/unstable_ffi_6.js
+++ b/cli/tests/testdata/unstable_ffi_6.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_cstr_read", [0, 0]);
+Deno.core.opSync("op_ffi_cstr_read", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_7.js b/cli/tests/testdata/unstable_ffi_7.js
index 02ef455ee..a0c27a71c 100644
--- a/cli/tests/testdata/unstable_ffi_7.js
+++ b/cli/tests/testdata/unstable_ffi_7.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_u8", [0, 0]);
+Deno.core.opSync("op_ffi_read_u8", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_8.js b/cli/tests/testdata/unstable_ffi_8.js
index d250c9f21..7c51f8aa3 100644
--- a/cli/tests/testdata/unstable_ffi_8.js
+++ b/cli/tests/testdata/unstable_ffi_8.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_i8", [0, 0]);
+Deno.core.opSync("op_ffi_read_i8", 0n);
diff --git a/cli/tests/testdata/unstable_ffi_9.js b/cli/tests/testdata/unstable_ffi_9.js
index f21a4cdbf..7798e4d2c 100644
--- a/cli/tests/testdata/unstable_ffi_9.js
+++ b/cli/tests/testdata/unstable_ffi_9.js
@@ -1 +1 @@
-Deno.core.opSync("op_ffi_read_u16", [0, 0]);
+Deno.core.opSync("op_ffi_read_u16", 0n);
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js
index fb4fcbfb8..d29e83fce 100644
--- a/ext/ffi/00_ffi.js
+++ b/ext/ffi/00_ffi.js
@@ -6,18 +6,18 @@
const __bootstrap = window.__bootstrap;
const {
ArrayBufferPrototype,
- Uint8Array,
+ ArrayPrototypePush,
+ ArrayPrototypeSome,
BigInt,
- Number,
+ NumberIsFinite,
+ NumberIsInteger,
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
+ PromisePrototypeThen,
TypeError,
+ Uint8Array,
} = window.__bootstrap.primordials;
- function pack64(value) {
- return [Number(value >> 32n) >>> 0, Number(value & 0xFFFFFFFFn)];
- }
-
function unpackU64([hi, lo]) {
return BigInt(hi) << 32n | BigInt(lo);
}
@@ -37,77 +37,77 @@
getUint8(offset = 0) {
return core.opSync(
"op_ffi_read_u8",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getInt8(offset = 0) {
return core.opSync(
"op_ffi_read_i8",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getUint16(offset = 0) {
return core.opSync(
"op_ffi_read_u16",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getInt16(offset = 0) {
return core.opSync(
"op_ffi_read_i16",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getUint32(offset = 0) {
return core.opSync(
"op_ffi_read_u32",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getInt32(offset = 0) {
return core.opSync(
"op_ffi_read_i32",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getBigUint64(offset = 0) {
- return unpackU64(core.opSync(
+ return core.opSync(
"op_ffi_read_u64",
- pack64(this.pointer.value + BigInt(offset)),
- ));
+ this.pointer.value + BigInt(offset),
+ );
}
getBigInt64(offset = 0) {
- return unpackI64(core.opSync(
+ return core.opSync(
"op_ffi_read_u64",
- pack64(this.pointer.value + BigInt(offset)),
- ));
+ this.pointer.value + BigInt(offset),
+ );
}
getFloat32(offset = 0) {
return core.opSync(
"op_ffi_read_f32",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getFloat64(offset = 0) {
return core.opSync(
"op_ffi_read_f64",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getCString(offset = 0) {
return core.opSync(
"op_ffi_cstr_read",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
@@ -118,11 +118,12 @@
}
copyInto(destination, offset = 0) {
- core.opSync("op_ffi_buf_copy_into", [
- pack64(this.pointer.value + BigInt(offset)),
+ core.opSync(
+ "op_ffi_buf_copy_into",
+ this.pointer.value + BigInt(offset),
destination,
destination.byteLength,
- ]);
+ );
}
}
@@ -130,12 +131,15 @@
value;
constructor(value) {
+ if (typeof value === "number") {
+ value = BigInt(value);
+ }
this.value = value;
}
static of(typedArray) {
return new UnsafePointer(
- unpackU64(core.opSync("op_ffi_ptr_of", typedArray)),
+ core.opSync("op_ffi_ptr_of", typedArray),
);
}
@@ -147,58 +151,118 @@
function prepareArgs(types, args) {
const parameters = [];
- const buffers = [];
+
+ if (types.length !== args.length) {
+ throw new TypeError("Invalid FFI call, parameter vs args count mismatch");
+ }
for (let i = 0; i < types.length; i++) {
const type = types[i];
const arg = args[i];
- if (type === "pointer") {
+ if (type === "u8" || type === "u16" || type === "u32") {
+ if (!NumberIsInteger(arg) || arg < 0) {
+ throw new TypeError(
+ `Expected FFI argument to be an unsigned integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "i8" || type === "i16" || type === "i32") {
+ if (!NumberIsInteger(arg)) {
+ throw new TypeError(
+ `Expected FFI argument to be a signed integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "u64" || type === "usize") {
+ if (
+ !(NumberIsInteger(arg) && arg >= 0 ||
+ typeof arg === "bigint" && 0n <= arg && arg <= 0xffffffffffffffffn)
+ ) {
+ throw new TypeError(
+ `Expected FFI argument to be an unsigned integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type == "i64" || type === "isize") {
+ if (
+ !(NumberIsInteger(arg) ||
+ typeof arg === "bigint" && -1n * 2n ** 63n <= arg &&
+ arg <= 2n ** 63n - 1n)
+ ) {
+ throw new TypeError(
+ `Expected FFI argument to be a signed integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "f32" || type === "f64") {
+ if (!NumberIsFinite(arg)) {
+ throw new TypeError(
+ `Expected FFI argument to be a number, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "pointer") {
if (
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, arg?.buffer) &&
arg.byteLength !== undefined
) {
- parameters.push(buffers.length);
- buffers.push(arg);
+ ArrayPrototypePush(parameters, arg);
} else if (ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)) {
- parameters.push(pack64(arg.value));
- buffers.push(undefined);
+ ArrayPrototypePush(parameters, arg.value);
} else if (arg === null) {
- parameters.push(null);
- buffers.push(undefined);
+ ArrayPrototypePush(parameters, null);
} else {
throw new TypeError(
- "Invalid ffi arg value, expected TypedArray, UnsafePointer or null",
+ "Expected FFI argument to be TypedArray, UnsafePointer or null",
);
}
- } else if (typeof arg === "bigint") {
- if (arg > 0xffffffffffffffffn) {
+ } else if (
+ typeof type === "object" && type !== null && "function" in type
+ ) {
+ if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, arg)) {
+ // Own registered callback, pass the pointer value
+ ArrayPrototypePush(parameters, arg.pointer.value);
+ } else if (arg === null) {
+ // nullptr
+ ArrayPrototypePush(parameters, null);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafeFnPointerPrototype, arg)
+ ) {
+ // Foreign function, pass the pointer value
+ ArrayPrototypePush(parameters, arg.pointer.value);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)
+ ) {
+ // Foreign function, pass the pointer value
+ ArrayPrototypePush(parameters, arg.value);
+ } else {
throw new TypeError(
- "Invalid ffi arg value, it needs to be less than 0xffffffffffffffff",
+ "Expected FFI argument to be UnsafeCallback, UnsafeFnPointer, UnsafePointer or null",
);
}
-
- parameters.push(pack64(arg));
} else {
- parameters.push(arg);
+ throw new TypeError(`Invalid FFI argument type '${type}'`);
}
}
- return { parameters, buffers };
+ return parameters;
}
- function unpackResult(type, result) {
+ function unpackNonblockingReturnValue(type, result) {
+ if (
+ typeof type === "object" && type !== null && "function" in type ||
+ type === "pointer"
+ ) {
+ return new UnsafePointer(unpackU64(result));
+ }
switch (type) {
- case "pointer":
- return new UnsafePointer(unpackU64(result));
- case "u64":
- return unpackU64(result);
+ case "isize":
case "i64":
return unpackI64(result);
case "usize":
+ case "u64":
return unpackU64(result);
- case "isize":
- return unpackI64(result);
default:
return result;
}
@@ -214,33 +278,39 @@
}
call(...args) {
- const { parameters, buffers } = prepareArgs(
+ const parameters = prepareArgs(
this.definition.parameters,
args,
);
+ const resultType = this.definition.result;
if (this.definition.nonblocking) {
- const promise = core.opAsync("op_ffi_call_ptr_nonblocking", {
- pointer: pack64(this.pointer.value),
- def: this.definition,
+ const promise = core.opAsync(
+ "op_ffi_call_ptr_nonblocking",
+ this.pointer.value,
+ this.definition,
parameters,
- buffers,
- });
+ );
- if (this.definition.result === "pointer") {
- return promise.then((value) => new UnsafePointer(unpackU64(value)));
+ if (
+ isReturnedAsBigInt(resultType)
+ ) {
+ return PromisePrototypeThen(
+ promise,
+ (result) => unpackNonblockingReturnValue(resultType, result),
+ );
}
return promise;
} else {
- const result = core.opSync("op_ffi_call_ptr", {
- pointer: pack64(this.pointer.value),
- def: this.definition,
+ const result = core.opSync(
+ "op_ffi_call_ptr",
+ this.pointer.value,
+ this.definition,
parameters,
- buffers,
- });
+ );
- if (this.definition.result === "pointer") {
- return new UnsafePointer(unpackU64(result));
+ if (isPointerType(resultType)) {
+ return new UnsafePointer(result);
}
return result;
@@ -248,6 +318,111 @@
}
}
+ const UnsafeFnPointerPrototype = UnsafeFnPointer.prototype;
+
+ function isPointerType(type) {
+ return type === "pointer" ||
+ typeof type === "object" && type !== null && "function" in type;
+ }
+
+ function isReturnedAsBigInt(type) {
+ return isPointerType(type) || type === "u64" || type === "i64" ||
+ type === "usize" || type === "isize";
+ }
+
+ function prepareUnsafeCallbackParameters(types, args) {
+ const parameters = [];
+ if (types.length === 0) {
+ return parameters;
+ }
+
+ for (let i = 0; i < types.length; i++) {
+ const type = types[i];
+ const arg = args[i];
+ ArrayPrototypePush(
+ parameters,
+ isPointerType(type) ? new UnsafePointer(arg) : arg,
+ );
+ }
+
+ return parameters;
+ }
+
+ function unwrapUnsafeCallbackReturnValue(result) {
+ if (
+ ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, result)
+ ) {
+ // Foreign function, return the pointer value
+ ArrayPrototypePush(parameters, result.value);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafeFnPointerPrototype, result)
+ ) {
+ // Foreign function, return the pointer value
+ ArrayPrototypePush(parameters, result.pointer.value);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, result)
+ ) {
+ // Own registered callback, return the pointer value.
+ // Note that returning the ResourceId here would not work as
+ // the Rust side code cannot access OpState to get the resource.
+ ArrayPrototypePush(parameters, result.pointer.value);
+ }
+ return result;
+ }
+
+ function createInternalCallback(definition, callback) {
+ const mustUnwrap = isPointerType(definition.result);
+ return (...args) => {
+ const convertedArgs = prepareUnsafeCallbackParameters(
+ definition.parameters,
+ args,
+ );
+ const result = callback(...convertedArgs);
+ if (mustUnwrap) {
+ return unwrapUnsafeCallbackReturnValue(result);
+ }
+ return result;
+ };
+ }
+
+ class UnsafeCallback {
+ #rid;
+ #internal;
+ definition;
+ callback;
+ pointer;
+
+ constructor(definition, callback) {
+ if (definition.nonblocking) {
+ throw new TypeError(
+ "Invalid UnsafeCallback, cannot be nonblocking",
+ );
+ }
+ const needsWrapping = isPointerType(definition.result) ||
+ ArrayPrototypeSome(definition.parameters, isPointerType);
+ const internalCallback = needsWrapping
+ ? createInternalCallback(definition, callback)
+ : callback;
+
+ const [rid, pointer] = core.opSync(
+ "op_ffi_unsafe_callback_create",
+ definition,
+ internalCallback,
+ );
+ this.#rid = rid;
+ this.pointer = new UnsafePointer(pointer);
+ this.#internal = internalCallback;
+ this.definition = definition;
+ this.callback = callback;
+ }
+
+ close() {
+ core.close(this.#rid);
+ }
+ }
+
+ const UnsafeCallbackPrototype = UnsafeCallback.prototype;
+
class DynamicLibrary {
#rid;
symbols = {};
@@ -267,19 +442,12 @@
const name = symbols[symbol].name || symbol;
let value = core.opSync(
"op_ffi_get_static",
- {
- rid: this.#rid,
- name,
- type,
- },
+ this.#rid,
+ name,
+ type,
);
- if (type === "pointer" || type === "u64") {
- value = unpackU64(value);
- if (type === "pointer") {
- value = new UnsafePointer(value);
- }
- } else if (type === "i64") {
- value = unpackI64(value);
+ if (type === "pointer") {
+ value = new UnsafePointer(value);
}
ObjectDefineProperty(
this.symbols,
@@ -298,33 +466,47 @@
const types = symbols[symbol].parameters;
const resultType = symbols[symbol].result;
- const fn = (...args) => {
- const { parameters, buffers } = prepareArgs(types, args);
+ let fn;
+ if (isNonBlocking) {
+ const needsUnpacking = isReturnedAsBigInt(resultType);
+ fn = (...args) => {
+ const parameters = prepareArgs(types, args);
- if (isNonBlocking) {
- const promise = core.opAsync("op_ffi_call_nonblocking", {
- rid: this.#rid,
+ const promise = core.opAsync(
+ "op_ffi_call_nonblocking",
+ this.#rid,
symbol,
parameters,
- buffers,
- });
+ );
- if (resultType === "pointer") {
- return promise.then((result) => unpackResult(resultType, result));
+ if (needsUnpacking) {
+ return PromisePrototypeThen(
+ promise,
+ (result) => unpackNonblockingReturnValue(resultType, result),
+ );
}
return promise;
- } else {
- const result = core.opSync("op_ffi_call", {
- rid: this.#rid,
+ };
+ } else {
+ const mustWrap = isPointerType(resultType);
+ fn = (...args) => {
+ const parameters = prepareArgs(types, args);
+
+ const result = core.opSync(
+ "op_ffi_call",
+ this.#rid,
symbol,
parameters,
- buffers,
- });
+ );
- return unpackResult(resultType, result);
- }
- };
+ if (mustWrap) {
+ return new UnsafePointer(result);
+ }
+
+ return result;
+ };
+ }
ObjectDefineProperty(
this.symbols,
@@ -352,6 +534,7 @@
window.__bootstrap.ffi = {
dlopen,
+ UnsafeCallback,
UnsafePointer,
UnsafePointerView,
UnsafeFnPointer,
diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs
index 8a1a75b1d..691d44460 100644
--- a/ext/ffi/lib.rs
+++ b/ext/ffi/lib.rs
@@ -1,16 +1,19 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::bad_resource_id;
+use core::ptr::NonNull;
+use deno_core::anyhow::anyhow;
use deno_core::error::generic_error;
use deno_core::error::range_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
+use deno_core::futures::Future;
use deno_core::include_js_files;
use deno_core::op;
-use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
+use deno_core::serde_v8;
+use deno_core::v8;
use deno_core::Extension;
use deno_core::OpState;
use deno_core::Resource;
@@ -18,6 +21,8 @@ use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use dlopen::raw::Library;
use libffi::middle::Arg;
+use libffi::middle::Cif;
+use libffi::raw::*;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
@@ -31,6 +36,10 @@ use std::path::PathBuf;
use std::ptr;
use std::rc::Rc;
+thread_local! {
+ static IS_ISOLATE_THREAD: RefCell<bool> = RefCell::new(false);
+}
+
pub struct Unstable(pub bool);
fn check_unstable(state: &OpState, api_name: &str) {
@@ -66,6 +75,32 @@ struct Symbol {
unsafe impl Send for Symbol {}
unsafe impl Sync for Symbol {}
+#[derive(Clone)]
+struct PtrSymbol {
+ cif: libffi::middle::Cif,
+ ptr: libffi::middle::CodePtr,
+}
+
+impl PtrSymbol {
+ fn new(fn_ptr: u64, def: &ForeignFunction) -> Self {
+ let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
+ let cif = libffi::middle::Cif::new(
+ def
+ .parameters
+ .clone()
+ .into_iter()
+ .map(libffi::middle::Type::from),
+ def.result.into(),
+ );
+
+ Self { cif, ptr }
+ }
+}
+
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for PtrSymbol {}
+unsafe impl Sync for PtrSymbol {}
+
struct DynamicLibraryResource {
lib: Library,
symbols: HashMap<String, Symbol>,
@@ -87,6 +122,7 @@ impl DynamicLibraryResource {
name: String,
foreign_fn: ForeignFunction,
) -> Result<(), AnyError> {
+ IS_ISOLATE_THREAD.with(|s| s.replace(true));
let symbol = match &foreign_fn.name {
Some(symbol) => symbol,
None => &name,
@@ -163,6 +199,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
op_ffi_read_u64::decl::<P>(),
op_ffi_read_f32::decl::<P>(),
op_ffi_read_f64::decl::<P>(),
+ op_ffi_unsafe_callback_create::decl::<P>(),
])
.state(move |state| {
// Stolen from deno_webgpu, is there a better option?
@@ -172,6 +209,8 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
.build()
}
+/// Defines the accepted types that can be used as
+/// parameters and return values in FFI.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
enum NativeType {
@@ -189,6 +228,7 @@ enum NativeType {
F32,
F64,
Pointer,
+ Function {},
}
impl From<NativeType> for libffi::middle::Type {
@@ -208,10 +248,13 @@ impl From<NativeType> for libffi::middle::Type {
NativeType::F32 => libffi::middle::Type::f32(),
NativeType::F64 => libffi::middle::Type::f64(),
NativeType::Pointer => libffi::middle::Type::pointer(),
+ NativeType::Function {} => libffi::middle::Type::pointer(),
}
}
}
+/// Intermediate format for easy translation from NativeType + V8 value
+/// to libffi argument types.
#[repr(C)]
union NativeValue {
void_value: (),
@@ -231,68 +274,9 @@ union NativeValue {
}
impl NativeValue {
- fn new(native_type: NativeType, value: Value) -> Result<Self, AnyError> {
- let value = match native_type {
- NativeType::Void => Self { void_value: () },
- NativeType::U8 => Self {
- u8_value: value_as_uint::<u8>(value)?,
- },
- NativeType::I8 => Self {
- i8_value: value_as_int::<i8>(value)?,
- },
- NativeType::U16 => Self {
- u16_value: value_as_uint::<u16>(value)?,
- },
- NativeType::I16 => Self {
- i16_value: value_as_int::<i16>(value)?,
- },
- NativeType::U32 => Self {
- u32_value: value_as_uint::<u32>(value)?,
- },
- NativeType::I32 => Self {
- i32_value: value_as_int::<i32>(value)?,
- },
- NativeType::U64 => Self {
- u64_value: value_as_uint::<u64>(value)?,
- },
- NativeType::I64 => Self {
- i64_value: value_as_int::<i64>(value)?,
- },
- NativeType::USize => Self {
- usize_value: value_as_uint::<usize>(value)?,
- },
- NativeType::ISize => Self {
- isize_value: value_as_int::<isize>(value)?,
- },
- NativeType::F32 => Self {
- f32_value: value_as_f32(value)?,
- },
- NativeType::F64 => Self {
- f64_value: value_as_f64(value)?,
- },
- NativeType::Pointer => {
- if value.is_null() {
- Self {
- pointer: ptr::null(),
- }
- } else {
- Self {
- pointer: u64::from(serde_json::from_value::<U32x2>(value)?)
- as *const u8,
- }
- }
- }
- };
- Ok(value)
- }
-
- fn buffer(ptr: *const u8) -> Self {
- Self { pointer: ptr }
- }
-
unsafe fn as_arg(&self, native_type: NativeType) -> Arg {
match native_type {
- NativeType::Void => Arg::new(&self.void_value),
+ NativeType::Void => unreachable!(),
NativeType::U8 => Arg::new(&self.u8_value),
NativeType::I8 => Arg::new(&self.i8_value),
NativeType::U16 => Arg::new(&self.u16_value),
@@ -305,56 +289,123 @@ impl NativeValue {
NativeType::ISize => Arg::new(&self.isize_value),
NativeType::F32 => Arg::new(&self.f32_value),
NativeType::F64 => Arg::new(&self.f64_value),
- NativeType::Pointer => Arg::new(&self.pointer),
+ NativeType::Pointer | NativeType::Function {} => Arg::new(&self.pointer),
}
}
-}
-
-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!(
- "Expected FFI argument to be an unsigned integer, but got {:?}",
- value
- ))),
- }
-}
-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)));
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ unsafe fn to_value(&self, native_type: NativeType) -> Value {
+ match native_type {
+ NativeType::Void => Value::Null,
+ NativeType::U8 => Value::from(self.u8_value),
+ NativeType::I8 => Value::from(self.i8_value),
+ NativeType::U16 => Value::from(self.u16_value),
+ NativeType::I16 => Value::from(self.i16_value),
+ NativeType::U32 => Value::from(self.u32_value),
+ NativeType::I32 => Value::from(self.i32_value),
+ NativeType::U64 => {
+ json!(U32x2::from(self.u64_value))
+ }
+ NativeType::I64 => {
+ json!(U32x2::from(self.i64_value as u64))
+ }
+ NativeType::USize => {
+ json!(U32x2::from(self.usize_value as u64))
+ }
+ NativeType::ISize => {
+ json!(U32x2::from(self.isize_value as u64))
+ }
+ NativeType::F32 => Value::from(self.f32_value),
+ NativeType::F64 => Value::from(self.f64_value),
+ NativeType::Pointer | NativeType::Function {} => {
+ json!(U32x2::from(self.pointer as u64))
+ }
+ }
}
- match value.as_i64().and_then(|v| T::try_from(v).ok()) {
- Some(value) => Ok(value),
- None => Err(type_error(format!(
- "Expected FFI argument to be a signed integer, but got {:?}",
- value
- ))),
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ unsafe fn to_v8<'scope>(
+ &self,
+ scope: &mut v8::HandleScope<'scope>,
+ native_type: NativeType,
+ ) -> serde_v8::Value<'scope> {
+ match native_type {
+ NativeType::Void => {
+ let local_value: v8::Local<v8::Value> = v8::undefined(scope).into();
+ local_value.into()
+ }
+ NativeType::U8 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u8_value as u32).into();
+ local_value.into()
+ }
+ NativeType::I8 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i8_value as i32).into();
+ local_value.into()
+ }
+ NativeType::U16 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u16_value as u32).into();
+ local_value.into()
+ }
+ NativeType::I16 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i16_value as i32).into();
+ local_value.into()
+ }
+ NativeType::U32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u32_value).into();
+ local_value.into()
+ }
+ NativeType::I32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i32_value).into();
+ local_value.into()
+ }
+ NativeType::U64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, self.u64_value).into();
+ local_value.into()
+ }
+ NativeType::I64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_i64(scope, self.i64_value).into();
+ local_value.into()
+ }
+ NativeType::USize => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, self.usize_value as u64).into();
+ local_value.into()
+ }
+ NativeType::ISize => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_i64(scope, self.isize_value as i64).into();
+ local_value.into()
+ }
+ NativeType::F32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Number::new(scope, self.f32_value as f64).into();
+ local_value.into()
+ }
+ NativeType::F64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Number::new(scope, self.f64_value).into();
+ local_value.into()
+ }
+ NativeType::Pointer | NativeType::Function {} => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, self.pointer as u64).into();
+ local_value.into()
+ }
+ }
}
}
-fn value_as_f32(value: Value) -> Result<f32, AnyError> {
- Ok(value_as_f64(value)? as f32)
-}
+unsafe impl Send for NativeValue {}
-fn value_as_f64(value: Value) -> Result<f64, AnyError> {
- match value.as_f64() {
- Some(value) => Ok(value),
- None => Err(type_error(format!(
- "Expected FFI argument to be a double, but got {:?}",
- value
- ))),
- }
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
+#[derive(Serialize, Debug, Clone, Copy)]
struct U32x2(u32, u32);
impl From<u64> for U32x2 {
@@ -363,31 +414,6 @@ impl From<u64> for U32x2 {
}
}
-impl From<U32x2> for u64 {
- fn from(value: U32x2) -> Self {
- (value.0 as u64) << 32 | value.1 as 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 {
@@ -535,322 +561,796 @@ where
Ok(state.resource_table.add(resource))
}
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FfiCallArgs {
- rid: ResourceId,
- symbol: String,
- parameters: Vec<Value>,
- buffers: Vec<Option<ZeroCopyBuf>>,
-}
+fn ffi_parse_args<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ args: serde_v8::Value<'scope>,
+ parameter_types: &[NativeType],
+) -> Result<Vec<NativeValue>, AnyError>
+where
+ 'scope: 'scope,
+{
+ if parameter_types.is_empty() {
+ return Ok(vec![]);
+ }
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FfiCallPtrArgs {
- pointer: U32x2,
- def: ForeignFunction,
- parameters: Vec<Value>,
- buffers: Vec<Option<ZeroCopyBuf>>,
-}
+ let args = v8::Local::<v8::Array>::try_from(args.v8_value)
+ .map_err(|_| type_error("Invalid FFI parameters, expected Array"))?;
+ let mut ffi_args: Vec<NativeValue> =
+ Vec::with_capacity(parameter_types.len());
-impl From<FfiCallPtrArgs> for FfiCallArgs {
- fn from(args: FfiCallPtrArgs) -> Self {
- FfiCallArgs {
- rid: 0,
- symbol: String::new(),
- parameters: args.parameters,
- buffers: args.buffers,
+ for (index, native_type) in parameter_types.iter().enumerate() {
+ let value = args.get_index(scope, index as u32).unwrap();
+ match native_type {
+ NativeType::Void => {
+ unreachable!();
+ }
+ NativeType::U8 => {
+ let value = value
+ .uint32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI u8 type, expected number"))?
+ as u8;
+ ffi_args.push(NativeValue { u8_value: value });
+ }
+ NativeType::I8 => {
+ let value = value
+ .int32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI i8 type, expected number"))?
+ as i8;
+ ffi_args.push(NativeValue { i8_value: value });
+ }
+ NativeType::U16 => {
+ let value = value
+ .uint32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI u16 type, expected number"))?
+ as u16;
+ ffi_args.push(NativeValue { u16_value: value });
+ }
+ NativeType::I16 => {
+ let value = value
+ .int32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI i16 type, expected number"))?
+ as i16;
+ ffi_args.push(NativeValue { i16_value: value });
+ }
+ NativeType::U32 => {
+ let value = value
+ .uint32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI u32 type, expected number"))?
+ as u32;
+ ffi_args.push(NativeValue { u32_value: value });
+ }
+ NativeType::I32 => {
+ let value = value
+ .int32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI i32 type, expected number"))?
+ as i32;
+ ffi_args.push(NativeValue { i32_value: value });
+ }
+ NativeType::U64 => {
+ let value: u64 = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.u64_value().0
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI u64 type, expected number")
+ })? as u64
+ };
+ ffi_args.push(NativeValue { u64_value: value });
+ }
+ NativeType::I64 => {
+ let value: i64 = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.i64_value().0
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI i64 type, expected number")
+ })? as i64
+ };
+ ffi_args.push(NativeValue { i64_value: value });
+ }
+ NativeType::USize => {
+ let value: usize = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.u64_value().0 as usize
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI usize type, expected number")
+ })? as usize
+ };
+ ffi_args.push(NativeValue { usize_value: value });
+ }
+ NativeType::ISize => {
+ let value: isize = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.i64_value().0 as isize
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI isize type, expected number")
+ })? as isize
+ };
+ ffi_args.push(NativeValue { isize_value: value });
+ }
+ NativeType::F32 => {
+ let value = value
+ .number_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI f32 type, expected number"))?
+ as f32;
+ ffi_args.push(NativeValue { f32_value: value });
+ }
+ NativeType::F64 => {
+ let value = value
+ .number_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI f64 type, expected number"))?
+ as f64;
+ ffi_args.push(NativeValue { f64_value: value });
+ }
+ NativeType::Pointer => {
+ if value.is_null() {
+ let value: *const u8 = ptr::null();
+ ffi_args.push(NativeValue { pointer: value })
+ } else if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ let value = value.u64_value().0 as *const u8;
+ ffi_args.push(NativeValue { pointer: value });
+ } else if value.is_array_buffer() || value.is_array_buffer_view() {
+ let value: ZeroCopyBuf = serde_v8::from_v8(scope, value)?;
+ let value: &[u8] = &value[..];
+ ffi_args.push(NativeValue {
+ pointer: value.as_ptr(),
+ });
+ } else {
+ return Err(type_error("Invalid FFI pointer type, expected null, BigInt, ArrayBuffer, or ArrayBufferView"));
+ }
+ }
+ NativeType::Function {} => {
+ if value.is_null() {
+ let value: *const u8 = ptr::null();
+ ffi_args.push(NativeValue { pointer: value })
+ } else if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ let value = value.u64_value().0 as *const u8;
+ ffi_args.push(NativeValue { pointer: value });
+ } else {
+ return Err(type_error(
+ "Invalid FFI function type, expected null, or BigInt",
+ ));
+ }
+ }
}
}
+
+ Ok(ffi_args)
}
-impl FfiCallPtrArgs {
- fn get_symbol(&self) -> Symbol {
- let fn_ptr: u64 = self.pointer.into();
- let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
- let cif = libffi::middle::Cif::new(
- self
- .def
- .parameters
- .clone()
- .into_iter()
- .map(libffi::middle::Type::from),
- self.def.result.into(),
- );
+fn ffi_call(
+ call_args: Vec<NativeValue>,
+ cif: &libffi::middle::Cif,
+ fun_ptr: libffi::middle::CodePtr,
+ parameter_types: &[NativeType],
+ result_type: NativeType,
+) -> Result<NativeValue, AnyError> {
+ let call_args: Vec<Arg> = call_args
+ .iter()
+ .enumerate()
+ .map(|(index, ffi_arg)| unsafe {
+ ffi_arg.as_arg(*parameter_types.get(index).unwrap())
+ })
+ .collect();
- Symbol {
- cif,
- ptr,
- parameter_types: self.def.parameters.clone(),
- result_type: self.def.result,
+ Ok(match result_type {
+ NativeType::Void => NativeValue {
+ void_value: unsafe { cif.call::<()>(fun_ptr, &call_args) },
+ },
+ NativeType::U8 => NativeValue {
+ u8_value: unsafe { cif.call::<u8>(fun_ptr, &call_args) },
+ },
+ NativeType::I8 => NativeValue {
+ i8_value: unsafe { cif.call::<i8>(fun_ptr, &call_args) },
+ },
+ NativeType::U16 => NativeValue {
+ u16_value: unsafe { cif.call::<u16>(fun_ptr, &call_args) },
+ },
+ NativeType::I16 => NativeValue {
+ i16_value: unsafe { cif.call::<i16>(fun_ptr, &call_args) },
+ },
+ NativeType::U32 => NativeValue {
+ u32_value: unsafe { cif.call::<u32>(fun_ptr, &call_args) },
+ },
+ NativeType::I32 => NativeValue {
+ i32_value: unsafe { cif.call::<i32>(fun_ptr, &call_args) },
+ },
+ NativeType::U64 => NativeValue {
+ u64_value: unsafe { cif.call::<u64>(fun_ptr, &call_args) },
+ },
+ NativeType::I64 => NativeValue {
+ i64_value: unsafe { cif.call::<i64>(fun_ptr, &call_args) },
+ },
+ NativeType::USize => NativeValue {
+ usize_value: unsafe { cif.call::<usize>(fun_ptr, &call_args) },
+ },
+ NativeType::ISize => NativeValue {
+ isize_value: unsafe { cif.call::<isize>(fun_ptr, &call_args) },
+ },
+ NativeType::F32 => NativeValue {
+ f32_value: unsafe { cif.call::<f32>(fun_ptr, &call_args) },
+ },
+ NativeType::F64 => NativeValue {
+ f64_value: unsafe { cif.call::<f64>(fun_ptr, &call_args) },
+ },
+ NativeType::Pointer | NativeType::Function {} => NativeValue {
+ pointer: unsafe { cif.call::<*const u8>(fun_ptr, &call_args) },
+ },
+ })
+}
+
+struct UnsafeCallbackResource {
+ // Closure is never directly touched, but it keeps the C callback alive
+ // until `close()` method is called.
+ #[allow(dead_code)]
+ closure: libffi::middle::Closure<'static>,
+ info: *const CallbackInfo,
+}
+
+impl Resource for UnsafeCallbackResource {
+ fn name(&self) -> Cow<str> {
+ "unsafecallback".into()
+ }
+
+ fn close(self: Rc<Self>) {
+ // SAFETY: This drops the closure and the callback info associated with it.
+ // Any retained function pointers to the closure become dangling pointers.
+ // It is up to the user to know that it is safe to call the `close()` on the
+ // UnsafeCallback instance.
+ unsafe {
+ let info = Box::from_raw(self.info as *mut CallbackInfo);
+ let isolate = info.isolate.as_mut().unwrap();
+ v8::Global::from_raw(isolate, info.callback);
+ v8::Global::from_raw(isolate, info.context);
}
}
}
-fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
- let buffers: Vec<Option<&[u8]>> = args
- .buffers
- .iter()
- .map(|buffer| buffer.as_ref().map(|buffer| &buffer[..]))
- .collect();
-
- let mut native_values: Vec<NativeValue> = vec![];
+struct CallbackInfo {
+ pub callback: NonNull<v8::Function>,
+ pub context: NonNull<v8::Context>,
+ pub isolate: *mut v8::Isolate,
+}
- for (&native_type, value) in symbol
- .parameter_types
- .iter()
- .zip(args.parameters.into_iter())
- {
- match native_type {
- NativeType::Pointer => match value.as_u64() {
- Some(idx) => {
- let buf = buffers
- .get(idx as usize)
- .ok_or_else(|| {
- generic_error(format!("No buffer present at index {}", idx))
- })?
- .unwrap();
- native_values.push(NativeValue::buffer(buf.as_ptr()));
- }
- _ => {
- let value = NativeValue::new(native_type, value)?;
- native_values.push(value);
- }
- },
+unsafe extern "C" fn deno_ffi_callback(
+ cif: &libffi::low::ffi_cif,
+ result: &mut c_void,
+ args: *const *const c_void,
+ info: &CallbackInfo,
+) {
+ let isolate = &mut *info.isolate;
+ let callback = v8::Global::from_raw(isolate, info.callback);
+ let context = std::mem::transmute::<
+ NonNull<v8::Context>,
+ v8::Local<v8::Context>,
+ >(info.context);
+ IS_ISOLATE_THREAD.with(|is_event_loop_thread| {
+ if !(*is_event_loop_thread.borrow()) {
+ // Call from another thread, not yet supported.
+ eprintln!(
+ "Calling Deno FFI's callbacks from other threads is not supported"
+ );
+ std::process::exit(1);
+ }
+ });
+ // Call from main thread. If this callback is being triggered due to a
+ // function call coming from Deno itself, then this callback will build
+ // ontop of that stack.
+ // If this callback is being triggered outside of Deno (for example from a
+ // signal handler) then this will either create an empty new stack if
+ // Deno currently has nothing running and is waiting for promises to resolve,
+ // or will (very incorrectly) build ontop of whatever stack exists.
+ // The callback will even be called through from a `while (true)` liveloop, but
+ // it somehow cannot change the values that the loop sees, even if they both
+ // refer the same `let bool_value`.
+ let mut cb_scope = v8::CallbackScope::new(context);
+ let mut scope = v8::HandleScope::new(&mut cb_scope);
+ let func = callback.open(&mut scope);
+ let result = result as *mut c_void;
+ let repr: &[*mut ffi_type] =
+ std::slice::from_raw_parts(cif.arg_types, cif.nargs as usize);
+ let vals: &[*const c_void] =
+ std::slice::from_raw_parts(args, cif.nargs as usize);
+
+ let mut params: Vec<v8::Local<v8::Value>> = vec![];
+ for (repr, val) in repr.iter().zip(vals) {
+ let value: v8::Local<v8::Value> = match (*(*repr)).type_ as _ {
+ FFI_TYPE_FLOAT => {
+ let value = *((*val) as *const f32);
+ v8::Number::new(&mut scope, value as f64).into()
+ }
+ FFI_TYPE_DOUBLE => {
+ let value = *((*val) as *const f64);
+ v8::Number::new(&mut scope, value).into()
+ }
+ FFI_TYPE_SINT8 => {
+ let value = *((*val) as *const i8);
+ v8::Integer::new(&mut scope, value as i32).into()
+ }
+ FFI_TYPE_UINT8 => {
+ let value = *((*val) as *const u8);
+ v8::Integer::new_from_unsigned(&mut scope, value as u32).into()
+ }
+ FFI_TYPE_SINT16 => {
+ let value = *((*val) as *const i16);
+ v8::Integer::new(&mut scope, value as i32).into()
+ }
+ FFI_TYPE_UINT16 => {
+ let value = *((*val) as *const u16);
+ v8::Integer::new_from_unsigned(&mut scope, value as u32).into()
+ }
+ FFI_TYPE_INT | FFI_TYPE_SINT32 => {
+ let value = *((*val) as *const i32);
+ v8::Integer::new(&mut scope, value).into()
+ }
+ FFI_TYPE_UINT32 => {
+ let value = *((*val) as *const u32);
+ v8::Integer::new_from_unsigned(&mut scope, value).into()
+ }
+ FFI_TYPE_SINT64 => {
+ let result = *((*val) as *const i64);
+ v8::BigInt::new_from_i64(&mut scope, result).into()
+ }
+ FFI_TYPE_POINTER | FFI_TYPE_STRUCT | FFI_TYPE_UINT64 => {
+ let result = *((*val) as *const u64);
+ v8::BigInt::new_from_u64(&mut scope, result).into()
+ }
+ FFI_TYPE_VOID => v8::undefined(&mut scope).into(),
_ => {
- let value = NativeValue::new(native_type, value)?;
- native_values.push(value);
+ unreachable!()
}
- }
+ };
+ params.push(value);
}
- let call_args = symbol
- .parameter_types
- .iter()
- .zip(native_values.iter())
- .map(|(&native_type, native_value)| unsafe {
- native_value.as_arg(native_type)
- })
- .collect::<Vec<_>>();
+ let recv = v8::undefined(&mut scope);
+ let call_result = func.call(&mut scope, recv.into(), &params);
+ std::mem::forget(callback);
+
+ if call_result.is_none() {
+ // JS function threw an exception. Set the return value to zero and return.
+ // The exception continue propagating up the call chain when the event loop
+ // resumes.
+ match (*cif.rtype).type_ as _ {
+ FFI_TYPE_INT | FFI_TYPE_SINT32 | FFI_TYPE_UINT32 => {
+ // zero is equal for signed and unsigned alike
+ *(result as *mut u32) = 0;
+ }
+ FFI_TYPE_FLOAT => {
+ *(result as *mut f32) = 0.0;
+ }
+ FFI_TYPE_DOUBLE => {
+ *(result as *mut f64) = 0.0;
+ }
+ FFI_TYPE_SINT8 | FFI_TYPE_UINT8 => {
+ // zero is equal for signed and unsigned alike
+ *(result as *mut u8) = 0;
+ }
+ FFI_TYPE_SINT16 | FFI_TYPE_UINT16 => {
+ // zero is equal for signed and unsigned alike
+ *(result as *mut u16) = 0;
+ }
+ FFI_TYPE_POINTER | FFI_TYPE_STRUCT | FFI_TYPE_UINT64
+ | FFI_TYPE_SINT64 => {
+ *(result as *mut u64) = 0;
+ }
+ FFI_TYPE_VOID => {
+ // nop
+ }
+ _ => {
+ unreachable!();
+ }
+ };
- Ok(match symbol.result_type {
- NativeType::Void => {
- json!(unsafe { symbol.cif.call::<()>(symbol.ptr, &call_args) })
- }
- NativeType::U8 => {
- json!(unsafe { symbol.cif.call::<u8>(symbol.ptr, &call_args) })
+ return;
+ }
+ let value = call_result.unwrap();
+
+ match (*cif.rtype).type_ as _ {
+ FFI_TYPE_INT | FFI_TYPE_SINT32 => {
+ *(result as *mut i32) = value
+ .int32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i32;
}
- NativeType::I8 => {
- json!(unsafe { symbol.cif.call::<i8>(symbol.ptr, &call_args) })
+ FFI_TYPE_FLOAT => {
+ *(result as *mut f32) = value
+ .number_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as f32;
}
- NativeType::U16 => {
- json!(unsafe { symbol.cif.call::<u16>(symbol.ptr, &call_args) })
+ FFI_TYPE_DOUBLE => {
+ *(result as *mut f64) = value
+ .number_value(&mut scope)
+ .expect("Unable to deserialize result parameter.");
}
- NativeType::I16 => {
- json!(unsafe { symbol.cif.call::<i16>(symbol.ptr, &call_args) })
+ FFI_TYPE_POINTER | FFI_TYPE_STRUCT => {
+ if value.is_array_buffer() | value.is_array_buffer_view() {
+ let value: ZeroCopyBuf = serde_v8::from_v8(&mut scope, value)
+ .expect("Unable to deserialize result parameter.");
+ let value: &[u8] = &value[..];
+ *(result as *mut *const u8) = value.as_ptr();
+ } else if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ *(result as *mut u64) = value.u64_value().0;
+ } else if value.is_null() {
+ *(result as *mut *const c_void) = ptr::null();
+ } else {
+ // Fallthrough: Probably someone returned a number but this could
+ // also be eg. a string. This is essentially UB.
+ *(result as *mut u64) = value
+ .integer_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u64;
+ }
}
- NativeType::U32 => {
- json!(unsafe { symbol.cif.call::<u32>(symbol.ptr, &call_args) })
+ FFI_TYPE_SINT8 => {
+ *(result as *mut i8) = value
+ .int32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i8;
}
- NativeType::I32 => {
- json!(unsafe { symbol.cif.call::<i32>(symbol.ptr, &call_args) })
+ FFI_TYPE_UINT8 => {
+ *(result as *mut u8) = value
+ .uint32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u8;
}
- NativeType::U64 => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<u64>(symbol.ptr, &call_args)
- }))
+ FFI_TYPE_SINT16 => {
+ *(result as *mut i16) = value
+ .int32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i16;
}
- NativeType::I64 => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<i64>(symbol.ptr, &call_args)
- } as u64))
+ FFI_TYPE_UINT16 => {
+ *(result as *mut u16) = value
+ .uint32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u16;
}
- NativeType::USize => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<usize>(symbol.ptr, &call_args)
- } as u64))
+ FFI_TYPE_UINT32 => {
+ *(result as *mut u32) = value
+ .uint32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.");
}
- NativeType::ISize => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<isize>(symbol.ptr, &call_args)
- } as u64))
+ FFI_TYPE_SINT64 => {
+ if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ *(result as *mut i64) = value.i64_value().0;
+ } else {
+ *(result as *mut i64) = value
+ .integer_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i64;
+ }
}
- NativeType::F32 => {
- json!(unsafe { symbol.cif.call::<f32>(symbol.ptr, &call_args) })
+ FFI_TYPE_UINT64 => {
+ if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ *(result as *mut u64) = value.u64_value().0;
+ } else {
+ *(result as *mut u64) = value
+ .integer_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u64;
+ }
}
- NativeType::F64 => {
- json!(unsafe { symbol.cif.call::<f64>(symbol.ptr, &call_args) })
+ FFI_TYPE_VOID => {
+ // nop
}
- NativeType::Pointer => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<*const u8>(symbol.ptr, &call_args)
- } as u64))
+ _ => {
+ unreachable!();
}
- })
+ };
}
-#[op]
-fn op_ffi_call_ptr<FP>(
+#[derive(Deserialize)]
+struct RegisterCallbackArgs {
+ parameters: Vec<NativeType>,
+ result: NativeType,
+}
+
+#[op(v8)]
+fn op_ffi_unsafe_callback_create<FP, 'scope>(
state: &mut deno_core::OpState,
- args: FfiCallPtrArgs,
-) -> Result<Value, AnyError>
+ scope: &mut v8::HandleScope<'scope>,
+ args: RegisterCallbackArgs,
+ cb: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
{
- check_unstable(state, "Deno.UnsafeFnPointer#call");
-
+ check_unstable(state, "Deno.UnsafeCallback");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- let symbol = args.get_symbol();
- ffi_call(args.into(), &symbol)
+ let v8_value = cb.v8_value;
+ let cb = v8::Local::<v8::Function>::try_from(v8_value)?;
+
+ let isolate: *mut v8::Isolate = &mut *scope as &mut v8::Isolate;
+ let callback = v8::Global::new(scope, cb).into_raw();
+ let current_context = scope.get_current_context();
+ let context = v8::Global::new(scope, current_context).into_raw();
+
+ let info = Box::leak(Box::new(CallbackInfo {
+ callback,
+ context,
+ isolate,
+ }));
+ let cif = Cif::new(
+ args.parameters.into_iter().map(libffi::middle::Type::from),
+ libffi::middle::Type::from(args.result),
+ );
+
+ let closure = libffi::middle::Closure::new(cif, deno_ffi_callback, info);
+ let ptr = *closure.code_ptr() as usize as u64;
+ let resource = UnsafeCallbackResource { closure, info };
+ let rid = state.resource_table.add(resource);
+
+ let rid_local = v8::Integer::new_from_unsigned(scope, rid);
+ let ptr_local = v8::BigInt::new_from_u64(scope, ptr);
+ let array = v8::Array::new(scope, 2);
+ array.set_index(scope, 0, rid_local.into());
+ array.set_index(scope, 1, ptr_local.into());
+ let array_value: v8::Local<v8::Value> = array.into();
+
+ Ok(array_value.into())
}
-#[op]
-async fn op_ffi_call_ptr_nonblocking<FP>(
+#[op(v8)]
+fn op_ffi_call_ptr<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: Rc<RefCell<deno_core::OpState>>,
- args: FfiCallPtrArgs,
-) -> Result<Value, AnyError>
+ pointer: u64,
+ def: ForeignFunction,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable2(&state, "Deno.UnsafeFnPointer#call");
-
{
let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- }
+ };
- let symbol = args.get_symbol();
- tokio::task::spawn_blocking(move || ffi_call(args.into(), &symbol))
- .await
- .unwrap()
+ let symbol = PtrSymbol::new(pointer, &def);
+ let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
+
+ let result = ffi_call(
+ call_args,
+ &symbol.cif,
+ symbol.ptr,
+ &def.parameters,
+ def.result,
+ )?;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ let result = unsafe { result.to_v8(scope, def.result) };
+ Ok(result)
}
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FfiGetArgs {
- rid: ResourceId,
- name: String,
- r#type: NativeType,
+#[op(v8)]
+fn op_ffi_call_ptr_nonblocking<'scope, FP>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ pointer: u64,
+ def: ForeignFunction,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<impl Future<Output = Result<Value, AnyError>>, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable2(&state, "Deno.UnsafeFnPointer#call");
+ {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+ };
+
+ let symbol = PtrSymbol::new(pointer, &def);
+ let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
+
+ let join_handle = tokio::task::spawn_blocking(move || {
+ let PtrSymbol { cif, ptr } = symbol.clone();
+ ffi_call(call_args, &cif, ptr, &def.parameters, def.result)
+ });
+
+ Ok(async move {
+ let result = join_handle
+ .await
+ .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ Ok(unsafe { result.to_value(def.result) })
+ })
}
-#[op]
-fn op_ffi_get_static(
+#[op(v8)]
+fn op_ffi_get_static<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
- args: FfiGetArgs,
-) -> Result<Value, AnyError> {
- let resource = state
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
+ rid: ResourceId,
+ name: String,
+ static_type: NativeType,
+) -> Result<serde_v8::Value<'scope>, AnyError> {
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
- let data_ptr = resource.get_static(args.name)? as *const u8;
+ let data_ptr = resource.get_static(name)? as *const u8;
- Ok(match args.r#type {
+ Ok(match static_type {
NativeType::Void => {
- unreachable!();
+ return Err(type_error("Invalid FFI static type 'void'"));
}
NativeType::U8 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u8) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u8) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::I8 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i8) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i8) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::U16 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u16) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u16) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::I16 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i16) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i16) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::U32 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u32) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u32) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::I32 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i32) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i32) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::U64 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u64) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u64) };
+ let big_int = v8::BigInt::new_from_u64(scope, result);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::I64 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i64) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i64) };
+ let big_int = v8::BigInt::new_from_i64(scope, result);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::USize => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const usize) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const usize) };
+ let big_int = v8::BigInt::new_from_u64(scope, result as u64);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::ISize => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const isize) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const isize) };
+ let big_int = v8::BigInt::new_from_i64(scope, result as i64);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::F32 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const f32) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const f32) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::F64 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const f64) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const f64) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
- NativeType::Pointer => {
- json!(U32x2::from(data_ptr as *const u8 as u64))
+ NativeType::Pointer | NativeType::Function {} => {
+ let result = data_ptr as *const u8 as u64;
+ let big_int = v8::BigInt::new_from_u64(scope, result);
+ serde_v8::from_v8(scope, big_int.into())?
}
})
}
-#[op]
-fn op_ffi_call(
- state: &mut deno_core::OpState,
- args: FfiCallArgs,
-) -> Result<Value, AnyError> {
- let resource = state
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
-
- let symbol = resource
- .symbols
- .get(&args.symbol)
- .ok_or_else(bad_resource_id)?;
-
- ffi_call(args, symbol)
+#[op(v8)]
+fn op_ffi_call<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ rid: ResourceId,
+ symbol: String,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, AnyError> {
+ let symbol = {
+ let state = &mut state.borrow();
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
+
+ resource
+ .symbols
+ .get(&symbol)
+ .ok_or_else(|| type_error("Invalid FFI symbol name"))?
+ .clone()
+ };
+
+ let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?;
+
+ let result = ffi_call(
+ call_args,
+ &symbol.cif,
+ symbol.ptr,
+ &symbol.parameter_types,
+ symbol.result_type,
+ )?;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ let result = unsafe { result.to_v8(scope, symbol.result_type) };
+ Ok(result)
}
/// A non-blocking FFI call.
-#[op]
-async fn op_ffi_call_nonblocking(
+#[op(v8)]
+fn op_ffi_call_nonblocking<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: Rc<RefCell<deno_core::OpState>>,
- args: FfiCallArgs,
-) -> Result<Value, AnyError> {
- let resource = state
- .borrow()
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
- let symbols = &resource.symbols;
- let symbol = symbols
- .get(&args.symbol)
- .ok_or_else(bad_resource_id)?
- .clone();
-
- tokio::task::spawn_blocking(move || ffi_call(args, &symbol))
- .await
- .unwrap()
+ rid: ResourceId,
+ symbol: String,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<impl Future<Output = Result<Value, AnyError>> + 'static, AnyError> {
+ let symbol = {
+ let state = state.borrow();
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
+ let symbols = &resource.symbols;
+ symbols
+ .get(&symbol)
+ .ok_or_else(|| type_error("Invalid FFI symbol name"))?
+ .clone()
+ };
+
+ let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?;
+
+ let result_type = symbol.result_type;
+ let join_handle = tokio::task::spawn_blocking(move || {
+ let Symbol {
+ cif,
+ ptr,
+ parameter_types,
+ ..
+ } = symbol.clone();
+ ffi_call(call_args, &cif, ptr, &parameter_types, result_type)
+ });
+
+ Ok(async move {
+ let result = join_handle
+ .await
+ .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ Ok(unsafe { result.to_value(result_type) })
+ })
}
-#[op]
-fn op_ffi_ptr_of<FP>(
+#[op(v8)]
+fn op_ffi_ptr_of<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
buf: ZeroCopyBuf,
-) -> Result<U32x2, AnyError>
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#of");
-
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(U32x2::from(buf.as_ptr() as u64))
+ let big_int: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, buf.as_ptr() as u64).into();
+ Ok(big_int.into())
}
#[op]
fn op_ffi_buf_copy_into<FP>(
state: &mut deno_core::OpState,
- (src, mut dst, len): (U32x2, ZeroCopyBuf, usize),
+ src: u64,
+ mut dst: ZeroCopyBuf,
+ len: usize,
) -> Result<(), AnyError>
where
FP: FfiPermissions + 'static,
@@ -865,7 +1365,7 @@ where
"Destination length is smaller than source length",
))
} else {
- let src = u64::from(src) as *const u8;
+ let src = src as *const u8;
unsafe { ptr::copy(src, dst.as_mut_ptr(), len) };
Ok(())
}
@@ -874,7 +1374,7 @@ where
#[op]
fn op_ffi_cstr_read<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<String, AnyError>
where
FP: FfiPermissions + 'static,
@@ -884,14 +1384,14 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- let ptr = u64::from(ptr) as *const c_char;
+ let ptr = ptr as *const c_char;
Ok(unsafe { CStr::from_ptr(ptr) }.to_str()?.to_string())
}
#[op]
fn op_ffi_read_u8<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<u8, AnyError>
where
FP: FfiPermissions + 'static,
@@ -901,13 +1401,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u8) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const u8) })
}
#[op]
fn op_ffi_read_i8<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<i8, AnyError>
where
FP: FfiPermissions + 'static,
@@ -917,13 +1417,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i8) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const i8) })
}
#[op]
fn op_ffi_read_u16<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<u16, AnyError>
where
FP: FfiPermissions + 'static,
@@ -933,13 +1433,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u16) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const u16) })
}
#[op]
fn op_ffi_read_i16<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<i16, AnyError>
where
FP: FfiPermissions + 'static,
@@ -949,13 +1449,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i16) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const i16) })
}
#[op]
fn op_ffi_read_u32<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<u32, AnyError>
where
FP: FfiPermissions + 'static,
@@ -965,13 +1465,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u32) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const u32) })
}
#[op]
fn op_ffi_read_i32<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<i32, AnyError>
where
FP: FfiPermissions + 'static,
@@ -981,31 +1481,35 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i32) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const i32) })
}
-#[op]
-fn op_ffi_read_u64<FP>(
+#[op(v8)]
+fn op_ffi_read_u64<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
- ptr: U32x2,
-) -> Result<U32x2, AnyError>
+ ptr: u64,
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
+ 'scope: 'scope,
{
check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(U32x2::from(unsafe {
- ptr::read_unaligned(u64::from(ptr) as *const u64)
- }))
+ let result = unsafe { ptr::read_unaligned(ptr as *const u64) };
+
+ let big_int: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, result).into();
+ Ok(big_int.into())
}
#[op]
fn op_ffi_read_f32<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<f32, AnyError>
where
FP: FfiPermissions + 'static,
@@ -1015,13 +1519,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f32) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const f32) })
}
#[op]
fn op_ffi_read_f64<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<f64, AnyError>
where
FP: FfiPermissions + 'static,
@@ -1031,7 +1535,7 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f64) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const f64) })
}
#[cfg(test)]
diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js
index ea5303149..f21b261db 100644
--- a/runtime/js/90_deno_ns.js
+++ b/runtime/js/90_deno_ns.js
@@ -139,6 +139,7 @@
createHttpClient: __bootstrap.fetch.createHttpClient,
http: __bootstrap.http,
dlopen: __bootstrap.ffi.dlopen,
+ UnsafeCallback: __bootstrap.ffi.UnsafeCallback,
UnsafePointer: __bootstrap.ffi.UnsafePointer,
UnsafePointerView: __bootstrap.ffi.UnsafePointerView,
UnsafeFnPointer: __bootstrap.ffi.UnsafeFnPointer,
diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs
index 9a06e29e7..5b813cd01 100644
--- a/test_ffi/src/lib.rs
+++ b/test_ffi/src/lib.rs
@@ -114,8 +114,233 @@ pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void {
}
#[no_mangle]
+pub extern "C" fn call_fn_ptr(func: Option<extern "C" fn()>) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ func();
+}
+
+#[no_mangle]
+pub extern "C" fn call_fn_ptr_many_parameters(
+ func: Option<
+ extern "C" fn(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, *const u8),
+ >,
+) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ func(1, -1, 2, -2, 3, -3, 4, -4, 0.5, -0.5, BUFFER.as_ptr());
+}
+
+#[no_mangle]
+pub extern "C" fn call_fn_ptr_return_u8(func: Option<extern "C" fn() -> u8>) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ println!("u8: {}", func());
+}
+
+#[allow(clippy::not_unsafe_ptr_arg_deref)]
+#[no_mangle]
+pub extern "C" fn call_fn_ptr_return_buffer(
+ func: Option<extern "C" fn() -> *const u8>,
+) {
+ if func.is_none() {
+ return;
+ }
+ let func = func.unwrap();
+ let ptr = func();
+ let buf = unsafe { std::slice::from_raw_parts(ptr, 8) };
+ println!("buf: {:?}", buf);
+}
+
+static mut STORED_FUNCTION: Option<extern "C" fn()> = None;
+static mut STORED_FUNCTION_2: Option<extern "C" fn(u8) -> u8> = None;
+
+#[no_mangle]
+pub extern "C" fn store_function(func: Option<extern "C" fn()>) {
+ unsafe { STORED_FUNCTION = func };
+ if func.is_none() {
+ println!("STORED_FUNCTION cleared");
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn store_function_2(func: Option<extern "C" fn(u8) -> u8>) {
+ unsafe { STORED_FUNCTION_2 = func };
+ if func.is_none() {
+ println!("STORED_FUNCTION_2 cleared");
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function() {
+ unsafe {
+ if STORED_FUNCTION.is_none() {
+ return;
+ }
+ STORED_FUNCTION.unwrap()();
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn call_stored_function_2(arg: u8) {
+ unsafe {
+ if STORED_FUNCTION_2.is_none() {
+ return;
+ }
+ println!("{}", STORED_FUNCTION_2.unwrap()(arg));
+ }
+}
+
+// FFI performance helper functions
+#[no_mangle]
+pub extern "C" fn nop() {}
+
+#[no_mangle]
+pub extern "C" fn nop_u8(_a: u8) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i8(_a: i8) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u16(_a: u16) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i16(_a: i16) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u32(_a: u32) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i32(_a: i32) {}
+
+#[no_mangle]
+pub extern "C" fn nop_u64(_a: u64) {}
+
+#[no_mangle]
+pub extern "C" fn nop_i64(_a: i64) {}
+
+#[no_mangle]
+pub extern "C" fn nop_usize(_a: usize) {}
+
+#[no_mangle]
+pub extern "C" fn nop_isize(_a: isize) {}
+
+#[no_mangle]
+pub extern "C" fn nop_f32(_a: f32) {}
+
+#[no_mangle]
+pub extern "C" fn nop_f64(_a: f64) {}
+
+#[no_mangle]
+pub extern "C" fn nop_buffer(_buffer: *mut [u8; 8]) {}
+
+#[no_mangle]
+pub extern "C" fn return_u8() -> u8 {
+ 255
+}
+
+#[no_mangle]
+pub extern "C" fn return_i8() -> i8 {
+ -128
+}
+
+#[no_mangle]
+pub extern "C" fn return_u16() -> u16 {
+ 65535
+}
+
+#[no_mangle]
+pub extern "C" fn return_i16() -> i16 {
+ -32768
+}
+
+#[no_mangle]
+pub extern "C" fn return_u32() -> u32 {
+ 4294967295
+}
+
+#[no_mangle]
+pub extern "C" fn return_i32() -> i32 {
+ -2147483648
+}
+
+#[no_mangle]
+pub extern "C" fn return_u64() -> u64 {
+ 18446744073709551615
+}
+
+#[no_mangle]
+pub extern "C" fn return_i64() -> i64 {
+ -9223372036854775808
+}
+
+#[no_mangle]
+pub extern "C" fn return_usize() -> usize {
+ 18446744073709551615
+}
+
+#[no_mangle]
+pub extern "C" fn return_isize() -> isize {
+ -9223372036854775808
+}
+
+#[no_mangle]
+pub extern "C" fn return_f32() -> f32 {
+ #[allow(clippy::excessive_precision)]
+ 0.20298023223876953125
+}
+
+#[no_mangle]
+pub extern "C" fn return_f64() -> f64 {
+ 1e-10
+}
+
+// Parameters iteration
+
+#[no_mangle]
+pub extern "C" fn nop_many_parameters(
+ _: u8,
+ _: i8,
+ _: u16,
+ _: i16,
+ _: u32,
+ _: i32,
+ _: u64,
+ _: i64,
+ _: usize,
+ _: isize,
+ _: f32,
+ _: f64,
+ _: *mut [u8; 8],
+ _: u8,
+ _: i8,
+ _: u16,
+ _: i16,
+ _: u32,
+ _: i32,
+ _: u64,
+ _: i64,
+ _: usize,
+ _: isize,
+ _: f32,
+ _: f64,
+ _: *mut [u8; 8],
+) {
+}
+
+// Statics
+#[no_mangle]
pub static static_u32: u32 = 42;
+#[no_mangle]
+pub static static_i64: i64 = -1242464576485;
+
#[repr(C)]
pub struct Structure {
_data: u32,
diff --git a/test_ffi/tests/bench.js b/test_ffi/tests/bench.js
new file mode 100644
index 000000000..398732cc8
--- /dev/null
+++ b/test_ffi/tests/bench.js
@@ -0,0 +1,503 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore-file
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(libPath, {
+ "nop": { parameters: [], result: "void" },
+ "nop_u8": { parameters: ["u8"], result: "void" },
+ "nop_i8": { parameters: ["i8"], result: "void" },
+ "nop_u16": { parameters: ["u16"], result: "void" },
+ "nop_i16": { parameters: ["i16"], result: "void" },
+ "nop_u32": { parameters: ["u32"], result: "void" },
+ "nop_i32": { parameters: ["i32"], result: "void" },
+ "nop_u64": { parameters: ["u64"], result: "void" },
+ "nop_i64": { parameters: ["i64"], result: "void" },
+ "nop_usize": { parameters: ["usize"], result: "void" },
+ "nop_isize": { parameters: ["isize"], result: "void" },
+ "nop_f32": { parameters: ["f32"], result: "void" },
+ "nop_f64": { parameters: ["f64"], result: "void" },
+ "nop_buffer": { parameters: ["pointer"], result: "void" },
+ "return_u8": { parameters: [], result: "u8" },
+ "return_i8": { parameters: [], result: "i8" },
+ "return_u16": { parameters: [], result: "u16" },
+ "return_i16": { parameters: [], result: "i16" },
+ "return_u32": { parameters: [], result: "u32" },
+ "return_i32": { parameters: [], result: "i32" },
+ "return_u64": { parameters: [], result: "u64" },
+ "return_i64": { parameters: [], result: "i64" },
+ "return_usize": { parameters: [], result: "usize" },
+ "return_isize": { parameters: [], result: "isize" },
+ "return_f32": { parameters: [], result: "f32" },
+ "return_f64": { parameters: [], result: "f64" },
+ "return_buffer": { parameters: [], result: "pointer" },
+ // Nonblocking calls
+ "nop_nonblocking": { name: "nop", parameters: [], result: "void" },
+ "nop_u8_nonblocking": { name: "nop_u8", parameters: ["u8"], result: "void" },
+ "nop_i8_nonblocking": { name: "nop_i8", parameters: ["i8"], result: "void" },
+ "nop_u16_nonblocking": {
+ name: "nop_u16",
+ parameters: ["u16"],
+ result: "void",
+ },
+ "nop_i16_nonblocking": {
+ name: "nop_i16",
+ parameters: ["i16"],
+ result: "void",
+ },
+ "nop_u32_nonblocking": {
+ name: "nop_u32",
+ parameters: ["u32"],
+ result: "void",
+ },
+ "nop_i32_nonblocking": {
+ name: "nop_i32",
+ parameters: ["i32"],
+ result: "void",
+ },
+ "nop_u64_nonblocking": {
+ name: "nop_u64",
+ parameters: ["u64"],
+ result: "void",
+ },
+ "nop_i64_nonblocking": {
+ name: "nop_i64",
+ parameters: ["i64"],
+ result: "void",
+ },
+ "nop_usize_nonblocking": {
+ name: "nop_usize",
+ parameters: ["usize"],
+ result: "void",
+ },
+ "nop_isize_nonblocking": {
+ name: "nop_isize",
+ parameters: ["isize"],
+ result: "void",
+ },
+ "nop_f32_nonblocking": {
+ name: "nop_f32",
+ parameters: ["f32"],
+ result: "void",
+ },
+ "nop_f64_nonblocking": {
+ name: "nop_f64",
+ parameters: ["f64"],
+ result: "void",
+ },
+ "nop_buffer_nonblocking": {
+ name: "nop_buffer",
+ parameters: ["pointer"],
+ result: "void",
+ },
+ "return_u8_nonblocking": { name: "return_u8", parameters: [], result: "u8" },
+ "return_i8_nonblocking": { name: "return_i8", parameters: [], result: "i8" },
+ "return_u16_nonblocking": {
+ name: "return_u16",
+ parameters: [],
+ result: "u16",
+ },
+ "return_i16_nonblocking": {
+ name: "return_i16",
+ parameters: [],
+ result: "i16",
+ },
+ "return_u32_nonblocking": {
+ name: "return_u32",
+ parameters: [],
+ result: "u32",
+ },
+ "return_i32_nonblocking": {
+ name: "return_i32",
+ parameters: [],
+ result: "i32",
+ },
+ "return_u64_nonblocking": {
+ name: "return_u64",
+ parameters: [],
+ result: "u64",
+ },
+ "return_i64_nonblocking": {
+ name: "return_i64",
+ parameters: [],
+ result: "i64",
+ },
+ "return_usize_nonblocking": {
+ name: "return_usize",
+ parameters: [],
+ result: "usize",
+ },
+ "return_isize_nonblocking": {
+ name: "return_isize",
+ parameters: [],
+ result: "isize",
+ },
+ "return_f32_nonblocking": {
+ name: "return_f32",
+ parameters: [],
+ result: "f32",
+ },
+ "return_f64_nonblocking": {
+ name: "return_f64",
+ parameters: [],
+ result: "f64",
+ },
+ "return_buffer_nonblocking": {
+ name: "return_buffer",
+ parameters: [],
+ result: "pointer",
+ },
+ // Parameter checking
+ "nop_many_parameters": {
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "pointer",
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "pointer",
+ ],
+ result: "void",
+ },
+ "nop_many_parameters_nonblocking": {
+ name: "nop_many_parameters",
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "pointer",
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "usize",
+ "isize",
+ "f32",
+ "f64",
+ "pointer",
+ ],
+ result: "void",
+ nonblocking: true,
+ },
+});
+
+Deno.bench("nop()", () => {
+ dylib.symbols.nop();
+});
+
+Deno.bench("nop_u8()", () => {
+ dylib.symbols.nop_u8(100);
+});
+
+Deno.bench("nop_i8()", () => {
+ dylib.symbols.nop_i8(100);
+});
+
+Deno.bench("nop_u16()", () => {
+ dylib.symbols.nop_u16(100);
+});
+
+Deno.bench("nop_i16()", () => {
+ dylib.symbols.nop_i16(100);
+});
+
+Deno.bench("nop_u32()", () => {
+ dylib.symbols.nop_u32(100);
+});
+
+Deno.bench("nop_i32()", () => {
+ dylib.symbols.nop_i32(100);
+});
+
+Deno.bench("nop_u64()", () => {
+ dylib.symbols.nop_u64(100);
+});
+
+Deno.bench("nop_i64()", () => {
+ dylib.symbols.nop_i64(100);
+});
+
+Deno.bench("nop_usize()", () => {
+ dylib.symbols.nop_usize(100);
+});
+
+Deno.bench("nop_isize()", () => {
+ dylib.symbols.nop_isize(100);
+});
+
+Deno.bench("nop_f32()", () => {
+ dylib.symbols.nop_f32(100);
+});
+
+Deno.bench("nop_f64()", () => {
+ dylib.symbols.nop_f64(100);
+});
+
+const buffer = new Uint8Array(8).fill(5);
+Deno.bench("nop_buffer()", () => {
+ dylib.symbols.nop_buffer(buffer);
+});
+
+Deno.bench("return_u8()", () => {
+ dylib.symbols.return_u8();
+});
+
+Deno.bench("return_i8()", () => {
+ dylib.symbols.return_i8();
+});
+
+Deno.bench("return_u16()", () => {
+ dylib.symbols.return_u16();
+});
+
+Deno.bench("return_i16()", () => {
+ dylib.symbols.return_i16();
+});
+
+Deno.bench("return_u32()", () => {
+ dylib.symbols.return_u32();
+});
+
+Deno.bench("return_i32()", () => {
+ dylib.symbols.return_i32();
+});
+
+Deno.bench("return_u64()", () => {
+ dylib.symbols.return_u64();
+});
+
+Deno.bench("return_i64()", () => {
+ dylib.symbols.return_i64();
+});
+
+Deno.bench("return_usize()", () => {
+ dylib.symbols.return_usize();
+});
+
+Deno.bench("return_isize()", () => {
+ dylib.symbols.return_isize();
+});
+
+Deno.bench("return_f32()", () => {
+ dylib.symbols.return_f32();
+});
+
+Deno.bench("return_f64()", () => {
+ dylib.symbols.return_f64();
+});
+
+Deno.bench("return_buffer()", () => {
+ dylib.symbols.return_buffer();
+});
+
+// Nonblocking calls
+
+Deno.bench("nop_nonblocking()", async () => {
+ await dylib.symbols.nop_nonblocking();
+});
+
+Deno.bench("nop_u8_nonblocking()", async () => {
+ await dylib.symbols.nop_u8_nonblocking(100);
+});
+
+Deno.bench("nop_i8_nonblocking()", async () => {
+ await dylib.symbols.nop_i8_nonblocking(100);
+});
+
+Deno.bench("nop_u16_nonblocking()", async () => {
+ await dylib.symbols.nop_u16_nonblocking(100);
+});
+
+Deno.bench("nop_i16_nonblocking()", async () => {
+ await dylib.symbols.nop_i16_nonblocking(100);
+});
+
+Deno.bench("nop_u32_nonblocking()", async () => {
+ await dylib.symbols.nop_u32_nonblocking(100);
+});
+
+Deno.bench("nop_i32_nonblocking()", async () => {
+ await dylib.symbols.nop_i32_nonblocking(100);
+});
+
+Deno.bench("nop_u64_nonblocking()", async () => {
+ await dylib.symbols.nop_u64_nonblocking(100);
+});
+
+Deno.bench("nop_i64_nonblocking()", async () => {
+ await dylib.symbols.nop_i64_nonblocking(100);
+});
+
+Deno.bench("nop_usize_nonblocking()", async () => {
+ await dylib.symbols.nop_usize_nonblocking(100);
+});
+
+Deno.bench("nop_isize_nonblocking()", async () => {
+ await dylib.symbols.nop_isize_nonblocking(100);
+});
+
+Deno.bench("nop_f32_nonblocking()", async () => {
+ await dylib.symbols.nop_f32_nonblocking(100);
+});
+
+Deno.bench("nop_f64_nonblocking()", async () => {
+ await dylib.symbols.nop_f64_nonblocking(100);
+});
+
+Deno.bench("nop_buffer_nonblocking()", async () => {
+ await dylib.symbols.nop_buffer_nonblocking(buffer);
+});
+
+Deno.bench("return_u8_nonblocking()", async () => {
+ await dylib.symbols.return_u8_nonblocking();
+});
+
+Deno.bench("return_i8_nonblocking()", async () => {
+ await dylib.symbols.return_i8_nonblocking();
+});
+
+Deno.bench("return_u16_nonblocking()", async () => {
+ await dylib.symbols.return_u16_nonblocking();
+});
+
+Deno.bench("return_i16_nonblocking()", async () => {
+ await dylib.symbols.return_i16_nonblocking();
+});
+
+Deno.bench("return_u32_nonblocking()", async () => {
+ await dylib.symbols.return_u32_nonblocking();
+});
+
+Deno.bench("return_i32_nonblocking()", async () => {
+ await dylib.symbols.return_i32_nonblocking();
+});
+
+Deno.bench("return_u64_nonblocking()", async () => {
+ await dylib.symbols.return_u64_nonblocking();
+});
+
+Deno.bench("return_i64_nonblocking()", async () => {
+ await dylib.symbols.return_i64_nonblocking();
+});
+
+Deno.bench("return_usize_nonblocking()", async () => {
+ await dylib.symbols.return_usize_nonblocking();
+});
+
+Deno.bench("return_isize_nonblocking()", async () => {
+ await dylib.symbols.return_isize_nonblocking();
+});
+
+Deno.bench("return_f32_nonblocking()", async () => {
+ await dylib.symbols.return_f32_nonblocking();
+});
+
+Deno.bench("return_f64_nonblocking()", async () => {
+ await dylib.symbols.return_f64_nonblocking();
+});
+
+Deno.bench("return_buffer_nonblocking()", async () => {
+ await dylib.symbols.return_buffer_nonblocking();
+});
+
+const buffer2 = new Uint8Array(8).fill(25);
+Deno.bench("nop_many_parameters()", () => {
+ dylib.symbols.nop_many_parameters(
+ 135,
+ 47,
+ 356,
+ -236,
+ 7457,
+ -1356,
+ 16471468n,
+ -1334748136n,
+ 132658769535n,
+ -42745856824n,
+ 13567.26437,
+ 7.686234e-3,
+ buffer,
+ 64,
+ -42,
+ 83,
+ -136,
+ 3657,
+ -2376,
+ 3277918n,
+ -474628146n,
+ 344657895n,
+ -2436732n,
+ 135.26437e3,
+ 264.3576468623546834,
+ buffer2,
+ );
+});
+
+Deno.bench("nop_many_parameters_nonblocking()", () => {
+ dylib.symbols.nop_many_parameters_nonblocking(
+ 135,
+ 47,
+ 356,
+ -236,
+ 7457,
+ -1356,
+ 16471468n,
+ -1334748136n,
+ 132658769535n,
+ -42745856824n,
+ 13567.26437,
+ 7.686234e-3,
+ buffer,
+ 64,
+ -42,
+ 83,
+ -136,
+ 3657,
+ -2376,
+ 3277918n,
+ -474628146n,
+ 344657895n,
+ -2436732n,
+ 135.26437e3,
+ 264.3576468623546834,
+ buffer2,
+ );
+});
diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts
index 9ad51e67c..92ac13892 100644
--- a/test_ffi/tests/ffi_types.ts
+++ b/test_ffi/tests/ffi_types.ts
@@ -24,6 +24,27 @@ const remote = Deno.dlopen(
method17: { parameters: [], result: "usize", nonblocking: true },
method18: { parameters: [], result: "pointer" },
method19: { parameters: [], result: "pointer", nonblocking: true },
+ method20: {
+ parameters: [{
+ function: { parameters: ["u8", "u32", "pointer"], result: "void" },
+ }],
+ result: "void",
+ },
+ method21: {
+ parameters: [
+ { function: { parameters: [], result: "u8" } },
+ ],
+ result: "void",
+ },
+ method22: {
+ parameters: [{
+ function: {
+ parameters: [],
+ result: { function: { parameters: [], result: "u8" } },
+ },
+ }],
+ result: "void",
+ },
static1: { type: "usize" },
static2: { type: "pointer" },
static3: { type: "usize" },
@@ -41,6 +62,23 @@ const remote = Deno.dlopen(
} as const,
);
+Deno.dlopen(
+ "dummy_lib_2.so",
+ // @ts-expect-error: Returning a function pointer
+ // is declared using "pointer" + UnsafeFnPointer
+ {
+ wrong_method1: {
+ parameters: [],
+ result: {
+ function: {
+ parameters: [],
+ result: "void",
+ },
+ },
+ },
+ } as const,
+);
+
// @ts-expect-error: Invalid argument
remote.symbols.method1(0);
// @ts-expect-error: Invalid return type
@@ -136,6 +174,100 @@ const fnptr = new Deno.UnsafeFnPointer(
fnptr.call(null, null);
fnptr.call(0, null);
+const unsafe_callback_wrong1 = new Deno.UnsafeCallback(
+ {
+ parameters: ["i8"],
+ result: "void",
+ } as const,
+ // @ts-expect-error: i8 is not a pointer
+ (_: Deno.UnsafePointer) => {},
+);
+const unsafe_callback_wrong2 = new Deno.UnsafeCallback(
+ {
+ parameters: ["pointer"],
+ result: "u64",
+ } as const,
+ // @ts-expect-error: must return a number or bigint
+ (_: Deno.UnsafePointer) => {},
+);
+const unsafe_callback_wrong3 = new Deno.UnsafeCallback(
+ {
+ parameters: [],
+ result: "void",
+ } as const,
+ // @ts-expect-error: no parameters
+ (_: Deno.UnsafePointer) => {},
+);
+const unsafe_callback_wrong4 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u64"],
+ result: "void",
+ } as const,
+ // @ts-expect-error: Callback's 64bit parameters are always called as bigint
+ (_: number) => {},
+);
+const unsafe_callback_right1 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u8", "u32", "pointer"],
+ result: "void",
+ } as const,
+ (_1: number, _2: number, _3: Deno.UnsafePointer) => {},
+);
+const unsafe_callback_right2 = new Deno.UnsafeCallback(
+ {
+ parameters: [],
+ result: "u8",
+ } as const,
+ () => 3,
+);
+const unsafe_callback_right3 = new Deno.UnsafeCallback(
+ {
+ parameters: [],
+ result: {
+ function: {
+ parameters: [],
+ result: "u8",
+ },
+ },
+ } as const,
+ // Callbacks can return other callbacks, if really wanted.
+ () => unsafe_callback_right2,
+);
+const unsafe_callback_right4 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u8", "u32", "pointer"],
+ result: "u8",
+ } as const,
+ (_1: number, _2: number, _3: Deno.UnsafePointer) => 3,
+);
+const unsafe_callback_right5 = new Deno.UnsafeCallback(
+ {
+ parameters: ["u8", "i32", "pointer"],
+ result: "void",
+ } as const,
+ (_1: number, _2: number, _3: Deno.UnsafePointer) => {},
+);
+
+// @ts-expect-error: Must pass callback
+remote.symbols.method20();
+// nullptr is okay
+remote.symbols.method20(null);
+// Foreign function ptr received as UnsafePointer is okay
+remote.symbols.method20({} as Deno.UnsafePointer);
+// @ts-expect-error: Callback does not match the parameter
+remote.symbols.method20(unsafe_callback_right2);
+remote.symbols.method20(unsafe_callback_right1);
+// @ts-expect-error: Callback must match return value as well
+remote.symbols.method20(unsafe_callback_right4);
+// @ts-expect-error: Subtle differences in parameter types are not allowed (i32 vs u32)
+remote.symbols.method20(unsafe_callback_right5);
+remote.symbols.method21(unsafe_callback_right2);
+remote.symbols.method22(unsafe_callback_right3);
+// @ts-expect-error: Callback returns a callback with the wrong return value
+remote.symbols.method21(unsafe_callback_right3);
+// @ts-expect-error: Callback returns a callback with the wrong return value
+remote.symbols.method22(unsafe_callback_right2);
+
// @ts-expect-error: Invalid member type
const static1_wrong: null = remote.symbols.static1;
const static1_right: bigint = remote.symbols.static1;
diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs
index 5b9bb2fc2..93f368787 100644
--- a/test_ffi/tests/integration_tests.rs
+++ b/test_ffi/tests/integration_tests.rs
@@ -65,11 +65,27 @@ fn basic() {
-8589934590n\n\
579.9119873046875\n\
579.912\n\
+ 579\n\
+ 8589934590n\n\
+ -8589934590n\n\
+ 8589934590n\n\
+ -8589934590n\n\
+ 579.9119873046875\n\
+ 579.912\n\
After sleep_blocking\n\
true\n\
Before\n\
true\n\
+ logCallback\n\
+ 1 -1 2 -2 3 -3 4n -4n 0.5 -0.5 1 2 3 4 5 6 7 8\n\
+ u8: 8\n\
+ buf: [1, 2, 3, 4, 5, 6, 7, 8]\n\
+ logCallback\n\
+ 30\n\
+ STORED_FUNCTION cleared\n\
+ STORED_FUNCTION_2 cleared\n\
Static u32: 42\n\
+ Static i64: -1242464576485n\n\
Static ptr: true\n\
Static ptr value: 42\n\
After\n\
diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js
index b89dca648..1568abcbd 100644
--- a/test_ffi/tests/test.js
+++ b/test_ffi/tests/test.js
@@ -52,6 +52,54 @@ const dylib = Deno.dlopen(libPath, {
"add_isize": { parameters: ["isize", "isize"], result: "isize" },
"add_f32": { parameters: ["f32", "f32"], result: "f32" },
"add_f64": { parameters: ["f64", "f64"], result: "f64" },
+ "add_u32_nonblocking": {
+ name: "add_u32",
+ parameters: ["u32", "u32"],
+ result: "u32",
+ nonblocking: true,
+ },
+ "add_i32_nonblocking": {
+ name: "add_i32",
+ parameters: ["i32", "i32"],
+ result: "i32",
+ nonblocking: true,
+ },
+ "add_u64_nonblocking": {
+ name: "add_u64",
+ parameters: ["u64", "u64"],
+ result: "u64",
+ nonblocking: true,
+ },
+ "add_i64_nonblocking": {
+ name: "add_i64",
+ parameters: ["i64", "i64"],
+ result: "i64",
+ nonblocking: true,
+ },
+ "add_usize_nonblocking": {
+ name: "add_usize",
+ parameters: ["usize", "usize"],
+ result: "usize",
+ nonblocking: true,
+ },
+ "add_isize_nonblocking": {
+ name: "add_isize",
+ parameters: ["isize", "isize"],
+ result: "isize",
+ nonblocking: true,
+ },
+ "add_f32_nonblocking": {
+ name: "add_f32",
+ parameters: ["f32", "f32"],
+ result: "f32",
+ nonblocking: true,
+ },
+ "add_f64_nonblocking": {
+ name: "add_f64",
+ parameters: ["f64", "f64"],
+ result: "f64",
+ nonblocking: true,
+ },
"fill_buffer": { parameters: ["u8", "pointer", "usize"], result: "void" },
"sleep_nonblocking": {
name: "sleep_blocking",
@@ -73,9 +121,63 @@ const dylib = Deno.dlopen(libPath, {
parameters: [],
result: "pointer",
},
+ // Callback function
+ call_fn_ptr: {
+ parameters: [{ function: { parameters: [], result: "void" } }],
+ result: "void",
+ },
+ call_fn_ptr_many_parameters: {
+ parameters: [{
+ function: {
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "f32",
+ "f64",
+ "pointer",
+ ],
+ result: "void",
+ },
+ }],
+ result: "void",
+ },
+ call_fn_ptr_return_u8: {
+ parameters: [{ function: { parameters: [], result: "u8" } }],
+ result: "void",
+ },
+ call_fn_ptr_return_buffer: {
+ parameters: [{ function: { parameters: [], result: "pointer" } }],
+ result: "void",
+ },
+ store_function: {
+ parameters: [{ function: { parameters: [], result: "void" } }],
+ result: "void",
+ },
+ store_function_2: {
+ parameters: [{ function: { parameters: ["u8"], result: "u8" } }],
+ result: "void",
+ },
+ call_stored_function: {
+ parameters: [],
+ result: "void",
+ },
+ call_stored_function_2: {
+ parameters: ["u8"],
+ result: "void",
+ },
+ // Statics
"static_u32": {
type: "u32",
},
+ "static_i64": {
+ type: "i64",
+ },
"static_ptr": {
type: "pointer",
},
@@ -135,14 +237,14 @@ assertThrows(
dylib.symbols.add_u32(-1, 100);
},
TypeError,
- "Expected FFI argument to be an unsigned integer, but got Number(-1)",
+ "Expected FFI argument to be an unsigned integer, but got '-1'",
);
assertThrows(
() => {
dylib.symbols.add_u32(null, 100);
},
TypeError,
- "Expected FFI argument to be an unsigned integer, but got Null",
+ "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(0xffffffffn, 0xffffffffn));
@@ -152,6 +254,21 @@ 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));
+// Test adders as nonblocking calls
+console.log(await dylib.symbols.add_i32_nonblocking(123, 456));
+console.log(await dylib.symbols.add_u64_nonblocking(0xffffffffn, 0xffffffffn));
+console.log(
+ await dylib.symbols.add_i64_nonblocking(-0xffffffffn, -0xffffffffn),
+);
+console.log(
+ await dylib.symbols.add_usize_nonblocking(0xffffffffn, 0xffffffffn),
+);
+console.log(
+ await dylib.symbols.add_isize_nonblocking(-0xffffffffn, -0xffffffffn),
+);
+console.log(await dylib.symbols.add_f32_nonblocking(123.123, 456.789));
+console.log(await dylib.symbols.add_f64_nonblocking(123.123, 456.789));
+
// test mutating sync calls
function test_fill_buffer(fillValue, arr) {
@@ -207,7 +324,84 @@ dylib.symbols.sleep_nonblocking(100).then(() => {
console.log("Before");
console.log(performance.now() - start < 100);
+// Test calls with callback parameters
+const logCallback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => console.log("logCallback"),
+);
+const logManyParametersCallback = new Deno.UnsafeCallback({
+ parameters: [
+ "u8",
+ "i8",
+ "u16",
+ "i16",
+ "u32",
+ "i32",
+ "u64",
+ "i64",
+ "f32",
+ "f64",
+ "pointer",
+ ],
+ result: "void",
+}, (u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, pointer) => {
+ const view = new Deno.UnsafePointerView(new Deno.UnsafePointer(pointer));
+ const copy_buffer = new Uint8Array(8);
+ view.copyInto(copy_buffer);
+ console.log(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, ...copy_buffer);
+});
+const returnU8Callback = new Deno.UnsafeCallback(
+ { parameters: [], result: "u8" },
+ () => 8,
+);
+const returnBufferCallback = new Deno.UnsafeCallback({
+ parameters: [],
+ result: "pointer",
+}, () => {
+ return buffer;
+});
+const add10Callback = new Deno.UnsafeCallback({
+ parameters: ["u8"],
+ result: "u8",
+}, (value) => value + 10);
+const throwCallback = new Deno.UnsafeCallback({
+ parameters: [],
+ result: "void",
+}, () => {
+ throw new TypeError("hi");
+});
+
+assertThrows(
+ () => {
+ dylib.symbols.call_fn_ptr(throwCallback);
+ },
+ TypeError,
+ "hi",
+);
+
+dylib.symbols.call_fn_ptr(logCallback);
+dylib.symbols.call_fn_ptr_many_parameters(logManyParametersCallback);
+dylib.symbols.call_fn_ptr_return_u8(returnU8Callback);
+dylib.symbols.call_fn_ptr_return_buffer(returnBufferCallback);
+dylib.symbols.store_function(logCallback);
+dylib.symbols.call_stored_function();
+dylib.symbols.store_function_2(add10Callback);
+dylib.symbols.call_stored_function_2(20);
+
+const nestedCallback = new Deno.UnsafeCallback(
+ { parameters: [], result: "void" },
+ () => {
+ dylib.symbols.call_stored_function_2(10);
+ },
+);
+dylib.symbols.store_function(nestedCallback);
+
+dylib.symbols.store_function(null);
+dylib.symbols.store_function_2(null);
+
+// Test statics
console.log("Static u32:", dylib.symbols.static_u32);
+console.log("Static i64:", dylib.symbols.static_i64);
console.log(
"Static ptr:",
dylib.symbols.static_ptr instanceof Deno.UnsafePointer,
@@ -217,6 +411,13 @@ console.log("Static ptr value:", view.getUint32());
function cleanup() {
dylib.close();
+ throwCallback.close();
+ logCallback.close();
+ logManyParametersCallback.close();
+ returnU8Callback.close();
+ returnBufferCallback.close();
+ add10Callback.close();
+ nestedCallback.close();
const resourcesPost = Deno.resources();