mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
feat(ext/ffi): UnsafeFnPointer API (#13340)
This commit is contained in:
parent
79b698f88b
commit
62291e9b0e
8 changed files with 226 additions and 31 deletions
20
cli/dts/lib.deno.unstable.d.ts
vendored
20
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -250,6 +250,26 @@ declare namespace Deno {
|
||||||
copyInto(destination: TypedArray, offset?: number): void;
|
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 */
|
/** A dynamic library resource */
|
||||||
export interface DynamicLibrary<S extends ForeignFunctionInterface> {
|
export interface DynamicLibrary<S extends ForeignFunctionInterface> {
|
||||||
/** All of the registered symbols along with functions for calling them */
|
/** All of the registered symbols along with functions for calling them */
|
||||||
|
|
|
@ -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 {
|
class DynamicLibrary {
|
||||||
#rid;
|
#rid;
|
||||||
symbols = {};
|
symbols = {};
|
||||||
|
@ -154,35 +232,7 @@
|
||||||
const types = symbols[symbol].parameters;
|
const types = symbols[symbol].parameters;
|
||||||
|
|
||||||
this.symbols[symbol] = (...args) => {
|
this.symbols[symbol] = (...args) => {
|
||||||
const parameters = [];
|
const { parameters, buffers } = prepareArgs(types, args);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNonBlocking) {
|
if (isNonBlocking) {
|
||||||
const promise = core.opAsync("op_ffi_call_nonblocking", {
|
const promise = core.opAsync("op_ffi_call_nonblocking", {
|
||||||
|
@ -228,5 +278,10 @@
|
||||||
return new DynamicLibrary(pathFromURL(path), symbols);
|
return new DynamicLibrary(pathFromURL(path), symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__bootstrap.ffi = { dlopen, UnsafePointer, UnsafePointerView };
|
window.__bootstrap.ffi = {
|
||||||
|
dlopen,
|
||||||
|
UnsafePointer,
|
||||||
|
UnsafePointerView,
|
||||||
|
UnsafeFnPointer,
|
||||||
|
};
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -130,6 +130,11 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
|
||||||
("op_ffi_load", op_sync(op_ffi_load::<P>)),
|
("op_ffi_load", op_sync(op_ffi_load::<P>)),
|
||||||
("op_ffi_call", op_sync(op_ffi_call)),
|
("op_ffi_call", op_sync(op_ffi_call)),
|
||||||
("op_ffi_call_nonblocking", op_async(op_ffi_call_nonblocking)),
|
("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_ptr_of", op_sync(op_ffi_ptr_of::<P>)),
|
||||||
("op_ffi_buf_copy_into", op_sync(op_ffi_buf_copy_into::<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>)),
|
("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);
|
struct U32x2(u32, u32);
|
||||||
|
|
||||||
impl From<u64> for U32x2 {
|
impl From<u64> for U32x2 {
|
||||||
|
@ -469,6 +474,49 @@ struct FfiCallArgs {
|
||||||
buffers: Vec<Option<ZeroCopyBuf>>,
|
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> {
|
fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
|
||||||
let buffers: Vec<Option<&[u8]>> = args
|
let buffers: Vec<Option<&[u8]>> = args
|
||||||
.buffers
|
.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(
|
fn op_ffi_call(
|
||||||
state: &mut deno_core::OpState,
|
state: &mut deno_core::OpState,
|
||||||
args: FfiCallArgs,
|
args: FfiCallArgs,
|
||||||
|
|
|
@ -138,6 +138,7 @@
|
||||||
dlopen: __bootstrap.ffi.dlopen,
|
dlopen: __bootstrap.ffi.dlopen,
|
||||||
UnsafePointer: __bootstrap.ffi.UnsafePointer,
|
UnsafePointer: __bootstrap.ffi.UnsafePointer,
|
||||||
UnsafePointerView: __bootstrap.ffi.UnsafePointerView,
|
UnsafePointerView: __bootstrap.ffi.UnsafePointerView,
|
||||||
|
UnsafeFnPointer: __bootstrap.ffi.UnsafeFnPointer,
|
||||||
flock: __bootstrap.fs.flock,
|
flock: __bootstrap.fs.flock,
|
||||||
flockSync: __bootstrap.fs.flockSync,
|
flockSync: __bootstrap.fs.flockSync,
|
||||||
funlock: __bootstrap.fs.funlock,
|
funlock: __bootstrap.fs.funlock,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::os::raw::c_void;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
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) };
|
let buf = unsafe { std::slice::from_raw_parts(ptr, len) };
|
||||||
assert_eq!(buf, vec![1, 2, 3, 4, 5, 6, 7, 8]);
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -109,3 +109,15 @@ const result4 = remote.symbols.method19();
|
||||||
// @ts-expect-error: Invalid argument
|
// @ts-expect-error: Invalid argument
|
||||||
result4.then((_0: Deno.TypedArray) => {});
|
result4.then((_0: Deno.TypedArray) => {});
|
||||||
result4.then((_1: Deno.UnsafePointer) => {});
|
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);
|
||||||
|
|
|
@ -56,6 +56,8 @@ fn basic() {
|
||||||
true\n\
|
true\n\
|
||||||
false\n\
|
false\n\
|
||||||
579\n\
|
579\n\
|
||||||
|
true\n\
|
||||||
|
579\n\
|
||||||
579\n\
|
579\n\
|
||||||
579\n\
|
579\n\
|
||||||
579\n\
|
579\n\
|
||||||
|
|
|
@ -65,6 +65,14 @@ const dylib = Deno.dlopen(libPath, {
|
||||||
result: "void",
|
result: "void",
|
||||||
nonblocking: true,
|
nonblocking: true,
|
||||||
},
|
},
|
||||||
|
"get_add_u32_ptr": {
|
||||||
|
parameters: [],
|
||||||
|
result: "pointer",
|
||||||
|
},
|
||||||
|
"get_sleep_blocking_ptr": {
|
||||||
|
parameters: [],
|
||||||
|
result: "pointer",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dylib.symbols.printSomething();
|
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(ptr)));
|
||||||
console.log(Boolean(dylib.symbols.is_null_ptr(null)));
|
console.log(Boolean(dylib.symbols.is_null_ptr(null)));
|
||||||
console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into))));
|
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));
|
console.log(dylib.symbols.add_u32(123, 456));
|
||||||
assertThrows(
|
assertThrows(
|
||||||
() => {
|
() => {
|
||||||
|
|
Loading…
Reference in a new issue