Skip to content

Commit

Permalink
feat(ext/ffi): Non-blocking FFI (#12274)
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy authored Oct 5, 2021
1 parent f1d3a17 commit 80aee99
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 25 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions ext/ffi/00_ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
}

Expand Down
1 change: 1 addition & 0 deletions ext/ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
60 changes: 46 additions & 14 deletions ext/ffi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -37,13 +39,17 @@ pub trait FfiPermissions {
fn check(&mut self, path: &str) -> Result<(), AnyError>;
}

#[derive(Clone)]
struct Symbol {
cif: libffi::middle::Cif,
ptr: libffi::middle::CodePtr,
parameter_types: Vec<NativeType>,
result_type: NativeType,
}

unsafe impl Send for Symbol {}
unsafe impl Sync for Symbol {}

struct DynamicLibraryResource {
lib: Library,
symbols: HashMap<String, Symbol>,
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
}
9 changes: 9 additions & 0 deletions test_ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::thread::sleep;
use std::time::Duration;

#[no_mangle]
pub extern "C" fn print_something() {
println!("something");
Expand Down Expand Up @@ -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);
}
4 changes: 4 additions & 0 deletions test_ffi/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, "");
Expand Down
34 changes: 25 additions & 9 deletions test_ffi/tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);

function cleanup() {
dylib.close();

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:
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");

0 comments on commit 80aee99

Please sign in to comment.