1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(ext/ffi): Support read only global statics (#13662)

This commit is contained in:
Aapo Alasuutari 2022-02-18 14:21:19 +02:00 committed by David Sherret
parent bbfc41645f
commit 0dfaccc533
7 changed files with 268 additions and 21 deletions

View file

@ -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> =
...args: StaticForeignFunctionParameters<T["parameters"]> T extends ForeignFunction ? (
) => ConditionalAsync< ...args: StaticForeignFunctionParameters<T["parameters"]>
T["nonblocking"], ) => ConditionalAsync<
StaticForeignFunctionResult<T["result"]> T["nonblocking"],
>; 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>;

View file

@ -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,
},
);
} }
} }

View file

@ -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,8 +490,15 @@ where
symbols: HashMap::new(), symbols: HashMap::new(),
}; };
for (symbol, foreign_fn) in args.symbols { for (symbol, foreign_symbol) in args.symbols {
resource.register(symbol, foreign_fn)?; 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)) 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,

View file

@ -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 };

View file

@ -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;

View file

@ -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";

View file

@ -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();