mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -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
35
cli/dts/lib.deno.unstable.d.ts
vendored
35
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -176,9 +176,15 @@ declare namespace Deno {
|
||||||
nonblocking?: NonBlocking;
|
nonblocking?: NonBlocking;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A foreign function interface descriptor */
|
export interface ForeignStatic<Type extends NativeType = NativeType> {
|
||||||
export interface ForeignFunctionInterface {
|
/** Name of the symbol, defaults to the key name in symbols object. */
|
||||||
[name: string]: ForeignFunction;
|
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 */
|
/** All possible number types interfacing with foreign functions */
|
||||||
|
@ -203,20 +209,23 @@ declare namespace Deno {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Infers a foreign function */
|
/** Infers a foreign symbol */
|
||||||
type StaticForeignFunction<T extends ForeignFunction> = (
|
type StaticForeignSymbol<T extends ForeignFunction | ForeignStatic> =
|
||||||
|
T extends ForeignFunction ? (
|
||||||
...args: StaticForeignFunctionParameters<T["parameters"]>
|
...args: StaticForeignFunctionParameters<T["parameters"]>
|
||||||
) => ConditionalAsync<
|
) => ConditionalAsync<
|
||||||
T["nonblocking"],
|
T["nonblocking"],
|
||||||
StaticForeignFunctionResult<T["result"]>
|
StaticForeignFunctionResult<T["result"]>
|
||||||
>;
|
>
|
||||||
|
: T extends ForeignStatic ? StaticForeignFunctionResult<T["type"]>
|
||||||
|
: never;
|
||||||
|
|
||||||
type ConditionalAsync<IsAsync extends boolean | undefined, T> =
|
type ConditionalAsync<IsAsync extends boolean | undefined, T> =
|
||||||
IsAsync extends true ? Promise<T> : T;
|
IsAsync extends true ? Promise<T> : T;
|
||||||
|
|
||||||
/** Infers a foreign function interface */
|
/** Infers a foreign library interface */
|
||||||
type StaticForeignFunctionInterface<T extends ForeignFunctionInterface> = {
|
type StaticForeignLibraryInterface<T extends ForeignLibraryInterface> = {
|
||||||
[K in keyof T]: StaticForeignFunction<T[K]>;
|
[K in keyof T]: StaticForeignSymbol<T[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TypedArray =
|
type TypedArray =
|
||||||
|
@ -313,9 +322,9 @@ declare namespace Deno {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A dynamic library resource */
|
/** A dynamic library resource */
|
||||||
export interface DynamicLibrary<S extends ForeignFunctionInterface> {
|
export interface DynamicLibrary<S extends ForeignLibraryInterface> {
|
||||||
/** All of the registered symbols along with functions for calling them */
|
/** All of the registered library along with functions for calling them */
|
||||||
symbols: StaticForeignFunctionInterface<S>;
|
symbols: StaticForeignLibraryInterface<S>;
|
||||||
close(): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +332,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* Opens a dynamic library and registers symbols
|
* Opens a dynamic library and registers symbols
|
||||||
*/
|
*/
|
||||||
export function dlopen<S extends ForeignFunctionInterface>(
|
export function dlopen<S extends ForeignLibraryInterface>(
|
||||||
filename: string | URL,
|
filename: string | URL,
|
||||||
symbols: S,
|
symbols: S,
|
||||||
): DynamicLibrary<S>;
|
): DynamicLibrary<S>;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
Uint8Array,
|
Uint8Array,
|
||||||
BigInt,
|
BigInt,
|
||||||
Number,
|
Number,
|
||||||
|
ObjectDefineProperty,
|
||||||
ObjectPrototypeIsPrototypeOf,
|
ObjectPrototypeIsPrototypeOf,
|
||||||
TypeError,
|
TypeError,
|
||||||
} = window.__bootstrap.primordials;
|
} = window.__bootstrap.primordials;
|
||||||
|
@ -230,10 +231,47 @@
|
||||||
this.#rid = core.opSync("op_ffi_load", { path, symbols });
|
this.#rid = core.opSync("op_ffi_load", { path, symbols });
|
||||||
|
|
||||||
for (const symbol in 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 isNonBlocking = symbols[symbol].nonblocking;
|
||||||
const types = symbols[symbol].parameters;
|
const types = symbols[symbol].parameters;
|
||||||
|
|
||||||
this.symbols[symbol] = (...args) => {
|
const fn = (...args) => {
|
||||||
const { parameters, buffers } = prepareArgs(types, args);
|
const { parameters, buffers } = prepareArgs(types, args);
|
||||||
|
|
||||||
if (isNonBlocking) {
|
if (isNonBlocking) {
|
||||||
|
@ -266,6 +304,17 @@
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ObjectDefineProperty(
|
||||||
|
this.symbols,
|
||||||
|
symbol,
|
||||||
|
{
|
||||||
|
configurable: false,
|
||||||
|
enumerable: true,
|
||||||
|
value: fn,
|
||||||
|
writable: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
108
ext/ffi/lib.rs
108
ext/ffi/lib.rs
|
@ -118,6 +118,19 @@ impl DynamicLibraryResource {
|
||||||
|
|
||||||
Ok(())
|
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 {
|
pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
|
||||||
|
@ -128,6 +141,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
|
||||||
))
|
))
|
||||||
.ops(vec![
|
.ops(vec![
|
||||||
("op_ffi_load", op_sync(op_ffi_load::<P>)),
|
("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", 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", op_sync(op_ffi_call_ptr)),
|
||||||
|
@ -351,10 +365,28 @@ struct ForeignFunction {
|
||||||
result: NativeType,
|
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)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct FfiLoadArgs {
|
struct FfiLoadArgs {
|
||||||
path: String,
|
path: String,
|
||||||
symbols: HashMap<String, ForeignFunction>,
|
symbols: HashMap<String, ForeignSymbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// `path` is only used on Windows.
|
// `path` is only used on Windows.
|
||||||
|
@ -458,9 +490,16 @@ where
|
||||||
symbols: HashMap::new(),
|
symbols: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (symbol, foreign_fn) in args.symbols {
|
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)?;
|
resource.register(symbol, foreign_fn)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(state.resource_table.add(resource))
|
Ok(state.resource_table.add(resource))
|
||||||
}
|
}
|
||||||
|
@ -631,6 +670,71 @@ async fn op_ffi_call_ptr_nonblocking(
|
||||||
.unwrap()
|
.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(
|
fn op_ffi_call(
|
||||||
state: &mut deno_core::OpState,
|
state: &mut deno_core::OpState,
|
||||||
args: FfiCallArgs,
|
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 {
|
pub extern "C" fn get_sleep_blocking_ptr() -> *const c_void {
|
||||||
sleep_blocking as *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 },
|
method17: { parameters: [], result: "usize", nonblocking: true },
|
||||||
method18: { parameters: [], result: "pointer" },
|
method18: { parameters: [], result: "pointer" },
|
||||||
method19: { parameters: [], result: "pointer", nonblocking: true },
|
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,
|
} as const,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -121,3 +135,46 @@ const fnptr = new Deno.UnsafeFnPointer(
|
||||||
// @ts-expect-error: Invalid argument
|
// @ts-expect-error: Invalid argument
|
||||||
fnptr.call(null, null);
|
fnptr.call(null, null);
|
||||||
fnptr.call(0, 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\
|
true\n\
|
||||||
Before\n\
|
Before\n\
|
||||||
true\n\
|
true\n\
|
||||||
|
Static u32: 42\n\
|
||||||
|
Static ptr: true\n\
|
||||||
|
Static ptr value: 42\n\
|
||||||
After\n\
|
After\n\
|
||||||
true\n\
|
true\n\
|
||||||
Correct number of resources\n";
|
Correct number of resources\n";
|
||||||
|
|
|
@ -73,6 +73,12 @@ const dylib = Deno.dlopen(libPath, {
|
||||||
parameters: [],
|
parameters: [],
|
||||||
result: "pointer",
|
result: "pointer",
|
||||||
},
|
},
|
||||||
|
"static_u32": {
|
||||||
|
type: "u32",
|
||||||
|
},
|
||||||
|
"static_ptr": {
|
||||||
|
type: "pointer",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dylib.symbols.printSomething();
|
dylib.symbols.printSomething();
|
||||||
|
@ -201,6 +207,14 @@ dylib.symbols.sleep_nonblocking(100).then(() => {
|
||||||
console.log("Before");
|
console.log("Before");
|
||||||
console.log(performance.now() - start < 100);
|
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() {
|
function cleanup() {
|
||||||
dylib.close();
|
dylib.close();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue