1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -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 GitHub
parent 4a144c7d6e
commit b1a6555c05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 268 additions and 21 deletions

View file

@ -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> = (
/** 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>;

View file

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

View file

@ -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,9 +490,16 @@ where
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)?;
}
}
}
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,

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 {
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 },
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;

View file

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

View file

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