From 62291e9b0e99406ac7f5a95dc329400f9f966825 Mon Sep 17 00:00:00 2001 From: DjDeveloper <43033058+DjDeveloperr@users.noreply.github.com> Date: Wed, 12 Jan 2022 17:08:26 +0530 Subject: [PATCH] feat(ext/ffi): UnsafeFnPointer API (#13340) --- cli/dts/lib.deno.unstable.d.ts | 20 +++++ ext/ffi/00_ffi.js | 115 ++++++++++++++++++++-------- ext/ffi/lib.rs | 70 ++++++++++++++++- runtime/js/90_deno_ns.js | 1 + test_ffi/src/lib.rs | 11 +++ test_ffi/tests/ffi_types.ts | 12 +++ test_ffi/tests/integration_tests.rs | 2 + test_ffi/tests/test.js | 26 +++++++ 8 files changed, 226 insertions(+), 31 deletions(-) diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index b9e9b4d2e1..82cb2cc8f7 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -250,6 +250,26 @@ declare namespace Deno { copyInto(destination: TypedArray, offset?: number): void; } + /** + * **UNSTABLE**: Unsafe and new API, beware! + * + * An unsafe pointer to a function, for calling functions that are not + * present as symbols. + */ + export class UnsafeFnPointer { + pointer: UnsafePointer; + definition: Fn; + + constructor(pointer: UnsafePointer, definition: Fn); + + call( + ...args: StaticForeignFunctionParameters + ): ConditionalAsync< + Fn["nonblocking"], + StaticForeignFunctionResult + >; + } + /** A dynamic library resource */ export interface DynamicLibrary { /** All of the registered symbols along with functions for calling them */ diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index b0bdbe8cfc..abe806cc08 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -142,6 +142,84 @@ } } + function prepareArgs(types, args) { + const parameters = []; + const buffers = []; + + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const arg = args[i]; + + if (type === "pointer") { + if ( + arg?.buffer instanceof ArrayBuffer && + arg.byteLength !== undefined + ) { + parameters.push(buffers.length); + buffers.push(arg); + } else if (arg instanceof UnsafePointer) { + parameters.push(packU64(arg.value)); + buffers.push(undefined); + } else if (arg === null) { + parameters.push(null); + buffers.push(undefined); + } else { + throw new TypeError( + "Invalid ffi arg value, expected TypedArray, UnsafePointer or null", + ); + } + } else { + parameters.push(arg); + } + } + + return { parameters, buffers }; + } + + class UnsafeFnPointer { + pointer; + definition; + + constructor(pointer, definition) { + this.pointer = pointer; + this.definition = definition; + } + + call(...args) { + const { parameters, buffers } = prepareArgs( + this.definition.parameters, + args, + ); + if (this.definition.nonblocking) { + const promise = core.opAsync("op_ffi_call_ptr_nonblocking", { + pointer: packU64(this.pointer.value), + def: this.definition, + parameters, + buffers, + }); + + if (this.definition.result === "pointer") { + return promise.then((value) => new UnsafePointer(unpackU64(value))); + } + + return promise; + } else { + const result = core.opSync("op_ffi_call_ptr", { + pointer: packU64(this.pointer.value), + def: this.definition, + parameters, + buffers, + }); + + if (this.definition.result === "pointer") { + return new UnsafePointer(unpackU64(result)); + } + + return result; + } + } + } + class DynamicLibrary { #rid; symbols = {}; @@ -154,35 +232,7 @@ const types = symbols[symbol].parameters; this.symbols[symbol] = (...args) => { - const parameters = []; - const buffers = []; - - for (let i = 0; i < types.length; i++) { - const type = types[i]; - const arg = args[i]; - - if (type === "pointer") { - if ( - arg?.buffer instanceof ArrayBuffer && - arg.byteLength !== undefined - ) { - parameters.push(buffers.length); - buffers.push(arg); - } else if (arg instanceof UnsafePointer) { - parameters.push(packU64(arg.value)); - buffers.push(undefined); - } else if (arg === null) { - parameters.push(null); - buffers.push(undefined); - } else { - throw new TypeError( - "Invalid ffi arg value, expected TypedArray, UnsafePointer or null", - ); - } - } else { - parameters.push(arg); - } - } + const { parameters, buffers } = prepareArgs(types, args); if (isNonBlocking) { const promise = core.opAsync("op_ffi_call_nonblocking", { @@ -228,5 +278,10 @@ return new DynamicLibrary(pathFromURL(path), symbols); } - window.__bootstrap.ffi = { dlopen, UnsafePointer, UnsafePointerView }; + window.__bootstrap.ffi = { + dlopen, + UnsafePointer, + UnsafePointerView, + UnsafeFnPointer, + }; })(this); diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 2a6799b5ff..8f06854d61 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -130,6 +130,11 @@ pub fn init(unstable: bool) -> Extension { ("op_ffi_load", op_sync(op_ffi_load::

)), ("op_ffi_call", op_sync(op_ffi_call)), ("op_ffi_call_nonblocking", op_async(op_ffi_call_nonblocking)), + ("op_ffi_call_ptr", op_sync(op_ffi_call_ptr)), + ( + "op_ffi_call_ptr_nonblocking", + op_async(op_ffi_call_ptr_nonblocking), + ), ("op_ffi_ptr_of", op_sync(op_ffi_ptr_of::

)), ("op_ffi_buf_copy_into", op_sync(op_ffi_buf_copy_into::

)), ("op_ffi_cstr_read", op_sync(op_ffi_cstr_read::

)), @@ -323,7 +328,7 @@ fn value_as_f64(value: Value) -> Result { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] struct U32x2(u32, u32); impl From for U32x2 { @@ -469,6 +474,49 @@ struct FfiCallArgs { buffers: Vec>, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FfiCallPtrArgs { + pointer: U32x2, + def: ForeignFunction, + parameters: Vec, + buffers: Vec>, +} + +impl From for FfiCallArgs { + fn from(args: FfiCallPtrArgs) -> Self { + FfiCallArgs { + rid: 0, + symbol: String::new(), + parameters: args.parameters, + buffers: args.buffers, + } + } +} + +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(), + ); + + Symbol { + cif, + ptr, + parameter_types: self.def.parameters.clone(), + result_type: self.def.result, + } + } +} + fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result { let buffers: Vec> = args .buffers @@ -563,6 +611,26 @@ fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result { }) } +fn op_ffi_call_ptr( + _state: &mut deno_core::OpState, + args: FfiCallPtrArgs, + _: (), +) -> Result { + let symbol = args.get_symbol(); + ffi_call(args.into(), &symbol) +} + +async fn op_ffi_call_ptr_nonblocking( + _state: Rc>, + args: FfiCallPtrArgs, + _: (), +) -> Result { + let symbol = args.get_symbol(); + tokio::task::spawn_blocking(move || ffi_call(args.into(), &symbol)) + .await + .unwrap() +} + fn op_ffi_call( state: &mut deno_core::OpState, args: FfiCallArgs, diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index f7f5184272..d93ea4c54b 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -138,6 +138,7 @@ dlopen: __bootstrap.ffi.dlopen, UnsafePointer: __bootstrap.ffi.UnsafePointer, UnsafePointerView: __bootstrap.ffi.UnsafePointerView, + UnsafeFnPointer: __bootstrap.ffi.UnsafeFnPointer, flock: __bootstrap.fs.flock, flockSync: __bootstrap.fs.flockSync, funlock: __bootstrap.fs.funlock, diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs index 93b274b4b0..a04c2c2fd5 100644 --- a/test_ffi/src/lib.rs +++ b/test_ffi/src/lib.rs @@ -1,5 +1,6 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use std::os::raw::c_void; use std::thread::sleep; use std::time::Duration; @@ -101,3 +102,13 @@ pub extern "C" fn nonblocking_buffer(ptr: *const u8, len: usize) { let buf = unsafe { std::slice::from_raw_parts(ptr, len) }; assert_eq!(buf, vec![1, 2, 3, 4, 5, 6, 7, 8]); } + +#[no_mangle] +pub extern "C" fn get_add_u32_ptr() -> *const c_void { + add_u32 as *const c_void +} + +#[no_mangle] +pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void { + sleep_blocking as *const c_void +} diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts index e10cfb8949..dca3615818 100644 --- a/test_ffi/tests/ffi_types.ts +++ b/test_ffi/tests/ffi_types.ts @@ -109,3 +109,15 @@ const result4 = remote.symbols.method19(); // @ts-expect-error: Invalid argument result4.then((_0: Deno.TypedArray) => {}); result4.then((_1: Deno.UnsafePointer) => {}); + +const ptr = new Deno.UnsafePointer(0n); +const fnptr = new Deno.UnsafeFnPointer( + ptr, + { + parameters: ["u32", "pointer"], + result: "void", + } as const, +); +// @ts-expect-error: Invalid argument +fnptr.call(null, null); +fnptr.call(0, null); diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index 91de4412e0..c818f12d9d 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -56,6 +56,8 @@ fn basic() { true\n\ false\n\ 579\n\ + true\n\ + 579\n\ 579\n\ 579\n\ 579\n\ diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index 7ebcf4460d..a9681ab9fb 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -65,6 +65,14 @@ const dylib = Deno.dlopen(libPath, { result: "void", nonblocking: true, }, + "get_add_u32_ptr": { + parameters: [], + result: "pointer", + }, + "get_sleep_blocking_ptr": { + parameters: [], + result: "pointer", + }, }); dylib.symbols.printSomething(); @@ -97,6 +105,24 @@ console.log(stringPtrview.getCString(11)); console.log(Boolean(dylib.symbols.is_null_ptr(ptr))); console.log(Boolean(dylib.symbols.is_null_ptr(null))); console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into)))); + +const addU32Ptr = dylib.symbols.get_add_u32_ptr(); +const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, { + parameters: ["u32", "u32"], + result: "u32", +}); +console.log(addU32.call(123, 456)); + +const sleepBlockingPtr = dylib.symbols.get_sleep_blocking_ptr(); +const sleepNonBlocking = new Deno.UnsafeFnPointer(sleepBlockingPtr, { + nonblocking: true, + parameters: ["u64"], + result: "void", +}); +const before = performance.now(); +await sleepNonBlocking.call(100); +console.log(performance.now() - before >= 100); + console.log(dylib.symbols.add_u32(123, 456)); assertThrows( () => {