diff --git a/.gitignore b/.gitignore index ba748c2..b7719c3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ scripts/physical_properties_enum_map.rs # Files generated by tests /tests/ffi/**/*.so + +# Core dump file + +/core diff --git a/crates/lune-std-ffi/src/data/callable_data.rs b/crates/lune-std-ffi/src/data/callable_data.rs index 2421143..f94d420 100644 --- a/crates/lune-std-ffi/src/data/callable_data.rs +++ b/crates/lune-std-ffi/src/data/callable_data.rs @@ -1,5 +1,8 @@ use core::ffi::c_void; -use std::ptr; +use std::{ + mem::{self, MaybeUninit}, + ptr, +}; use libffi::{ low::{ffi_cif, CodePtr}, @@ -17,6 +20,95 @@ pub struct CallableData { code: CodePtr, } +const VOID_RESULT_PTR: *mut () = ptr::null_mut(); +const ZERO_SIZE_ARG_PTR: *mut *mut c_void = ptr::null_mut(); + +macro_rules! create_caller { + ($len:expr) => { + |callable: &CallableData, result: LuaValue, args: LuaMultiValue| unsafe { + let mut arg_list: [MaybeUninit<*mut c_void>; $len] = [MaybeUninit::uninit(); $len]; + + let result_pointer = if callable.result_info.size == 0 { + VOID_RESULT_PTR + } else { + let result_data = result.get_ffi_data()?; + if !result_data.check_inner_boundary(0, callable.result_info.size) { + return Err(LuaError::external("Result boundary check failed")); + } + result_data.get_inner_pointer() + } + .cast::(); + + for (index, arg) in arg_list.iter_mut().enumerate() { + let arg_value = args + .get(index) + .ok_or_else(|| LuaError::external(format!("argument {index} required")))? + .as_userdata() + .ok_or_else(|| LuaError::external("argument should be Ref"))?; + + let arg_ref = arg_value.borrow::()?; + + arg.write(arg_ref.get_inner_pointer().cast::()); + } + + ffi_call( + callable.cif, + Some(*callable.code.as_safe_fun()), + result_pointer, + // SAFETY: MaybeUninit has the same layout as `T`, and initialized above + mem::transmute::<[MaybeUninit<*mut c_void>; $len], [*mut c_void; $len]>(arg_list) + .as_mut_ptr(), + ); + + Ok(()) + } + }; +} + +unsafe fn zero_size_caller( + callable: &CallableData, + result: LuaValue, + _args: LuaMultiValue, +) -> LuaResult<()> { + let result_pointer = if callable.result_info.size == 0 { + VOID_RESULT_PTR + } else { + let result_data = result.get_ffi_data()?; + if !result_data.check_inner_boundary(0, callable.result_info.size) { + return Err(LuaError::external("Result boundary check failed")); + } + result_data.get_inner_pointer() + } + .cast::(); + + ffi_call( + callable.cif, + Some(*callable.code.as_safe_fun()), + result_pointer, + ZERO_SIZE_ARG_PTR, + ); + + Ok(()) +} + +type Caller = + unsafe fn(callable: &CallableData, result: LuaValue, args: LuaMultiValue) -> LuaResult<()>; +const SIZED_CALLERS: [Caller; 13] = [ + zero_size_caller, + create_caller!(1), + create_caller!(2), + create_caller!(3), + create_caller!(4), + create_caller!(5), + create_caller!(6), + create_caller!(7), + create_caller!(8), + create_caller!(9), + create_caller!(10), + create_caller!(11), + create_caller!(12), +]; + impl CallableData { pub unsafe fn new( cif: *mut ffi_cif, @@ -33,10 +125,15 @@ impl CallableData { } pub unsafe fn call(&self, result: LuaValue, args: LuaMultiValue) -> LuaResult<()> { - let mut arg_list = Vec::<*mut c_void>::with_capacity(self.arg_info_list.len()); + let arg_len = self.arg_info_list.len(); + if arg_len < SIZED_CALLERS.len() { + return SIZED_CALLERS[arg_len](self, result, args); + } + + let mut arg_list = Vec::<*mut c_void>::with_capacity(arg_len); let result_pointer = if self.result_info.size == 0 { - ptr::null_mut() + VOID_RESULT_PTR } else { let result_data = result.get_ffi_data()?; if !result_data.check_inner_boundary(0, self.result_info.size) {