summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/dts/lib.deno.unstable.d.ts20
-rw-r--r--ext/ffi/00_ffi.js115
-rw-r--r--ext/ffi/lib.rs70
-rw-r--r--runtime/js/90_deno_ns.js1
-rw-r--r--test_ffi/src/lib.rs11
-rw-r--r--test_ffi/tests/ffi_types.ts12
-rw-r--r--test_ffi/tests/integration_tests.rs2
-rw-r--r--test_ffi/tests/test.js26
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 b9e9b4d2e..82cb2cc8f 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<Fn extends ForeignFunction> {
+ pointer: UnsafePointer;
+ definition: Fn;
+
+ constructor(pointer: UnsafePointer, definition: Fn);
+
+ call(
+ ...args: StaticForeignFunctionParameters<Fn["parameters"]>
+ ): ConditionalAsync<
+ Fn["nonblocking"],
+ StaticForeignFunctionResult<Fn["result"]>
+ >;
+ }
+
/** A dynamic library resource */
export interface DynamicLibrary<S extends ForeignFunctionInterface> {
/** 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 b0bdbe8cf..abe806cc0 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 2a6799b5f..8f06854d6 100644
--- a/ext/ffi/lib.rs
+++ b/ext/ffi/lib.rs
@@ -130,6 +130,11 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
("op_ffi_load", op_sync(op_ffi_load::<P>)),
("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::<P>)),
("op_ffi_buf_copy_into", op_sync(op_ffi_buf_copy_into::<P>)),
("op_ffi_cstr_read", op_sync(op_ffi_cstr_read::<P>)),
@@ -323,7 +328,7 @@ fn value_as_f64(value: Value) -> Result<f64, AnyError> {
}
}
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
struct U32x2(u32, u32);
impl From<u64> for U32x2 {
@@ -469,6 +474,49 @@ struct FfiCallArgs {
buffers: Vec<Option<ZeroCopyBuf>>,
}
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct FfiCallPtrArgs {
+ pointer: U32x2,
+ def: ForeignFunction,
+ parameters: Vec<Value>,
+ buffers: Vec<Option<ZeroCopyBuf>>,
+}
+
+impl From<FfiCallPtrArgs> 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<Value, AnyError> {
let buffers: Vec<Option<&[u8]>> = args
.buffers
@@ -563,6 +611,26 @@ fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
})
}
+fn op_ffi_call_ptr(
+ _state: &mut deno_core::OpState,
+ args: FfiCallPtrArgs,
+ _: (),
+) -> Result<Value, AnyError> {
+ let symbol = args.get_symbol();
+ ffi_call(args.into(), &symbol)
+}
+
+async fn op_ffi_call_ptr_nonblocking(
+ _state: Rc<RefCell<deno_core::OpState>>,
+ args: FfiCallPtrArgs,
+ _: (),
+) -> Result<Value, AnyError> {
+ 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 f7f518427..d93ea4c54 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 93b274b4b..a04c2c2fd 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 e10cfb894..dca361581 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 91de4412e..c818f12d9 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 7ebcf4460..a9681ab9f 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(
() => {