diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 7ebd804225b1c4..3482d1c80c1e85 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 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 | UnsafePointer | null : unknown; /** Infers a foreign function parameter list. */ - type StaticForeignFunctionParameters = [ + type StaticForeignFunctionParameters< + T extends readonly NativeParameterType[], + > = [ ...{ [K in keyof T]: StaticForeignFunctionParameter; }, @@ -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; + } + + type UnsafeCallbackParameters = T extends [] + ? [] + : T extends + readonly [infer U extends NativeType, ...(infer V extends NativeType[])] + ? [ + UnsafeCallbackParameter, + ...UnsafeCallbackParameters, + ] + : never; + + type UnsafeCallbackParameter = T extends + StaticNativeBigIntType ? bigint + : T extends StaticNativeNumberType ? number + : T extends "pointer" ? UnsafePointer + : never; + + type UnsafeCallbackResult = 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 | UnsafePointer | null + : never; + + type UnsafeCallbackFunction< + Parameters extends readonly NativeType[] = readonly NativeType[], + Result extends NativeParameterType = NativeParameterType, + > = Result extends NativeParameterType + ? Parameters extends readonly [] ? () => UnsafeCallbackResult + : Parameters extends readonly NativeType[] ? ( + ...args: UnsafeCallbackParameters + ) => UnsafeCallbackResult + : 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, + callback: UnsafeCallbackFunction, + ); + + pointer: UnsafePointer; + definition: UnsafeCallbackDefinition; + callback: UnsafeCallbackFunction; + + close(): void; + } + /** A dynamic library resource */ export interface DynamicLibrary { /** 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 c7cafd3ea4b820..e8fc9b3f0cebe4 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 37bd75cc970c05..77c86109a57b3f 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 b05f92d39376a2..65934a82fd8119 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 a83b8dc1881d3c..0ab43781b31251 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 b39b99da5f17f7..b65a50a2098ab6 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 afd49b722f8cad..de9f291688f0a5 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 de392fa7d57a6d..fe3d9d709e5ed8 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 4924d9d6746793..a8f7f41806c4fb 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 447ff584276048..dc494023d49568 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 cc791b8f09dfe4..c66681225e2173 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 02ef455ee4d292..a0c27a71c64b0a 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 d250c9f21994c3..7c51f8aa3d2bfa 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 f21a4cdbf2117a..7798e4d2c44943 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 fb4fcbfb83171f..d29e83fce2ac41 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 8a1a75b1d9a80a..691d44460bdad7 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 = 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, @@ -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(unstable: bool) -> Extension { op_ffi_read_u64::decl::

(), op_ffi_read_f32::decl::

(), op_ffi_read_f64::decl::

(), + op_ffi_unsafe_callback_create::decl::

(), ]) .state(move |state| { // Stolen from deno_webgpu, is there a better option? @@ -172,6 +209,8 @@ pub fn init(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 for libffi::middle::Type { @@ -208,10 +248,13 @@ impl From 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 { - let value = match native_type { - NativeType::Void => Self { void_value: () }, - NativeType::U8 => Self { - u8_value: value_as_uint::(value)?, - }, - NativeType::I8 => Self { - i8_value: value_as_int::(value)?, - }, - NativeType::U16 => Self { - u16_value: value_as_uint::(value)?, - }, - NativeType::I16 => Self { - i16_value: value_as_int::(value)?, - }, - NativeType::U32 => Self { - u32_value: value_as_uint::(value)?, - }, - NativeType::I32 => Self { - i32_value: value_as_int::(value)?, - }, - NativeType::U64 => Self { - u64_value: value_as_uint::(value)?, - }, - NativeType::I64 => Self { - i64_value: value_as_int::(value)?, - }, - NativeType::USize => Self { - usize_value: value_as_uint::(value)?, - }, - NativeType::ISize => Self { - isize_value: value_as_int::(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::(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>(value: Value) -> Result { - 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>(value: Value) -> Result { - 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::undefined(scope).into(); + local_value.into() + } + NativeType::U8 => { + let local_value: v8::Local = + v8::Integer::new_from_unsigned(scope, self.u8_value as u32).into(); + local_value.into() + } + NativeType::I8 => { + let local_value: v8::Local = + v8::Integer::new(scope, self.i8_value as i32).into(); + local_value.into() + } + NativeType::U16 => { + let local_value: v8::Local = + v8::Integer::new_from_unsigned(scope, self.u16_value as u32).into(); + local_value.into() + } + NativeType::I16 => { + let local_value: v8::Local = + v8::Integer::new(scope, self.i16_value as i32).into(); + local_value.into() + } + NativeType::U32 => { + let local_value: v8::Local = + v8::Integer::new_from_unsigned(scope, self.u32_value).into(); + local_value.into() + } + NativeType::I32 => { + let local_value: v8::Local = + v8::Integer::new(scope, self.i32_value).into(); + local_value.into() + } + NativeType::U64 => { + let local_value: v8::Local = + v8::BigInt::new_from_u64(scope, self.u64_value).into(); + local_value.into() + } + NativeType::I64 => { + let local_value: v8::Local = + v8::BigInt::new_from_i64(scope, self.i64_value).into(); + local_value.into() + } + NativeType::USize => { + let local_value: v8::Local = + v8::BigInt::new_from_u64(scope, self.usize_value as u64).into(); + local_value.into() + } + NativeType::ISize => { + let local_value: v8::Local = + v8::BigInt::new_from_i64(scope, self.isize_value as i64).into(); + local_value.into() + } + NativeType::F32 => { + let local_value: v8::Local = + v8::Number::new(scope, self.f32_value as f64).into(); + local_value.into() + } + NativeType::F64 => { + let local_value: v8::Local = + v8::Number::new(scope, self.f64_value).into(); + local_value.into() + } + NativeType::Pointer | NativeType::Function {} => { + let local_value: v8::Local = + v8::BigInt::new_from_u64(scope, self.pointer as u64).into(); + local_value.into() + } + } } } -fn value_as_f32(value: Value) -> Result { - Ok(value_as_f64(value)? as f32) -} +unsafe impl Send for NativeValue {} -fn value_as_f64(value: Value) -> Result { - 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 for U32x2 { @@ -363,31 +414,6 @@ impl From for U32x2 { } } -impl From for u64 { - fn from(value: U32x2) -> Self { - (value.0 as u64) << 32 | value.1 as u64 - } -} - -impl TryFrom for U32x2 { - type Error = AnyError; - - fn try_from(value: Value) -> Result { - 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, - buffers: Vec>, -} +fn ffi_parse_args<'scope>( + scope: &mut v8::HandleScope<'scope>, + args: serde_v8::Value<'scope>, + parameter_types: &[NativeType], +) -> Result, 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, - buffers: Vec>, -} + let args = v8::Local::::try_from(args.v8_value) + .map_err(|_| type_error("Invalid FFI parameters, expected Array"))?; + let mut ffi_args: Vec = + Vec::with_capacity(parameter_types.len()); -impl From 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::::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::::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::::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::::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::::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::::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, + cif: &libffi::middle::Cif, + fun_ptr: libffi::middle::CodePtr, + parameter_types: &[NativeType], + result_type: NativeType, +) -> Result { + let call_args: Vec = 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::(fun_ptr, &call_args) }, + }, + NativeType::I8 => NativeValue { + i8_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::U16 => NativeValue { + u16_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::I16 => NativeValue { + i16_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::U32 => NativeValue { + u32_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::I32 => NativeValue { + i32_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::U64 => NativeValue { + u64_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::I64 => NativeValue { + i64_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::USize => NativeValue { + usize_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::ISize => NativeValue { + isize_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::F32 => NativeValue { + f32_value: unsafe { cif.call::(fun_ptr, &call_args) }, + }, + NativeType::F64 => NativeValue { + f64_value: unsafe { cif.call::(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 { + "unsafecallback".into() + } + + fn close(self: Rc) { + // 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 { - let buffers: Vec> = args - .buffers - .iter() - .map(|buffer| buffer.as_ref().map(|buffer| &buffer[..])) - .collect(); - - let mut native_values: Vec = vec![]; +struct CallbackInfo { + pub callback: NonNull, + pub context: NonNull, + 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::Local, + >(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> = vec![]; + for (repr, val) in repr.iter().zip(vals) { + let value: v8::Local = 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::>(); + let recv = v8::undefined(&mut scope); + let call_result = func.call(&mut scope, recv.into(), ¶ms); + 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::(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::(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::(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::(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::::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::(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::(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::(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::(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::(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::(symbol.ptr, &call_args) - } as u64)) + FFI_TYPE_SINT64 => { + if value.is_big_int() { + let value = v8::Local::::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::(symbol.ptr, &call_args) }) + FFI_TYPE_UINT64 => { + if value.is_big_int() { + let value = v8::Local::::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::(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( +#[derive(Deserialize)] +struct RegisterCallbackArgs { + parameters: Vec, + result: NativeType, +} + +#[op(v8)] +fn op_ffi_unsafe_callback_create( state: &mut deno_core::OpState, - args: FfiCallPtrArgs, -) -> Result + scope: &mut v8::HandleScope<'scope>, + args: RegisterCallbackArgs, + cb: serde_v8::Value<'scope>, +) -> Result, AnyError> where FP: FfiPermissions + 'static, { - check_unstable(state, "Deno.UnsafeFnPointer#call"); - + check_unstable(state, "Deno.UnsafeCallback"); let permissions = state.borrow_mut::(); permissions.check(None)?; - let symbol = args.get_symbol(); - ffi_call(args.into(), &symbol) + let v8_value = cb.v8_value; + let cb = v8::Local::::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 = array.into(); + + Ok(array_value.into()) } -#[op] -async fn op_ffi_call_ptr_nonblocking( +#[op(v8)] +fn op_ffi_call_ptr( + scope: &mut v8::HandleScope<'scope>, state: Rc>, - args: FfiCallPtrArgs, -) -> Result + pointer: u64, + def: ForeignFunction, + parameters: serde_v8::Value<'scope>, +) -> Result, AnyError> where FP: FfiPermissions + 'static, { check_unstable2(&state, "Deno.UnsafeFnPointer#call"); - { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); 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>, + pointer: u64, + def: ForeignFunction, + parameters: serde_v8::Value<'scope>, +) -> Result>, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable2(&state, "Deno.UnsafeFnPointer#call"); + { + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::(); + 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 { - let resource = state - .resource_table - .get::(args.rid)?; + rid: ResourceId, + name: String, + static_type: NativeType, +) -> Result, AnyError> { + let resource = state.resource_table.get::(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 { - let resource = state - .resource_table - .get::(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>, + rid: ResourceId, + symbol: String, + parameters: serde_v8::Value<'scope>, +) -> Result, AnyError> { + let symbol = { + let state = &mut state.borrow(); + let resource = state.resource_table.get::(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>, - args: FfiCallArgs, -) -> Result { - let resource = state - .borrow() - .resource_table - .get::(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> + 'static, AnyError> { + let symbol = { + let state = state.borrow(); + let resource = state.resource_table.get::(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, ¶meter_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( +#[op(v8)] +fn op_ffi_ptr_of( + scope: &mut v8::HandleScope<'scope>, state: &mut deno_core::OpState, buf: ZeroCopyBuf, -) -> Result +) -> Result, AnyError> where FP: FfiPermissions + 'static, { check_unstable(state, "Deno.UnsafePointer#of"); - let permissions = state.borrow_mut::(); permissions.check(None)?; - Ok(U32x2::from(buf.as_ptr() as u64)) + let big_int: v8::Local = + v8::BigInt::new_from_u64(scope, buf.as_ptr() as u64).into(); + Ok(big_int.into()) } #[op] fn op_ffi_buf_copy_into( 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -884,14 +1384,14 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -901,13 +1401,13 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -917,13 +1417,13 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -933,13 +1433,13 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -949,13 +1449,13 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -965,13 +1465,13 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -981,31 +1481,35 @@ where let permissions = state.borrow_mut::(); 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( +#[op(v8)] +fn op_ffi_read_u64( + scope: &mut v8::HandleScope<'scope>, state: &mut deno_core::OpState, - ptr: U32x2, -) -> Result + ptr: u64, +) -> Result, AnyError> where FP: FfiPermissions + 'static, + 'scope: 'scope, { check_unstable(state, "Deno.UnsafePointerView#getBigUint64"); let permissions = state.borrow_mut::(); 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::BigInt::new_from_u64(scope, result).into(); + Ok(big_int.into()) } #[op] fn op_ffi_read_f32( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -1015,13 +1519,13 @@ where let permissions = state.borrow_mut::(); 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( state: &mut deno_core::OpState, - ptr: U32x2, + ptr: u64, ) -> Result where FP: FfiPermissions + 'static, @@ -1031,7 +1535,7 @@ where let permissions = state.borrow_mut::(); 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 ea530314962a93..f21b261db89f76 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 9a06e29e74ed30..5b813cd012e85f 100644 --- a/test_ffi/src/lib.rs +++ b/test_ffi/src/lib.rs @@ -113,9 +113,234 @@ pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void { sleep_blocking as *const c_void } +#[no_mangle] +pub extern "C" fn call_fn_ptr(func: Option) { + 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 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 *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 = None; +static mut STORED_FUNCTION_2: Option u8> = None; + +#[no_mangle] +pub extern "C" fn store_function(func: Option) { + unsafe { STORED_FUNCTION = func }; + if func.is_none() { + println!("STORED_FUNCTION cleared"); + } +} + +#[no_mangle] +pub extern "C" fn store_function_2(func: Option 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 00000000000000..398732cc8de908 --- /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 9ad51e67c978a1..92ac1389265436 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 5b9bb2fc292c54..93f3687871ca6f 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 b89dca64811592..1568abcbd32bb6 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();