mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(ext/ffi): Non-blocking FFI (#12274)
This commit is contained in:
parent
f1d3a17043
commit
80aee99c9e
7 changed files with 95 additions and 25 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -778,6 +778,7 @@ dependencies = [
|
|||
"deno_core",
|
||||
"dlopen",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -13,8 +13,15 @@
|
|||
this.#rid = core.opSync("op_ffi_load", { path, symbols });
|
||||
|
||||
for (const symbol in symbols) {
|
||||
this.symbols[symbol] = (...parameters) =>
|
||||
core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters });
|
||||
this.symbols[symbol] = symbols[symbol].nonblocking
|
||||
? (...parameters) =>
|
||||
core.opAsync("op_ffi_call_nonblocking", {
|
||||
rid: this.#rid,
|
||||
symbol,
|
||||
parameters,
|
||||
})
|
||||
: (...parameters) =>
|
||||
core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,3 +18,4 @@ deno_core = { version = "0.101.0", path = "../../core" }
|
|||
dlopen = "0.1.8"
|
||||
libffi = { version = "=0.0.7", package = "deno-libffi" }
|
||||
serde = { version = "1.0.129", features = ["derive"] }
|
||||
tokio = { version = "1.10.1", features = ["full"] }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use deno_core::error::bad_resource_id;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::include_js_files;
|
||||
use deno_core::op_async;
|
||||
use deno_core::op_sync;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::serde_json::Value;
|
||||
|
@ -14,6 +15,7 @@ use dlopen::raw::Library;
|
|||
use libffi::middle::Arg;
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::c_void;
|
||||
|
@ -37,6 +39,7 @@ pub trait FfiPermissions {
|
|||
fn check(&mut self, path: &str) -> Result<(), AnyError>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Symbol {
|
||||
cif: libffi::middle::Cif,
|
||||
ptr: libffi::middle::CodePtr,
|
||||
|
@ -44,6 +47,9 @@ struct Symbol {
|
|||
result_type: NativeType,
|
||||
}
|
||||
|
||||
unsafe impl Send for Symbol {}
|
||||
unsafe impl Sync for Symbol {}
|
||||
|
||||
struct DynamicLibraryResource {
|
||||
lib: Library,
|
||||
symbols: HashMap<String, Symbol>,
|
||||
|
@ -99,6 +105,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
|
|||
.ops(vec![
|
||||
("op_ffi_load", op_sync(op_ffi_load::<P>)),
|
||||
("op_ffi_call", op_sync(op_ffi_call)),
|
||||
("op_ffi_call_nonblocking", op_async(op_ffi_call_nonblocking)),
|
||||
])
|
||||
.state(move |state| {
|
||||
// Stolen from deno_webgpu, is there a better option?
|
||||
|
@ -294,20 +301,7 @@ struct FfiCallArgs {
|
|||
parameters: Vec<Value>,
|
||||
}
|
||||
|
||||
fn op_ffi_call(
|
||||
state: &mut deno_core::OpState,
|
||||
args: FfiCallArgs,
|
||||
_: (),
|
||||
) -> Result<Value, AnyError> {
|
||||
let resource = state
|
||||
.resource_table
|
||||
.get::<DynamicLibraryResource>(args.rid)?;
|
||||
|
||||
let symbol = resource
|
||||
.symbols
|
||||
.get(&args.symbol)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
|
||||
fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
|
||||
let native_values = symbol
|
||||
.parameter_types
|
||||
.iter()
|
||||
|
@ -366,3 +360,41 @@ fn op_ffi_call(
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn op_ffi_call(
|
||||
state: &mut deno_core::OpState,
|
||||
args: FfiCallArgs,
|
||||
_: (),
|
||||
) -> Result<Value, AnyError> {
|
||||
let resource = state
|
||||
.resource_table
|
||||
.get::<DynamicLibraryResource>(args.rid)?;
|
||||
|
||||
let symbol = resource
|
||||
.symbols
|
||||
.get(&args.symbol)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
|
||||
ffi_call(args, symbol)
|
||||
}
|
||||
|
||||
/// A non-blocking FFI call.
|
||||
async fn op_ffi_call_nonblocking(
|
||||
state: Rc<RefCell<deno_core::OpState>>,
|
||||
args: FfiCallArgs,
|
||||
_: (),
|
||||
) -> Result<Value, AnyError> {
|
||||
let resource = state
|
||||
.borrow()
|
||||
.resource_table
|
||||
.get::<DynamicLibraryResource>(args.rid)?;
|
||||
let symbols = &resource.symbols;
|
||||
let symbol = symbols
|
||||
.get(&args.symbol)
|
||||
.ok_or_else(bad_resource_id)?
|
||||
.clone();
|
||||
|
||||
tokio::task::spawn_blocking(move || ffi_call(args, &symbol))
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn print_something() {
|
||||
println!("something");
|
||||
|
@ -42,3 +45,9 @@ pub extern "C" fn add_f32(a: f32, b: f32) -> f32 {
|
|||
pub extern "C" fn add_f64(a: f64, b: f64) -> f64 {
|
||||
a + b
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sleep_blocking(ms: u64) {
|
||||
let duration = Duration::from_millis(ms);
|
||||
sleep(duration);
|
||||
}
|
||||
|
|
|
@ -46,6 +46,10 @@ fn basic() {
|
|||
579\n\
|
||||
579.9119873046875\n\
|
||||
579.912\n\
|
||||
Before\n\
|
||||
true\n\
|
||||
After\n\
|
||||
true\n\
|
||||
Correct number of resources\n";
|
||||
assert_eq!(stdout, expected);
|
||||
assert_eq!(stderr, "");
|
||||
|
|
|
@ -20,6 +20,7 @@ const dylib = Deno.dlopen(libPath, {
|
|||
"add_isize": { parameters: ["isize", "isize"], result: "isize" },
|
||||
"add_f32": { parameters: ["f32", "f32"], result: "f32" },
|
||||
"add_f64": { parameters: ["f64", "f64"], result: "f64" },
|
||||
"sleep_blocking": { parameters: ["u64"], result: "void", nonblocking: true },
|
||||
});
|
||||
|
||||
dylib.symbols.print_something();
|
||||
|
@ -32,16 +33,31 @@ console.log(dylib.symbols.add_isize(123, 456));
|
|||
console.log(dylib.symbols.add_f32(123.123, 456.789));
|
||||
console.log(dylib.symbols.add_f64(123.123, 456.789));
|
||||
|
||||
dylib.close();
|
||||
const resourcesPost = Deno.resources();
|
||||
// Test non blocking calls
|
||||
const start = performance.now();
|
||||
dylib.symbols.sleep_blocking(100).then(() => {
|
||||
console.log("After");
|
||||
console.log(performance.now() - start >= 100);
|
||||
// Close after task is complete.
|
||||
cleanup();
|
||||
});
|
||||
console.log("Before");
|
||||
console.log(performance.now() - start < 100);
|
||||
|
||||
const preStr = JSON.stringify(resourcesPre, null, 2);
|
||||
const postStr = JSON.stringify(resourcesPost, null, 2);
|
||||
if (preStr !== postStr) {
|
||||
throw new Error(
|
||||
`Difference in open resources before dlopen and after closing:
|
||||
function cleanup() {
|
||||
dylib.close();
|
||||
|
||||
const resourcesPost = Deno.resources();
|
||||
|
||||
const preStr = JSON.stringify(resourcesPre, null, 2);
|
||||
const postStr = JSON.stringify(resourcesPost, null, 2);
|
||||
if (preStr !== postStr) {
|
||||
throw new Error(
|
||||
`Difference in open resources before dlopen and after closing:
|
||||
Before: ${preStr}
|
||||
After: ${postStr}`,
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Correct number of resources");
|
||||
}
|
||||
console.log("Correct number of resources");
|
||||
|
|
Loading…
Reference in a new issue