diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 708eafe6a6..08b3ca80c0 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -176,9 +176,15 @@ declare namespace Deno { nonblocking?: NonBlocking; } - /** A foreign function interface descriptor */ - export interface ForeignFunctionInterface { - [name: string]: ForeignFunction; + export interface ForeignStatic { + /** Name of the symbol, defaults to the key name in symbols object. */ + name?: string; + type: Exclude; + } + + /** A foreign library interface descriptor */ + export interface ForeignLibraryInterface { + [name: string]: ForeignFunction | ForeignStatic; } /** All possible number types interfacing with foreign functions */ @@ -203,20 +209,23 @@ declare namespace Deno { }, ]; - /** Infers a foreign function */ - type StaticForeignFunction = ( - ...args: StaticForeignFunctionParameters - ) => ConditionalAsync< - T["nonblocking"], - StaticForeignFunctionResult - >; + /** Infers a foreign symbol */ + type StaticForeignSymbol = + T extends ForeignFunction ? ( + ...args: StaticForeignFunctionParameters + ) => ConditionalAsync< + T["nonblocking"], + StaticForeignFunctionResult + > + : T extends ForeignStatic ? StaticForeignFunctionResult + : never; type ConditionalAsync = IsAsync extends true ? Promise : T; - /** Infers a foreign function interface */ - type StaticForeignFunctionInterface = { - [K in keyof T]: StaticForeignFunction; + /** Infers a foreign library interface */ + type StaticForeignLibraryInterface = { + [K in keyof T]: StaticForeignSymbol; }; type TypedArray = @@ -313,9 +322,9 @@ declare namespace Deno { } /** A dynamic library resource */ - export interface DynamicLibrary { - /** All of the registered symbols along with functions for calling them */ - symbols: StaticForeignFunctionInterface; + export interface DynamicLibrary { + /** All of the registered library along with functions for calling them */ + symbols: StaticForeignLibraryInterface; close(): void; } @@ -323,7 +332,7 @@ declare namespace Deno { * * Opens a dynamic library and registers symbols */ - export function dlopen( + export function dlopen( filename: string | URL, symbols: S, ): DynamicLibrary; diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index b979c73d42..3debeef146 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -9,6 +9,7 @@ Uint8Array, BigInt, Number, + ObjectDefineProperty, ObjectPrototypeIsPrototypeOf, TypeError, } = window.__bootstrap.primordials; @@ -230,10 +231,47 @@ this.#rid = core.opSync("op_ffi_load", { path, symbols }); for (const symbol in symbols) { + if ("type" in symbols[symbol]) { + const type = symbols[symbol].type; + if (type === "void") { + throw new TypeError( + "Foreign symbol of type 'void' is not supported.", + ); + } + + const name = symbols[symbol].name || symbol; + let value = core.opSync( + "op_ffi_get_static", + { + rid: 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); + } + ObjectDefineProperty( + this.symbols, + symbol, + { + configurable: false, + enumerable: true, + value, + writable: false, + }, + ); + continue; + } const isNonBlocking = symbols[symbol].nonblocking; const types = symbols[symbol].parameters; - this.symbols[symbol] = (...args) => { + const fn = (...args) => { const { parameters, buffers } = prepareArgs(types, args); if (isNonBlocking) { @@ -266,6 +304,17 @@ return result; } }; + + ObjectDefineProperty( + this.symbols, + symbol, + { + configurable: false, + enumerable: true, + value: fn, + writable: false, + }, + ); } } diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 3075684d81..f2e7bb175a 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -118,6 +118,19 @@ impl DynamicLibraryResource { Ok(()) } + + fn get_static(&self, symbol: String) -> Result<*const c_void, AnyError> { + // By default, Err returned by this function does not tell + // which symbol wasn't exported. So we'll modify the error + // message to include the name of symbol. + match unsafe { self.lib.symbol::<*const c_void>(&symbol) } { + Ok(value) => Ok(Ok(value)), + Err(err) => Err(generic_error(format!( + "Failed to register symbol {}: {}", + symbol, err + ))), + }? + } } pub fn init(unstable: bool) -> Extension { @@ -128,6 +141,7 @@ pub fn init(unstable: bool) -> Extension { )) .ops(vec![ ("op_ffi_load", op_sync(op_ffi_load::

)), + ("op_ffi_get_static", op_sync(op_ffi_get_static)), ("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)), @@ -351,10 +365,28 @@ struct ForeignFunction { result: NativeType, } +// ForeignStatic's name and type fields are read and used by +// serde_v8 to determine which variant a ForeignSymbol is. +// They are not used beyond that and are thus marked with underscores. +#[derive(Deserialize, Debug)] +struct ForeignStatic { + #[serde(rename(deserialize = "name"))] + _name: Option, + #[serde(rename(deserialize = "type"))] + _type: String, +} + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +enum ForeignSymbol { + ForeignFunction(ForeignFunction), + ForeignStatic(ForeignStatic), +} + #[derive(Deserialize, Debug)] struct FfiLoadArgs { path: String, - symbols: HashMap, + symbols: HashMap, } // `path` is only used on Windows. @@ -458,8 +490,15 @@ where symbols: HashMap::new(), }; - for (symbol, foreign_fn) in args.symbols { - resource.register(symbol, foreign_fn)?; + for (symbol, foreign_symbol) in args.symbols { + match foreign_symbol { + ForeignSymbol::ForeignStatic(_) => { + // No-op: Statics will be handled separately and are not part of the Rust-side resource. + } + ForeignSymbol::ForeignFunction(foreign_fn) => { + resource.register(symbol, foreign_fn)?; + } + } } Ok(state.resource_table.add(resource)) @@ -631,6 +670,71 @@ async fn op_ffi_call_ptr_nonblocking( .unwrap() } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FfiGetArgs { + rid: ResourceId, + name: String, + r#type: NativeType, +} + +fn op_ffi_get_static( + state: &mut deno_core::OpState, + args: FfiGetArgs, + _: (), +) -> Result { + let resource = state + .resource_table + .get::(args.rid)?; + + let data_ptr = resource.get_static(args.name)? as *const u8; + + Ok(match args.r#type { + NativeType::Void => { + unreachable!(); + } + NativeType::U8 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const u8) }) + } + NativeType::I8 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const i8) }) + } + NativeType::U16 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const u16) }) + } + NativeType::I16 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const i16) }) + } + NativeType::U32 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const u32) }) + } + NativeType::I32 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const i32) }) + } + NativeType::U64 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const u64) }) + } + NativeType::I64 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const i64) }) + } + NativeType::USize => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const usize) }) + } + NativeType::ISize => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const isize) }) + } + NativeType::F32 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const f32) }) + } + NativeType::F64 => { + json!(unsafe { ptr::read_unaligned(data_ptr as *const f64) }) + } + NativeType::Pointer => { + json!(U32x2::from(data_ptr as *const u8 as u64)) + } + }) +} + fn op_ffi_call( state: &mut deno_core::OpState, args: FfiCallArgs, diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs index a04c2c2fd5..9a06e29e74 100644 --- a/test_ffi/src/lib.rs +++ b/test_ffi/src/lib.rs @@ -112,3 +112,14 @@ pub extern "C" fn get_add_u32_ptr() -> *const c_void { pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void { sleep_blocking as *const c_void } + +#[no_mangle] +pub static static_u32: u32 = 42; + +#[repr(C)] +pub struct Structure { + _data: u32, +} + +#[no_mangle] +pub static static_ptr: Structure = Structure { _data: 42 }; diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts index b1e10868a2..0aab6a8faa 100644 --- a/test_ffi/tests/ffi_types.ts +++ b/test_ffi/tests/ffi_types.ts @@ -24,6 +24,20 @@ const remote = Deno.dlopen( method17: { parameters: [], result: "usize", nonblocking: true }, method18: { parameters: [], result: "pointer" }, method19: { parameters: [], result: "pointer", nonblocking: true }, + static1: { type: "usize" }, + static2: { type: "pointer" }, + static3: { type: "usize" }, + static4: { type: "isize" }, + static5: { type: "u8" }, + static6: { type: "u16" }, + static7: { type: "u32" }, + static8: { type: "u64" }, + static9: { type: "i8" }, + static10: { type: "i16" }, + static11: { type: "i32" }, + static12: { type: "i64" }, + static13: { type: "f32" }, + static14: { type: "f64" }, } as const, ); @@ -121,3 +135,46 @@ const fnptr = new Deno.UnsafeFnPointer( // @ts-expect-error: Invalid argument fnptr.call(null, null); fnptr.call(0, null); + +// @ts-expect-error: Invalid member type +const static1_wrong: null = remote.symbols.static1; +const static1_right: number = remote.symbols.static1; +// @ts-expect-error: Invalid member type +const static2_wrong: null = remote.symbols.static2; +const static2_right: Deno.UnsafePointer = remote.symbols.static2; +// @ts-expect-error: Invalid member type +const static3_wrong: null = remote.symbols.static3; +const static3_right: number = remote.symbols.static3; +// @ts-expect-error: Invalid member type +const static4_wrong: null = remote.symbols.static4; +const static4_right: number = remote.symbols.static4; +// @ts-expect-error: Invalid member type +const static5_wrong: null = remote.symbols.static5; +const static5_right: number = remote.symbols.static5; +// @ts-expect-error: Invalid member type +const static6_wrong: null = remote.symbols.static6; +const static6_right: number = remote.symbols.static6; +// @ts-expect-error: Invalid member type +const static7_wrong: null = remote.symbols.static7; +const static7_right: number = remote.symbols.static7; +// @ts-expect-error: Invalid member type +const static8_wrong: null = remote.symbols.static8; +const static8_right: number = remote.symbols.static8; +// @ts-expect-error: Invalid member type +const static9_wrong: null = remote.symbols.static9; +const static9_right: number = remote.symbols.static9; +// @ts-expect-error: Invalid member type +const static10_wrong: null = remote.symbols.static10; +const static10_right: number = remote.symbols.static10; +// @ts-expect-error: Invalid member type +const static11_wrong: null = remote.symbols.static11; +const static11_right: number = remote.symbols.static11; +// @ts-expect-error: Invalid member type +const static12_wrong: null = remote.symbols.static12; +const static12_right: number = remote.symbols.static12; +// @ts-expect-error: Invalid member type +const static13_wrong: null = remote.symbols.static13; +const static13_right: number = remote.symbols.static13; +// @ts-expect-error: Invalid member type +const static14_wrong: null = remote.symbols.static14; +const static14_right: number = remote.symbols.static14; diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index c818f12d9d..fea5b5fbda 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -69,6 +69,9 @@ fn basic() { true\n\ Before\n\ true\n\ + Static u32: 42\n\ + Static ptr: true\n\ + Static ptr value: 42\n\ After\n\ true\n\ Correct number of resources\n"; diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index a9681ab9fb..943f86ae89 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -73,6 +73,12 @@ const dylib = Deno.dlopen(libPath, { parameters: [], result: "pointer", }, + "static_u32": { + type: "u32", + }, + "static_ptr": { + type: "pointer", + }, }); dylib.symbols.printSomething(); @@ -201,6 +207,14 @@ dylib.symbols.sleep_nonblocking(100).then(() => { console.log("Before"); console.log(performance.now() - start < 100); +console.log("Static u32:", dylib.symbols.static_u32); +console.log( + "Static ptr:", + dylib.symbols.static_ptr instanceof Deno.UnsafePointer, +); +const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr); +console.log("Static ptr value:", view.getUint32()); + function cleanup() { dylib.close();