diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index db59980ec0b871..24d8376dc503ae 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -117,13 +117,19 @@ declare namespace Deno { | "usize" | "isize" | "f32" - | "f64"; + | "f64" + | "string" + | "buffer"; /** A foreign function as defined by its parameter and result types */ - export interface ForeignFunction { + export type ForeignFunction = { parameters: NativeType[]; result: NativeType; - } + } | { + parameters: NativeType[]; + result: "buffer"; + resultLength?: number; + }; /** A dynamic library resource */ export interface DynamicLibrary> { diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index 553ea1dd3663b3..50103cb4ab2b4b 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -4,7 +4,9 @@ ((window) => { const core = window.Deno.core; const __bootstrap = window.__bootstrap; - + const { + ArrayBuffer, + } = window.__bootstrap.primordials; class DynamicLibrary { #rid; symbols = {}; @@ -13,8 +15,29 @@ 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] = (...args) => { + const parameters = []; + const buffers = []; + + for (const arg of args) { + if ( + arg?.buffer instanceof ArrayBuffer && + arg.byteLength !== undefined + ) { + parameters.push(buffers.length); + buffers.push(arg); + } else { + parameters.push(arg); + } + } + + return core.opSync("op_ffi_call", { + rid: this.#rid, + symbol, + parameters, + buffers, + }); + }; } } diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 673f8347223f57..f29dabdc987fd5 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -10,6 +10,7 @@ use deno_core::Extension; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; use dlopen::raw::Library; use libffi::middle::Arg; use serde::Deserialize; @@ -17,6 +18,9 @@ use std::borrow::Cow; use std::collections::HashMap; use std::convert::TryFrom; use std::ffi::c_void; +use std::ffi::CStr; +use std::ffi::CString; +use std::os::raw::c_char; use std::rc::Rc; pub struct Unstable(pub bool); @@ -50,6 +54,7 @@ struct Symbol { ptr: libffi::middle::CodePtr, parameter_types: Vec, result_type: NativeType, + result_length: Option, } struct DynamicLibraryResource { @@ -75,13 +80,12 @@ impl DynamicLibraryResource { ) -> Result<(), AnyError> { let fn_ptr = unsafe { self.lib.symbol::<*const c_void>(&symbol) }?; let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _); + let parameter_types = + foreign_fn.parameters.into_iter().map(NativeType::from); + let result_type = NativeType::from(foreign_fn.result); let cif = libffi::middle::Cif::new( - foreign_fn - .parameters - .clone() - .into_iter() - .map(libffi::middle::Type::from), - foreign_fn.result.into(), + parameter_types.clone().map(libffi::middle::Type::from), + result_type.into(), ); self.symbols.insert( @@ -89,8 +93,9 @@ impl DynamicLibraryResource { Symbol { cif, ptr, - parameter_types: foreign_fn.parameters, - result_type: foreign_fn.result, + parameter_types: parameter_types.collect(), + result_type, + result_length: foreign_fn.result_length, }, ); @@ -132,6 +137,8 @@ enum NativeType { ISize, F32, F64, + String, + Buffer, } impl From for libffi::middle::Type { @@ -150,85 +157,99 @@ impl From for libffi::middle::Type { NativeType::ISize => libffi::middle::Type::isize(), NativeType::F32 => libffi::middle::Type::f32(), NativeType::F64 => libffi::middle::Type::f64(), + NativeType::String => libffi::middle::Type::pointer(), + NativeType::Buffer => libffi::middle::Type::pointer(), } } } -#[repr(C)] -union NativeValue { - void_value: (), - u8_value: u8, - i8_value: i8, - u16_value: u16, - i16_value: i16, - u32_value: u32, - i32_value: i32, - u64_value: u64, - i64_value: i64, - usize_value: usize, - isize_value: isize, - f32_value: f32, - f64_value: f64, +impl From for NativeType { + fn from(string: String) -> Self { + match string.as_str() { + "void" => NativeType::Void, + "u8" => NativeType::U8, + "i8" => NativeType::I8, + "u16" => NativeType::U16, + "i16" => NativeType::I16, + "u32" => NativeType::U32, + "i32" => NativeType::I32, + "u64" => NativeType::U64, + "i64" => NativeType::I64, + "usize" => NativeType::USize, + "isize" => NativeType::ISize, + "f32" => NativeType::F32, + "f64" => NativeType::F64, + "string" => NativeType::String, + "buffer" => NativeType::Buffer, + _ => unimplemented!(), + } + } +} + +enum NativeValue { + Void, + U8(u8), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + USize(usize), + ISize(isize), + F32(f32), + F64(f64), + String(*const i8), + Buffer(*const u8), } impl NativeValue { - fn new(native_type: NativeType, value: Value) -> Self { + fn from_value(native_type: NativeType, value: Value) -> Self { match native_type { - NativeType::Void => Self { void_value: () }, - NativeType::U8 => Self { - u8_value: value_as_uint::(value), - }, - NativeType::I8 => Self { - i8_value: value_as_int::(value), - }, - NativeType::U16 => Self { - u16_value: value_as_uint::(value), - }, - NativeType::I16 => Self { - i16_value: value_as_int::(value), - }, - NativeType::U32 => Self { - u32_value: value_as_uint::(value), - }, - NativeType::I32 => Self { - i32_value: value_as_int::(value), - }, - NativeType::U64 => Self { - u64_value: value_as_uint::(value), - }, - NativeType::I64 => Self { - i64_value: value_as_int::(value), - }, - NativeType::USize => Self { - usize_value: value_as_uint::(value), - }, - NativeType::ISize => Self { - isize_value: value_as_int::(value), - }, - NativeType::F32 => Self { - f32_value: value_as_f32(value), - }, - NativeType::F64 => Self { - f64_value: value_as_f64(value), - }, + NativeType::Void => Self::Void, + NativeType::U8 => Self::U8(value_as_uint::(value)), + NativeType::I8 => Self::I8(value_as_int::(value)), + NativeType::U16 => Self::U16(value_as_uint::(value)), + NativeType::I16 => Self::I16(value_as_int::(value)), + NativeType::U32 => Self::U32(value_as_uint::(value)), + NativeType::I32 => Self::I32(value_as_int::(value)), + NativeType::U64 => Self::U64(value_as_uint::(value)), + NativeType::I64 => Self::I64(value_as_int::(value)), + NativeType::USize => Self::USize(value_as_uint::(value)), + NativeType::ISize => Self::ISize(value_as_int::(value)), + NativeType::F32 => Self::F32(value_as_f32(value)), + NativeType::F64 => Self::F64(value_as_f64(value)), + NativeType::String => Self::String( + CString::new( + value + .as_str() + .expect("Expected ffi arg value to be a string"), + ) + .unwrap() + .into_raw(), + ), + NativeType::Buffer => unreachable!(), } } - unsafe fn as_arg(&self, native_type: NativeType) -> Arg { - match native_type { - NativeType::Void => Arg::new(&self.void_value), - NativeType::U8 => Arg::new(&self.u8_value), - NativeType::I8 => Arg::new(&self.i8_value), - NativeType::U16 => Arg::new(&self.u16_value), - NativeType::I16 => Arg::new(&self.i16_value), - NativeType::U32 => Arg::new(&self.u32_value), - NativeType::I32 => Arg::new(&self.i32_value), - NativeType::U64 => Arg::new(&self.u64_value), - NativeType::I64 => Arg::new(&self.i64_value), - NativeType::USize => Arg::new(&self.usize_value), - NativeType::ISize => Arg::new(&self.isize_value), - NativeType::F32 => Arg::new(&self.f32_value), - NativeType::F64 => Arg::new(&self.f64_value), + fn as_arg(&self) -> Arg { + match self { + Self::Void => Arg::new(&()), + Self::U8(value) => Arg::new(value), + Self::I8(value) => Arg::new(value), + Self::U16(value) => Arg::new(value), + Self::I16(value) => Arg::new(value), + Self::U32(value) => Arg::new(value), + Self::I32(value) => Arg::new(value), + Self::U64(value) => Arg::new(value), + Self::I64(value) => Arg::new(value), + Self::USize(value) => Arg::new(value), + Self::ISize(value) => Arg::new(value), + Self::F32(value) => Arg::new(value), + Self::F64(value) => Arg::new(value), + Self::String(value) => Arg::new(value), + Self::Buffer(value) => Arg::new(value), } } } @@ -258,9 +279,11 @@ fn value_as_f64(value: Value) -> f64 { } #[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] struct ForeignFunction { - parameters: Vec, - result: NativeType, + parameters: Vec, + result: String, + result_length: Option, } #[derive(Deserialize, Debug)] @@ -294,12 +317,13 @@ where Ok(state.resource_table.add(resource)) } -#[derive(Deserialize, Debug)] +#[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct FfiCallArgs { rid: ResourceId, symbol: String, parameters: Vec, + buffers: Vec, } fn op_ffi_call( @@ -316,20 +340,27 @@ fn op_ffi_call( .get(&args.symbol) .ok_or_else(bad_resource_id)?; + let buffers: Vec<&[u8]> = + args.buffers.iter().map(|buffer| &buffer[..]).collect(); + let native_values = symbol .parameter_types .iter() .zip(args.parameters.into_iter()) - .map(|(&native_type, value)| NativeValue::new(native_type, value)) + .map(|(&native_type, value)| { + if let NativeType::Buffer = native_type { + let idx: usize = value_as_uint(value); + let ptr = buffers[idx].as_ptr(); + NativeValue::Buffer(ptr) + } else { + NativeValue::from_value(native_type, value) + } + }) .collect::>(); - let call_args = symbol - .parameter_types + let call_args = native_values .iter() - .zip(native_values.iter()) - .map(|(&native_type, native_value)| unsafe { - native_value.as_arg(native_type) - }) + .map(|native_value| native_value.as_arg()) .collect::>(); Ok(match symbol.result_type { @@ -372,5 +403,21 @@ fn op_ffi_call( NativeType::F64 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } + NativeType::String => { + json!(unsafe { + let ptr = symbol.cif.call::<*const c_char>(symbol.ptr, &call_args); + let cstr = CStr::from_ptr(ptr); + cstr.to_str().unwrap() + }) + } + NativeType::Buffer => { + let ptr = unsafe { symbol.cif.call::<*const u8>(symbol.ptr, &call_args) }; + if let Some(len) = symbol.result_length { + let slice = unsafe { std::slice::from_raw_parts(ptr, len) }; + json!(slice) + } else { + json!(ptr as usize) + } + } }) } diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs index a5a07795028b5d..6e50079cb1a305 100644 --- a/test_ffi/src/lib.rs +++ b/test_ffi/src/lib.rs @@ -1,8 +1,37 @@ +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + #[no_mangle] pub extern "C" fn print_something() { println!("something"); } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn print_string(ptr: *const c_char) { + let cstr = unsafe { CStr::from_ptr(ptr) }; + let name = cstr.to_str().unwrap(); + println!("{}", name); +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn print_buffer(ptr: *const u8, len: usize) { + let buf = unsafe { std::slice::from_raw_parts(ptr, len) }; + println!("{:?}", buf); +} + +#[no_mangle] +pub extern "C" fn return_string() -> *const c_char { + let cstring = CString::new("Hello from test ffi!").unwrap(); + cstring.into_raw() +} + +#[no_mangle] +pub extern "C" fn return_buffer() -> *const u8 { + [1, 2, 3, 4, 5, 6, 7, 8].as_ptr() +} + #[no_mangle] pub extern "C" fn add_u32(a: u32, b: u32) -> u32 { a + b diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index 62b28d879fafd0..8d70d0de4d9a49 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -38,6 +38,10 @@ fn basic() { assert!(output.status.success()); let expected = "\ something\n\ + hello from deno!\n\ + [1, 2, 3, 4, 5, 6, 7, 8]\n\ + Hello from test ffi!\n\ + [1, 2, 3, 4, 5, 6, 7, 8]\n\ 579\n\ 579\n\ 579\n\ diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index 24b64722ca87c9..ac9394c0c815cf 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -12,6 +12,10 @@ const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`; const resourcesPre = Deno.resources(); const dylib = Deno.dlopen(libPath, { "print_something": { parameters: [], result: "void" }, + "print_string": { parameters: ["string"], result: "void" }, + "print_buffer": { parameters: ["buffer", "usize"], result: "void" }, + "return_string": { parameters: [], result: "string" }, + "return_buffer": { parameters: [], result: "buffer", resultLength: 8 }, "add_u32": { parameters: ["u32", "u32"], result: "u32" }, "add_i32": { parameters: ["i32", "i32"], result: "i32" }, "add_u64": { parameters: ["u64", "u64"], result: "u64" }, @@ -23,6 +27,11 @@ const dylib = Deno.dlopen(libPath, { }); dylib.symbols.print_something(); +dylib.symbols.print_string("hello from deno!"); +const buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); +dylib.symbols.print_buffer(buffer, buffer.length); +console.log(dylib.symbols.return_string()); +console.log("[" + dylib.symbols.return_buffer().join(", ") + "]"); console.log(dylib.symbols.add_u32(123, 456)); console.log(dylib.symbols.add_i32(123, 456)); console.log(dylib.symbols.add_u64(123, 456));