summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/dts/lib.deno.unstable.d.ts43
-rw-r--r--ext/ffi/00_ffi.js51
-rw-r--r--ext/ffi/lib.rs110
-rw-r--r--test_ffi/src/lib.rs11
-rw-r--r--test_ffi/tests/ffi_types.ts57
-rw-r--r--test_ffi/tests/integration_tests.rs3
-rw-r--r--test_ffi/tests/test.js14
7 files changed, 268 insertions, 21 deletions
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 708eafe6a..08b3ca80c 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<Type extends NativeType = NativeType> {
+ /** Name of the symbol, defaults to the key name in symbols object. */
+ name?: string;
+ type: Exclude<Type, "void">;
+ }
+
+ /** 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<T extends ForeignFunction> = (
- ...args: StaticForeignFunctionParameters<T["parameters"]>
- ) => ConditionalAsync<
- T["nonblocking"],
- StaticForeignFunctionResult<T["result"]>
- >;
+ /** Infers a foreign symbol */
+ type StaticForeignSymbol<T extends ForeignFunction | ForeignStatic> =
+ T extends ForeignFunction ? (
+ ...args: StaticForeignFunctionParameters<T["parameters"]>
+ ) => ConditionalAsync<
+ T["nonblocking"],
+ StaticForeignFunctionResult<T["result"]>
+ >
+ : T extends ForeignStatic ? StaticForeignFunctionResult<T["type"]>
+ : never;
type ConditionalAsync<IsAsync extends boolean | undefined, T> =
IsAsync extends true ? Promise<T> : T;
- /** Infers a foreign function interface */
- type StaticForeignFunctionInterface<T extends ForeignFunctionInterface> = {
- [K in keyof T]: StaticForeignFunction<T[K]>;
+ /** Infers a foreign library interface */
+ type StaticForeignLibraryInterface<T extends ForeignLibraryInterface> = {
+ [K in keyof T]: StaticForeignSymbol<T[K]>;
};
type TypedArray =
@@ -313,9 +322,9 @@ declare namespace Deno {
}
/** A dynamic library resource */
- export interface DynamicLibrary<S extends ForeignFunctionInterface> {
- /** All of the registered symbols along with functions for calling them */
- symbols: StaticForeignFunctionInterface<S>;
+ export interface DynamicLibrary<S extends ForeignLibraryInterface> {
+ /** All of the registered library along with functions for calling them */
+ symbols: StaticForeignLibraryInterface<S>;
close(): void;
}
@@ -323,7 +332,7 @@ declare namespace Deno {
*
* Opens a dynamic library and registers symbols
*/
- export function dlopen<S extends ForeignFunctionInterface>(
+ export function dlopen<S extends ForeignLibraryInterface>(
filename: string | URL,
symbols: S,
): DynamicLibrary<S>;
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js
index b979c73d4..3debeef14 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 3075684d8..f2e7bb175 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<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
@@ -128,6 +141,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
))
.ops(vec![
("op_ffi_load", op_sync(op_ffi_load::<P>)),
+ ("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<String>,
+ #[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<String, ForeignFunction>,
+ symbols: HashMap<String, ForeignSymbol>,
}
// `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<Value, AnyError> {
+ let resource = state
+ .resource_table
+ .get::<DynamicLibraryResource>(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 a04c2c2fd..9a06e29e7 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 b1e10868a..0aab6a8fa 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 c818f12d9..fea5b5fbd 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 a9681ab9f..943f86ae8 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();