diff options
author | Dj <43033058+DjDeveloperr@users.noreply.github.com> | 2023-01-07 19:58:10 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-08 09:28:10 +0530 |
commit | ad82918f56b215a428ebe7c533ee825e1152d1b4 (patch) | |
tree | 2669f9d8d419a88e9d58b12b98579e884aec6988 /ext | |
parent | 84ef26ac9b5f0e1199d77837cd97cb203baa8729 (diff) |
feat(ext/ffi): structs by value (#15060)
Adds support for passing and returning structs as buffers to FFI. This does not implement fastapi support for structs. Needed for certain system APIs such as AppKit on macOS.
Diffstat (limited to 'ext')
-rw-r--r-- | ext/ffi/00_ffi.js | 157 | ||||
-rw-r--r-- | ext/ffi/Cargo.toml | 2 | ||||
-rw-r--r-- | ext/ffi/call.rs | 79 | ||||
-rw-r--r-- | ext/ffi/callback.rs | 60 | ||||
-rw-r--r-- | ext/ffi/dlfcn.rs | 31 | ||||
-rw-r--r-- | ext/ffi/ir.rs | 79 | ||||
-rw-r--r-- | ext/ffi/static.rs | 3 | ||||
-rw-r--r-- | ext/ffi/symbol.rs | 6 | ||||
-rw-r--r-- | ext/ffi/turbocall.rs | 52 |
9 files changed, 407 insertions, 62 deletions
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index 66ac315b3..5ebd3f96c 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -14,12 +14,18 @@ Number, NumberIsSafeInteger, TypeError, + Uint8Array, Int32Array, Uint32Array, BigInt64Array, BigUint64Array, Function, ReflectHas, + PromisePrototypeThen, + MathMax, + MathCeil, + Map, + SafeArrayIterator, } = window.__bootstrap.primordials; const U32_BUFFER = new Uint32Array(2); @@ -181,26 +187,55 @@ class UnsafeFnPointer { pointer; definition; + #structSize; constructor(pointer, definition) { this.pointer = pointer; this.definition = definition; + this.#structSize = isStruct(definition.result) + ? getTypeSizeAndAlignment(definition.result)[0] + : null; } call(...parameters) { if (this.definition.nonblocking) { - return core.opAsync( - "op_ffi_call_ptr_nonblocking", - this.pointer, - this.definition, - parameters, - ); + if (this.#structSize === null) { + return core.opAsync( + "op_ffi_call_ptr_nonblocking", + this.pointer, + this.definition, + parameters, + ); + } else { + const buffer = new Uint8Array(this.#structSize); + return PromisePrototypeThen( + core.opAsync( + "op_ffi_call_ptr_nonblocking", + this.pointer, + this.definition, + parameters, + buffer, + ), + () => buffer, + ); + } } else { - return ops.op_ffi_call_ptr( - this.pointer, - this.definition, - parameters, - ); + if (this.#structSize === null) { + return ops.op_ffi_call_ptr( + this.pointer, + this.definition, + parameters, + ); + } else { + const buffer = new Uint8Array(this.#structSize); + ops.op_ffi_call_ptr( + this.pointer, + this.definition, + parameters, + buffer, + ); + return buffer; + } } } } @@ -215,6 +250,60 @@ return type === "i64" || type === "isize"; } + function isStruct(type) { + return typeof type === "object" && type !== null && + typeof type.struct === "object"; + } + + function getTypeSizeAndAlignment(type, cache = new Map()) { + if (isStruct(type)) { + const cached = cache.get(type); + if (cached !== undefined) { + if (cached === null) { + throw new TypeError("Recursive struct definition"); + } + return cached; + } + cache.set(type, null); + let size = 0; + let alignment = 1; + for (const field of new SafeArrayIterator(type.struct)) { + const [fieldSize, fieldAlign] = getTypeSizeAndAlignment(field, cache); + alignment = MathMax(alignment, fieldAlign); + size = MathCeil(size / fieldAlign) * fieldAlign; + size += fieldSize; + } + size = MathCeil(size / alignment) * alignment; + cache.set(type, size); + return [size, alignment]; + } + + switch (type) { + case "bool": + case "u8": + case "i8": + return [1, 1]; + case "u16": + case "i16": + return [2, 2]; + case "u32": + case "i32": + case "f32": + return [4, 4]; + case "u64": + case "i64": + case "f64": + case "pointer": + case "buffer": + case "function": + case "usize": + case "isize": + return [8, 8]; + default: + throw new TypeError(`Unsupported type: ${type}`); + } + } + class UnsafeCallback { #refcount; // Internal promise only meant to keep Deno from exiting @@ -306,6 +395,10 @@ continue; } const resultType = symbols[symbol].result; + const isStructResult = isStruct(resultType); + const structSize = isStructResult + ? getTypeSizeAndAlignment(resultType)[0] + : 0; const needsUnpacking = isReturnedAsBigInt(resultType); const isNonBlocking = symbols[symbol].nonblocking; @@ -317,12 +410,27 @@ configurable: false, enumerable: true, value: (...parameters) => { - return core.opAsync( - "op_ffi_call_nonblocking", - this.#rid, - symbol, - parameters, - ); + if (isStructResult) { + const buffer = new Uint8Array(structSize); + const ret = core.opAsync( + "op_ffi_call_nonblocking", + this.#rid, + symbol, + parameters, + buffer, + ); + return PromisePrototypeThen( + ret, + () => buffer, + ); + } else { + return core.opAsync( + "op_ffi_call_nonblocking", + this.#rid, + symbol, + parameters, + ); + } }, writable: false, }, @@ -359,6 +467,21 @@ return b[0]; }`, )(vi, vui, b, call, NumberIsSafeInteger, Number); + } else if (isStructResult && !isNonBlocking) { + const call = this.symbols[symbol]; + const parameters = symbols[symbol].parameters; + const params = ArrayPrototypeJoin( + ArrayPrototypeMap(parameters, (_, index) => `p${index}`), + ", ", + ); + this.symbols[symbol] = new Function( + "call", + `return function (${params}) { + const buffer = new Uint8Array(${structSize}); + call(${params}${parameters.length > 0 ? ", " : ""}buffer); + return buffer; + }`, + )(call); } } } diff --git a/ext/ffi/Cargo.toml b/ext/ffi/Cargo.toml index 840e2620a..f4a7cf160 100644 --- a/ext/ffi/Cargo.toml +++ b/ext/ffi/Cargo.toml @@ -17,7 +17,7 @@ path = "lib.rs" deno_core.workspace = true dlopen.workspace = true dynasmrt = "1.2.3" -libffi = "3.0.0" +libffi = "3.1.0" serde.workspace = true tokio.workspace = true diff --git a/ext/ffi/call.rs b/ext/ffi/call.rs index 2479c3092..2cfd5cef0 100644 --- a/ext/ffi/call.rs +++ b/ext/ffi/call.rs @@ -22,11 +22,28 @@ use std::ffi::c_void; use std::future::Future; use std::rc::Rc; +// SAFETY: Makes an FFI call +unsafe fn ffi_call_rtype_struct( + cif: &libffi::middle::Cif, + fn_ptr: &libffi::middle::CodePtr, + call_args: Vec<Arg>, + out_buffer: *mut u8, +) -> NativeValue { + libffi::raw::ffi_call( + cif.as_raw_ptr(), + Some(*fn_ptr.as_safe_fun()), + out_buffer as *mut c_void, + call_args.as_ptr() as *mut *mut c_void, + ); + NativeValue { void_value: () } +} + // A one-off synchronous FFI call. pub(crate) fn ffi_call_sync<'scope>( scope: &mut v8::HandleScope<'scope>, args: v8::FunctionCallbackArguments, symbol: &Symbol, + out_buffer: Option<OutBuffer>, ) -> Result<NativeValue, AnyError> where 'scope: 'scope, @@ -86,6 +103,9 @@ where NativeType::Buffer => { ffi_args.push(ffi_parse_buffer_arg(scope, value)?); } + NativeType::Struct(_) => { + ffi_args.push(ffi_parse_struct_arg(scope, value)?); + } NativeType::Pointer => { ffi_args.push(ffi_parse_pointer_arg(scope, value)?); } @@ -97,7 +117,12 @@ where } } } - let call_args: Vec<Arg> = ffi_args.iter().map(Arg::new).collect(); + let call_args: Vec<Arg> = ffi_args + .iter() + .enumerate() + // SAFETY: Creating a `Arg` from a `NativeValue` is pretty safe. + .map(|(i, v)| unsafe { v.as_arg(parameter_types.get(i).unwrap()) }) + .collect(); // SAFETY: types in the `Cif` match the actual calling convention and // types of symbol. unsafe { @@ -149,6 +174,12 @@ where pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args), } } + NativeType::Struct(_) => ffi_call_rtype_struct( + &symbol.cif, + &symbol.ptr, + call_args, + out_buffer.unwrap().0, + ), }) } } @@ -159,13 +190,14 @@ fn ffi_call( fun_ptr: libffi::middle::CodePtr, parameter_types: &[NativeType], result_type: NativeType, + out_buffer: Option<OutBuffer>, ) -> Result<NativeValue, AnyError> { let call_args: Vec<Arg> = call_args .iter() .enumerate() .map(|(index, ffi_arg)| { // SAFETY: the union field is initialized - unsafe { ffi_arg.as_arg(*parameter_types.get(index).unwrap()) } + unsafe { ffi_arg.as_arg(parameter_types.get(index).unwrap()) } }) .collect(); @@ -220,6 +252,9 @@ fn ffi_call( pointer: cif.call::<*mut c_void>(fun_ptr, &call_args), } } + NativeType::Struct(_) => { + ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0) + } }) } } @@ -231,6 +266,7 @@ pub fn op_ffi_call_ptr_nonblocking<'scope, FP>( pointer: usize, def: ForeignFunction, parameters: serde_v8::Value<'scope>, + out_buffer: Option<serde_v8::Value<'scope>>, ) -> Result<impl Future<Output = Result<Value, AnyError>>, AnyError> where FP: FfiPermissions + 'static, @@ -244,10 +280,22 @@ where let symbol = PtrSymbol::new(pointer, &def); let call_args = ffi_parse_args(scope, parameters, &def.parameters)?; + let def_result = def.result.clone(); + + let out_buffer = out_buffer + .map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap()); + let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); let join_handle = tokio::task::spawn_blocking(move || { let PtrSymbol { cif, ptr } = symbol.clone(); - ffi_call(call_args, &cif, ptr, &def.parameters, def.result) + ffi_call( + call_args, + &cif, + ptr, + &def.parameters, + def.result, + out_buffer_ptr, + ) }); Ok(async move { @@ -255,7 +303,7 @@ where .await .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - Ok(unsafe { result.to_value(def.result) }) + Ok(unsafe { result.to_value(def_result) }) }) } @@ -267,6 +315,7 @@ pub fn op_ffi_call_nonblocking<'scope>( rid: ResourceId, symbol: String, parameters: serde_v8::Value<'scope>, + out_buffer: Option<serde_v8::Value<'scope>>, ) -> Result<impl Future<Output = Result<Value, AnyError>> + 'static, AnyError> { let symbol = { let state = state.borrow(); @@ -279,8 +328,11 @@ pub fn op_ffi_call_nonblocking<'scope>( }; let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?; + let out_buffer = out_buffer + .map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap()); + let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); - let result_type = symbol.result_type; + let result_type = symbol.result_type.clone(); let join_handle = tokio::task::spawn_blocking(move || { let Symbol { cif, @@ -289,7 +341,14 @@ pub fn op_ffi_call_nonblocking<'scope>( result_type, .. } = symbol.clone(); - ffi_call(call_args, &cif, ptr, ¶meter_types, result_type) + ffi_call( + call_args, + &cif, + ptr, + ¶meter_types, + result_type, + out_buffer_ptr, + ) }); Ok(async move { @@ -308,6 +367,7 @@ pub fn op_ffi_call_ptr<FP, 'scope>( pointer: usize, def: ForeignFunction, parameters: serde_v8::Value<'scope>, + out_buffer: Option<serde_v8::Value<'scope>>, ) -> Result<serde_v8::Value<'scope>, AnyError> where FP: FfiPermissions + 'static, @@ -322,12 +382,17 @@ where let symbol = PtrSymbol::new(pointer, &def); let call_args = ffi_parse_args(scope, parameters, &def.parameters)?; + let out_buffer = out_buffer + .map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap()); + let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); + let result = ffi_call( call_args, &symbol.cif, symbol.ptr, &def.parameters, - def.result, + def.result.clone(), + out_buffer_ptr, )?; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. let result = unsafe { result.to_v8(scope, def.result) }; diff --git a/ext/ffi/callback.rs b/ext/ffi/callback.rs index b9398c790..d6ef51823 100644 --- a/ext/ffi/callback.rs +++ b/ext/ffi/callback.rs @@ -48,7 +48,7 @@ impl PtrSymbol { .clone() .into_iter() .map(libffi::middle::Type::from), - def.result.into(), + def.result.clone().into(), ); Self { cif, ptr } @@ -113,7 +113,7 @@ impl Future for CallbackInfo { } } unsafe extern "C" fn deno_ffi_callback( - _cif: &libffi::low::ffi_cif, + cif: &libffi::low::ffi_cif, result: &mut c_void, args: *const *const c_void, info: &CallbackInfo, @@ -121,15 +121,16 @@ unsafe extern "C" fn deno_ffi_callback( LOCAL_ISOLATE_POINTER.with(|s| { if ptr::eq(*s.borrow(), info.isolate) { // Own isolate thread, okay to call directly - do_ffi_callback(info, result, args); + do_ffi_callback(cif, info, result, args); } else { let async_work_sender = &info.async_work_sender; // SAFETY: Safe as this function blocks until `do_ffi_callback` completes and a response message is received. + let cif: &'static libffi::low::ffi_cif = std::mem::transmute(cif); let result: &'static mut c_void = std::mem::transmute(result); let info: &'static CallbackInfo = std::mem::transmute(info); let (response_sender, response_receiver) = sync_channel::<()>(0); let fut = Box::new(move || { - do_ffi_callback(info, result, args); + do_ffi_callback(cif, info, result, args); response_sender.send(()).unwrap(); }); async_work_sender.unbounded_send(fut).unwrap(); @@ -143,6 +144,7 @@ unsafe extern "C" fn deno_ffi_callback( } unsafe fn do_ffi_callback( + cif: &libffi::low::ffi_cif, info: &CallbackInfo, result: &mut c_void, args: *const *const c_void, @@ -172,9 +174,12 @@ unsafe fn do_ffi_callback( let result = result as *mut c_void; let vals: &[*const c_void] = std::slice::from_raw_parts(args, info.parameters.len()); + let arg_types = std::slice::from_raw_parts(cif.arg_types, cif.nargs as usize); let mut params: Vec<v8::Local<v8::Value>> = vec![]; - for (native_type, val) in info.parameters.iter().zip(vals) { + for ((index, native_type), val) in + info.parameters.iter().enumerate().zip(vals) + { let value: v8::Local<v8::Value> = match native_type { NativeType::Bool => { let value = *((*val) as *const bool); @@ -237,6 +242,20 @@ unsafe fn do_ffi_callback( v8::Number::new(scope, result as f64).into() } } + NativeType::Struct(_) => { + let size = arg_types[index].as_ref().unwrap().size; + let ptr = (*val) as *const u8; + let slice = std::slice::from_raw_parts(ptr, size); + let boxed = Box::from(slice); + let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(boxed); + let ab = + v8::ArrayBuffer::with_backing_store(scope, &store.make_shared()); + let local_value: v8::Local<v8::Value> = + v8::Uint8Array::new(scope, ab, 0, ab.byte_length()) + .unwrap() + .into(); + local_value + } NativeType::Void => unreachable!(), }; params.push(value); @@ -440,6 +459,35 @@ unsafe fn do_ffi_callback( as u64; } } + NativeType::Struct(_) => { + let size; + let pointer = if let Ok(value) = + v8::Local::<v8::ArrayBufferView>::try_from(value) + { + let byte_offset = value.byte_offset(); + let ab = value + .buffer(scope) + .expect("Unable to deserialize result parameter."); + size = value.byte_length(); + ab.data() + .expect("Unable to deserialize result parameter.") + .as_ptr() + .add(byte_offset) + } else if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(value) { + size = value.byte_length(); + value + .data() + .expect("Unable to deserialize result parameter.") + .as_ptr() + } else { + panic!("Unable to deserialize result parameter."); + }; + std::ptr::copy_nonoverlapping( + pointer as *mut u8, + result as *mut u8, + std::cmp::min(size, (*cif.rtype).size), + ); + } NativeType::Void => { // nop } @@ -522,7 +570,7 @@ where let info: *mut CallbackInfo = Box::leak(Box::new(CallbackInfo { parameters: args.parameters.clone(), - result: args.result, + result: args.result.clone(), async_work_sender, callback, context, diff --git a/ext/ffi/dlfcn.rs b/ext/ffi/dlfcn.rs index 5caf95ef2..eeff2c8a7 100644 --- a/ext/ffi/dlfcn.rs +++ b/ext/ffi/dlfcn.rs @@ -1,6 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::check_unstable; +use crate::ir::out_buffer_as_ptr; use crate::symbol::NativeType; use crate::symbol::Symbol; use crate::turbocall; @@ -52,7 +53,7 @@ impl DynamicLibraryResource { } } -pub fn needs_unwrap(rv: NativeType) -> bool { +pub fn needs_unwrap(rv: &NativeType) -> bool { matches!( rv, NativeType::Function @@ -65,7 +66,7 @@ pub fn needs_unwrap(rv: NativeType) -> bool { ) } -fn is_i64(rv: NativeType) -> bool { +fn is_i64(rv: &NativeType) -> bool { matches!(rv, NativeType::I64 | NativeType::ISize) } @@ -166,7 +167,7 @@ where .clone() .into_iter() .map(libffi::middle::Type::from), - foreign_fn.result.into(), + foreign_fn.result.clone().into(), ); let func_key = v8::String::new(scope, &symbol_key).unwrap(); @@ -216,11 +217,24 @@ fn make_sync_fn<'s>( // SAFETY: The pointer will not be deallocated until the function is // garbage collected. let symbol = unsafe { &*(external.value() as *const Symbol) }; - let needs_unwrap = match needs_unwrap(symbol.result_type) { + let needs_unwrap = match needs_unwrap(&symbol.result_type) { true => Some(args.get(symbol.parameter_types.len() as i32)), false => None, }; - match crate::call::ffi_call_sync(scope, args, symbol) { + let out_buffer = match symbol.result_type { + NativeType::Struct(_) => { + let argc = args.length(); + out_buffer_as_ptr( + scope, + Some( + v8::Local::<v8::TypedArray>::try_from(args.get(argc - 1)) + .unwrap(), + ), + ) + } + _ => None, + }; + match crate::call::ffi_call_sync(scope, args, symbol, out_buffer) { Ok(result) => { match needs_unwrap { Some(v) => { @@ -228,7 +242,7 @@ fn make_sync_fn<'s>( let backing_store = view.buffer(scope).unwrap().get_backing_store(); - if is_i64(symbol.result_type) { + if is_i64(&symbol.result_type) { // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>, // it points to a fixed continuous slice of bytes on the heap. let bs = unsafe { @@ -251,8 +265,9 @@ fn make_sync_fn<'s>( } } None => { - // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - let result = unsafe { result.to_v8(scope, symbol.result_type) }; + let result = + // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. + unsafe { result.to_v8(scope, symbol.result_type.clone()) }; rv.set(result.v8_value); } } diff --git a/ext/ffi/ir.rs b/ext/ffi/ir.rs index df13f0611..80f727cd2 100644 --- a/ext/ffi/ir.rs +++ b/ext/ffi/ir.rs @@ -12,6 +12,29 @@ use libffi::middle::Arg; use std::ffi::c_void; use std::ptr; +pub struct OutBuffer(pub *mut u8, pub usize); + +// SAFETY: OutBuffer is allocated by us in 00_ffi.js and is guaranteed to be +// only used for the purpose of writing return value of structs. +unsafe impl Send for OutBuffer {} +// SAFETY: See above +unsafe impl Sync for OutBuffer {} + +pub fn out_buffer_as_ptr( + scope: &mut v8::HandleScope, + out_buffer: Option<v8::Local<v8::TypedArray>>, +) -> Option<OutBuffer> { + match out_buffer { + Some(out_buffer) => { + let ab = out_buffer.buffer(scope).unwrap(); + let len = ab.byte_length(); + ab.data() + .map(|non_null| OutBuffer(non_null.as_ptr() as *mut u8, len)) + } + None => None, + } +} + /// Intermediate format for easy translation from NativeType + V8 value /// to libffi argument types. #[repr(C)] @@ -34,7 +57,7 @@ pub union NativeValue { } impl NativeValue { - pub unsafe fn as_arg(&self, native_type: NativeType) -> Arg { + pub unsafe fn as_arg(&self, native_type: &NativeType) -> Arg { match native_type { NativeType::Void => unreachable!(), NativeType::Bool => Arg::new(&self.bool_value), @@ -53,6 +76,7 @@ impl NativeValue { NativeType::Pointer | NativeType::Buffer | NativeType::Function => { Arg::new(&self.pointer) } + NativeType::Struct(_) => Arg::new(&*self.pointer), } } @@ -76,6 +100,10 @@ impl NativeValue { NativeType::Pointer | NativeType::Function | NativeType::Buffer => { Value::from(self.pointer as usize) } + NativeType::Struct(_) => { + // Return value is written to out_buffer + Value::Null + } } } @@ -187,6 +215,10 @@ impl NativeValue { }; local_value.into() } + NativeType::Struct(_) => { + let local_value: v8::Local<v8::Value> = v8::null(scope).into(); + local_value.into() + } } } } @@ -427,6 +459,48 @@ pub fn ffi_parse_buffer_arg( } #[inline] +pub fn ffi_parse_struct_arg( + scope: &mut v8::HandleScope, + arg: v8::Local<v8::Value>, +) -> Result<NativeValue, AnyError> { + // Order of checking: + // 1. ArrayBuffer: Fairly common and not supported by Fast API, optimise this case. + // 2. ArrayBufferView: Common and supported by Fast API + + let pointer = if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(arg) { + if let Some(non_null) = value.data() { + non_null.as_ptr() + } else { + return Err(type_error( + "Invalid FFI ArrayBuffer, expected data in buffer", + )); + } + } else if let Ok(value) = v8::Local::<v8::ArrayBufferView>::try_from(arg) { + let byte_offset = value.byte_offset(); + let pointer = value + .buffer(scope) + .ok_or_else(|| { + type_error("Invalid FFI ArrayBufferView, expected data in the buffer") + })? + .data(); + if let Some(non_null) = pointer { + // SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset + // is within the buffer backing store. + unsafe { non_null.as_ptr().add(byte_offset) } + } else { + return Err(type_error( + "Invalid FFI ArrayBufferView, expected data in buffer", + )); + } + } else { + return Err(type_error( + "Invalid FFI struct type, expected ArrayBuffer, or ArrayBufferView", + )); + }; + Ok(NativeValue { pointer }) +} + +#[inline] pub fn ffi_parse_function_arg( scope: &mut v8::HandleScope, arg: v8::Local<v8::Value>, @@ -511,6 +585,9 @@ where NativeType::Buffer => { ffi_args.push(ffi_parse_buffer_arg(scope, value)?); } + NativeType::Struct(_) => { + ffi_args.push(ffi_parse_struct_arg(scope, value)?); + } NativeType::Pointer => { ffi_args.push(ffi_parse_pointer_arg(scope, value)?); } diff --git a/ext/ffi/static.rs b/ext/ffi/static.rs index 87c09dbfb..9ea0d616d 100644 --- a/ext/ffi/static.rs +++ b/ext/ffi/static.rs @@ -142,5 +142,8 @@ pub fn op_ffi_get_static<'scope>( }; integer.into() } + NativeType::Struct(_) => { + return Err(type_error("Invalid FFI static type 'struct'")); + } }) } diff --git a/ext/ffi/symbol.rs b/ext/ffi/symbol.rs index 39466560b..bccef79b1 100644 --- a/ext/ffi/symbol.rs +++ b/ext/ffi/symbol.rs @@ -2,7 +2,7 @@ /// Defines the accepted types that can be used as /// parameters and return values in FFI. -#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub enum NativeType { Void, @@ -22,6 +22,7 @@ pub enum NativeType { Pointer, Buffer, Function, + Struct(Box<[NativeType]>), } impl From<NativeType> for libffi::middle::Type { @@ -43,6 +44,9 @@ impl From<NativeType> for libffi::middle::Type { NativeType::Pointer | NativeType::Buffer | NativeType::Function => { libffi::middle::Type::pointer() } + NativeType::Struct(fields) => libffi::middle::Type::structure( + fields.iter().map(|field| field.clone().into()), + ), } } } diff --git a/ext/ffi/turbocall.rs b/ext/ffi/turbocall.rs index e8858df86..079743c16 100644 --- a/ext/ffi/turbocall.rs +++ b/ext/ffi/turbocall.rs @@ -14,11 +14,17 @@ use crate::NativeType; use crate::Symbol; pub(crate) fn is_compatible(sym: &Symbol) -> bool { + // TODO: Support structs by value in fast call cfg!(any( all(target_arch = "x86_64", target_family = "unix"), all(target_arch = "x86_64", target_family = "windows"), all(target_arch = "aarch64", target_vendor = "apple") )) && !sym.can_callback + && !matches!(sym.result_type, NativeType::Struct(_)) + && !sym + .parameter_types + .iter() + .any(|t| matches!(t, NativeType::Struct(_))) } pub(crate) fn compile_trampoline(sym: &Symbol) -> Trampoline { @@ -39,7 +45,7 @@ pub(crate) fn make_template(sym: &Symbol, trampoline: &Trampoline) -> Template { .chain(sym.parameter_types.iter().map(|t| t.into())) .collect::<Vec<_>>(); - let ret = if needs_unwrap(sym.result_type) { + let ret = if needs_unwrap(&sym.result_type) { params.push(fast_api::Type::TypedArray(fast_api::CType::Int32)); fast_api::Type::Void } else { @@ -104,6 +110,9 @@ impl From<&NativeType> for fast_api::Type { fast_api::Type::Uint64 } NativeType::Buffer => fast_api::Type::TypedArray(fast_api::CType::Uint8), + NativeType::Struct(_) => { + fast_api::Type::TypedArray(fast_api::CType::Uint8) + } } } } @@ -161,9 +170,9 @@ impl SysVAmd64 { let mut compiler = Self::new(); let must_cast_return_value = - compiler.must_cast_return_value(sym.result_type); + compiler.must_cast_return_value(&sym.result_type); let must_wrap_return_value = - compiler.must_wrap_return_value_in_typed_array(sym.result_type); + compiler.must_wrap_return_value_in_typed_array(&sym.result_type); let must_save_preserved_register = must_wrap_return_value; let cannot_tailcall = must_cast_return_value || must_wrap_return_value; @@ -174,7 +183,7 @@ impl SysVAmd64 { compiler.allocate_stack(&sym.parameter_types); } - for param in sym.parameter_types.iter().copied() { + for param in sym.parameter_types.iter().cloned() { compiler.move_left(param) } if !compiler.is_recv_arg_overridden() { @@ -188,7 +197,7 @@ impl SysVAmd64 { if cannot_tailcall { compiler.call(sym.ptr.as_ptr()); if must_cast_return_value { - compiler.cast_return_value(sym.result_type); + compiler.cast_return_value(&sym.result_type); } if must_wrap_return_value { compiler.wrap_return_value_in_out_array(); @@ -400,7 +409,7 @@ impl SysVAmd64 { ); } - fn cast_return_value(&mut self, rv: NativeType) { + fn cast_return_value(&mut self, rv: &NativeType) { let s = &mut self.assmblr; // V8 only supports 32bit integers. We support 8 and 16 bit integers casting them to 32bits. // In SysV-AMD64 the convention dictates that the unused bits of the return value contain garbage, so we @@ -550,7 +559,7 @@ impl SysVAmd64 { self.integral_params > 0 } - fn must_cast_return_value(&self, rv: NativeType) -> bool { + fn must_cast_return_value(&self, rv: &NativeType) -> bool { // V8 only supports i32 and u32 return types for integers // We support 8 and 16 bit integers by extending them to 32 bits in the trampoline before returning matches!( @@ -559,7 +568,7 @@ impl SysVAmd64 { ) } - fn must_wrap_return_value_in_typed_array(&self, rv: NativeType) -> bool { + fn must_wrap_return_value_in_typed_array(&self, rv: &NativeType) -> bool { // V8 only supports i32 and u32 return types for integers // We support 64 bit integers by wrapping them in a TypedArray out parameter crate::dlfcn::needs_unwrap(rv) @@ -607,7 +616,7 @@ impl Aarch64Apple { let mut compiler = Self::new(); let must_wrap_return_value = - compiler.must_wrap_return_value_in_typed_array(sym.result_type); + compiler.must_wrap_return_value_in_typed_array(&sym.result_type); let must_save_preserved_register = must_wrap_return_value; let cannot_tailcall = must_wrap_return_value; @@ -619,14 +628,14 @@ impl Aarch64Apple { } } - for param in sym.parameter_types.iter().copied() { + for param in sym.parameter_types.iter().cloned() { compiler.move_left(param) } if !compiler.is_recv_arg_overridden() { // the receiver object should never be expected. Avoid its unexpected or deliberate leak compiler.zero_first_arg(); } - if compiler.must_wrap_return_value_in_typed_array(sym.result_type) { + if compiler.must_wrap_return_value_in_typed_array(&sym.result_type) { compiler.save_out_array_to_preserved_register(); } @@ -963,7 +972,7 @@ impl Aarch64Apple { let mut int_params = 0u32; let mut float_params = 0u32; let mut stack_size = 0u32; - for param in symbol.parameter_types.iter().copied() { + for param in symbol.parameter_types.iter().cloned() { match param.into() { Float(float_param) => { float_params += 1; @@ -1069,10 +1078,10 @@ impl Aarch64Apple { } fn must_save_preserved_register_to_stack(&mut self, symbol: &Symbol) -> bool { - self.must_wrap_return_value_in_typed_array(symbol.result_type) + self.must_wrap_return_value_in_typed_array(&symbol.result_type) } - fn must_wrap_return_value_in_typed_array(&self, rv: NativeType) -> bool { + fn must_wrap_return_value_in_typed_array(&self, rv: &NativeType) -> bool { // V8 only supports i32 and u32 return types for integers // We support 64 bit integers by wrapping them in a TypedArray out parameter crate::dlfcn::needs_unwrap(rv) @@ -1120,9 +1129,9 @@ impl Win64 { let mut compiler = Self::new(); let must_cast_return_value = - compiler.must_cast_return_value(sym.result_type); + compiler.must_cast_return_value(&sym.result_type); let must_wrap_return_value = - compiler.must_wrap_return_value_in_typed_array(sym.result_type); + compiler.must_wrap_return_value_in_typed_array(&sym.result_type); let must_save_preserved_register = must_wrap_return_value; let cannot_tailcall = must_cast_return_value || must_wrap_return_value; @@ -1133,7 +1142,7 @@ impl Win64 { compiler.allocate_stack(&sym.parameter_types); } - for param in sym.parameter_types.iter().copied() { + for param in sym.parameter_types.iter().cloned() { compiler.move_left(param) } if !compiler.is_recv_arg_overridden() { @@ -1147,7 +1156,7 @@ impl Win64 { if cannot_tailcall { compiler.call(sym.ptr.as_ptr()); if must_cast_return_value { - compiler.cast_return_value(sym.result_type); + compiler.cast_return_value(&sym.result_type); } if must_wrap_return_value { compiler.wrap_return_value_in_out_array(); @@ -1284,7 +1293,7 @@ impl Win64 { x64!(self.assmblr; xor ecx, ecx); } - fn cast_return_value(&mut self, rv: NativeType) { + fn cast_return_value(&mut self, rv: &NativeType) { let s = &mut self.assmblr; // V8 only supports 32bit integers. We support 8 and 16 bit integers casting them to 32bits. // Section "Return Values" of the Windows x64 Calling Convention doc: @@ -1419,7 +1428,7 @@ impl Win64 { self.params > 0 } - fn must_cast_return_value(&self, rv: NativeType) -> bool { + fn must_cast_return_value(&self, rv: &NativeType) -> bool { // V8 only supports i32 and u32 return types for integers // We support 8 and 16 bit integers by extending them to 32 bits in the trampoline before returning matches!( @@ -1428,7 +1437,7 @@ impl Win64 { ) } - fn must_wrap_return_value_in_typed_array(&self, rv: NativeType) -> bool { + fn must_wrap_return_value_in_typed_array(&self, rv: &NativeType) -> bool { // V8 only supports i32 and u32 return types for integers // We support 64 bit integers by wrapping them in a TypedArray out parameter crate::dlfcn::needs_unwrap(rv) @@ -1510,6 +1519,7 @@ impl From<NativeType> for Param { NativeType::I32 => Int(I(DW)), NativeType::I64 | NativeType::ISize => Int(I(QW)), NativeType::Buffer => Int(Buffer), + NativeType::Struct(_) => unimplemented!(), } } } |