mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 00:29:09 -05:00
feat(ext/ffi): Support read only global statics (#13662)
This commit is contained in:
parent
bbfc41645f
commit
0dfaccc533
7 changed files with 268 additions and 21 deletions
43
cli/dts/lib.deno.unstable.d.ts
vendored
43
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
110
ext/ffi/lib.rs
110
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,
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue