use std::alloc::{self, Layout};
use std::collections::HashMap;
use std::error::Error;
-use std::ffi::{c_void, CString};
+use std::ffi::{c_char, c_void, CStr, CString};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::ops::Deref;
#[derive(Debug)]
pub struct FunctionImpl {
cif: Cif,
- args: Vec<Type>,
+ args: Vec<FfiType>,
code_ptr: CodePtr,
- return_struct_name: Option<String>,
+ return_type: FfiType,
}
-#[derive(Debug, Default)]
-pub struct ForeignFunctionTable {
- table: HashMap<String, FunctionImpl>,
- structs: HashMap<String, StructImpl>,
-}
-
-#[derive(Clone, Debug)]
-struct StructImpl {
- ffi_type: Type,
- fields: Vec<Type>,
- atom_fields: Vec<Atom>,
-}
-
-struct PointerArgs<'a, 'val> {
- memory: Vec<Arg>,
- phantom: PhantomData<&'a mut ArgValue<'val>>,
-}
-
-impl Deref for PointerArgs<'_, '_> {
- type Target = [Arg];
-
- fn deref(&self) -> &Self::Target {
- &self.memory
+impl FunctionImpl {
+ unsafe fn call_void(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError> {
+ self.cif.call::<()>(self.code_ptr, args);
+ Ok(Value::Number(Number::Fixnum(Fixnum::build_with(0))))
}
-}
-
-enum ArgValue<'a> {
- U8(u8),
- I8(i8),
- U16(u16),
- I16(i16),
- U32(u32),
- I32(i32),
- U64(u64),
- I64(i64),
- F32(f32),
- F64(f64),
- Ptr(*mut c_void, PhantomData<&'a CString>),
- Struct(FfiStruct),
-}
-impl<'val> ArgValue<'val> {
- fn new(
- val: &'val mut Value,
- arg_type: &Type,
- structs_table: &HashMap<String, StructImpl>,
- ) -> Result<Self, FfiError> {
- match (unsafe { *arg_type.as_raw_ptr() }).type_ as u32 {
- libffi::raw::FFI_TYPE_UINT8 => Ok(Self::U8(val.as_int()?)),
- libffi::raw::FFI_TYPE_SINT8 => Ok(Self::I8(val.as_int()?)),
- libffi::raw::FFI_TYPE_UINT16 => Ok(Self::U16(val.as_int()?)),
- libffi::raw::FFI_TYPE_SINT16 => Ok(Self::I16(val.as_int()?)),
- libffi::raw::FFI_TYPE_UINT32 => Ok(Self::U32(val.as_int()?)),
- libffi::raw::FFI_TYPE_SINT32 => Ok(Self::I32(val.as_int()?)),
- libffi::raw::FFI_TYPE_UINT64 => Ok(Self::U64(val.as_int()?)),
- libffi::raw::FFI_TYPE_SINT64 => Ok(Self::I64(val.as_int()?)),
- libffi::raw::FFI_TYPE_FLOAT => Ok(Self::F32(val.as_float()? as f32)),
- libffi::raw::FFI_TYPE_DOUBLE => Ok(Self::F64(val.as_float()?)),
- libffi::raw::FFI_TYPE_POINTER => Ok(Self::Ptr(val.as_ptr()?, PhantomData)),
- libffi::raw::FFI_TYPE_STRUCT => Ok(Self::Struct(ForeignFunctionTable::build_struct(
- val,
- structs_table,
- )?)),
- _ => Err(FfiError::InvalidFfiType),
- }
+ unsafe fn call_int<T>(&self, args: &[Arg], arena: &mut Arena) -> Result<Value, FfiError>
+ where
+ Integer: From<T>,
+ T: Copy + TryInto<i64> + MightNotFitInFixnum,
+ {
+ let n = self.cif.call::<T>(self.code_ptr, args);
+ Ok(Value::Number(fixnum!(Number, n, arena)))
}
- fn build_args(
- args: &'val mut [Value],
- types: &[Type],
- structs_table: &HashMap<String, StructImpl>,
- ) -> Result<Vec<Self>, FfiError> {
- if types.len() != args.len() {
- return Err(FfiError::ArgCountMismatch);
- }
-
- args.iter_mut()
- .zip(types)
- .map(|(arg, arg_type)| ArgValue::new(arg, arg_type, structs_table))
- .collect::<Result<Vec<_>, _>>()
+ unsafe fn call_float<T>(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError>
+ where
+ T: Into<f64>,
+ {
+ let n = self.cif.call::<T>(self.code_ptr, args);
+ Ok(Value::Number(Number::Float(OrderedFloat(n.into()))))
}
-}
-struct FfiStruct {
- ptr: NonNull<c_void>,
- layout: Layout,
-}
-
-impl FfiStruct {
- fn new(layout: Layout) -> Result<Self, FfiError> {
- if let Some(ptr) = NonNull::new(unsafe { alloc::alloc(layout) as *mut c_void }) {
- Ok(FfiStruct { ptr, layout })
- } else {
- Err(FfiError::AllocationFailed)
- }
+ unsafe fn call_ptr(&self, args: &[Arg], arena: &mut Arena) -> Result<Value, FfiError> {
+ let ptr = unsafe { self.cif.call::<*mut c_void>(self.code_ptr, args) };
+ Ok(Value::Number(fixnum!(Number, ptr as isize, arena)))
}
-}
-impl Drop for FfiStruct {
- fn drop(&mut self) {
- unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) };
+ unsafe fn call_cstr(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError> {
+ let ptr = unsafe { self.cif.call::<*mut c_char>(self.code_ptr, args) };
+ Ok(Value::CString(unsafe { CStr::from_ptr(ptr) }.to_owned()))
}
-}
-impl ForeignFunctionTable {
- pub fn merge(&mut self, other: ForeignFunctionTable) {
- self.table.extend(other.table);
- }
+ unsafe fn call_struct(
+ &self,
+ return_type_name: Atom,
+ args: &[Arg],
+ arena: &mut Arena,
+ structs_table: &HashMap<String, StructImpl>,
+ ) -> Result<Value, FfiError> {
+ let struct_type = structs_table
+ .get(&*return_type_name.as_str())
+ .ok_or(FfiError::StructNotFound)?;
+ let ffi_type = unsafe { *struct_type.ffi_type.as_raw_ptr() };
- pub fn define_struct(&mut self, name: &str, atom_fields: Vec<Atom>) -> Result<(), FfiError> {
- let fields: Vec<_> = atom_fields
- .iter()
- .map(|x| self.map_type_ffi(x))
- .collect::<Result<_, _>>()?;
- let struct_type = libffi::middle::Type::structure(fields.iter().cloned());
+ let layout = Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
+ .map_err(|_| FfiError::LayoutError)?;
+
+ let alloc = FfiStruct::new(layout)?;
unsafe {
- // ensure that size and alignment of struct_type are set properly
- use libffi::low::{ffi_abi_FFI_DEFAULT_ABI, prep_cif};
- prep_cif(
- &mut Default::default(),
- ffi_abi_FFI_DEFAULT_ABI,
- 1,
- struct_type.as_raw_ptr(),
- [struct_type.as_raw_ptr()].as_mut_ptr(),
- )?;
+ libffi::raw::ffi_call(
+ self.cif.as_raw_ptr(),
+ Some(*self.code_ptr.as_safe_fun()),
+ alloc.ptr.as_ptr(),
+ args.as_ptr() as *mut *mut c_void,
+ )
};
- self.structs.insert(
- name.to_string(),
- StructImpl {
- ffi_type: struct_type,
- fields,
- atom_fields,
- },
+ let struct_val = struct_type.read(
+ alloc.ptr.as_ptr(),
+ &return_type_name.as_str(),
+ structs_table,
+ arena,
);
- Ok(())
- }
- fn map_type_ffi(&mut self, source: &Atom) -> Result<libffi::middle::Type, FfiError> {
- Ok(match source {
- atom!("sint64") | atom!("i64") => libffi::middle::Type::i64(),
- atom!("sint32") | atom!("i32") => libffi::middle::Type::i32(),
- atom!("sint16") | atom!("i16") => libffi::middle::Type::i16(),
- atom!("sint8") | atom!("i8") => libffi::middle::Type::i8(),
- atom!("uint64") | atom!("u64") => libffi::middle::Type::u64(),
- atom!("uint32") | atom!("u32") => libffi::middle::Type::u32(),
- atom!("uint16") | atom!("u16") => libffi::middle::Type::u16(),
- atom!("uint8") | atom!("u8") => libffi::middle::Type::u8(),
- atom!("bool") => libffi::middle::Type::i8(),
- atom!("void") => libffi::middle::Type::void(),
- atom!("cstr") => libffi::middle::Type::pointer(),
- atom!("ptr") => libffi::middle::Type::pointer(),
- atom!("f32") => libffi::middle::Type::f32(),
- atom!("f64") => libffi::middle::Type::f64(),
- struct_name => match self.structs.get_mut(&*struct_name.as_str()) {
- Some(ref mut struct_type) => struct_type.ffi_type.clone(),
- None => return Err(FfiError::InvalidFfiType),
- },
- })
+ drop(alloc);
+
+ struct_val
}
- pub(crate) fn load_library(
- &mut self,
- library_name: &str,
- functions: &Vec<FunctionDefinition>,
- ) -> Result<(), Box<dyn Error>> {
- let mut ff_table: ForeignFunctionTable = Default::default();
- unsafe {
- let library = Library::new(library_name)?;
- for function in functions {
- let symbol_name: CString = CString::new(function.name.clone())?;
- let code_ptr: Symbol<*mut c_void> = library.get(symbol_name.as_bytes_with_nul())?;
- let args: Vec<_> = function
- .args
- .iter()
- .map(|x| self.map_type_ffi(x))
- .collect::<Result<_, _>>()?;
- let result = self.map_type_ffi(&function.return_value)?;
-
- let cif = libffi::middle::Cif::new(args.iter().cloned(), result.clone());
-
- let return_struct_name =
- if (*result.as_raw_ptr()).type_ as u32 == libffi::raw::FFI_TYPE_STRUCT {
- Some(function.return_value.as_str().to_string())
- } else {
- None
- };
-
- ff_table.table.insert(
- function.name.clone(),
- FunctionImpl {
- cif,
- args,
- code_ptr: CodePtr(code_ptr.into_raw().as_raw_ptr()),
- return_struct_name,
- },
- );
- }
- std::mem::forget(library);
- }
- self.merge(ff_table);
- Ok(())
+ fn call(
+ &self,
+ args: &[Arg],
+ arena: &mut Arena,
+ structs_table: &HashMap<String, StructImpl>,
+ ) -> Result<Value, FfiError> {
+ let call_fn: unsafe fn(&Self, &[Arg], &mut Arena) -> Result<Value, FfiError> =
+ match self.return_type {
+ FfiType::Void => FunctionImpl::call_void,
+ FfiType::U8 => FunctionImpl::call_int::<u8>,
+ FfiType::I8 | FfiType::Bool => FunctionImpl::call_int::<i8>,
+ FfiType::U16 => FunctionImpl::call_int::<u16>,
+ FfiType::I16 => FunctionImpl::call_int::<i16>,
+ FfiType::U32 => FunctionImpl::call_int::<u32>,
+ FfiType::I32 => FunctionImpl::call_int::<i32>,
+ FfiType::U64 => FunctionImpl::call_int::<u64>,
+ FfiType::I64 => FunctionImpl::call_int::<i64>,
+ FfiType::F32 => FunctionImpl::call_float::<f32>,
+ FfiType::F64 => FunctionImpl::call_float::<f64>,
+ FfiType::Ptr => FunctionImpl::call_ptr,
+ FfiType::CStr => FunctionImpl::call_cstr,
+ FfiType::Struct(name) => {
+ return unsafe { self.call_struct(name, args, arena, structs_table) }
+ }
+ };
+ unsafe { call_fn(self, args, arena) }
}
+}
- fn build_pointer_args<'args, 'val>(args: &[ArgValue<'val>]) -> PointerArgs<'args, 'val> {
- let args = args
- .iter()
- .map(|arg| match arg {
- ArgValue::U8(a) => libffi::middle::arg(a),
- ArgValue::I8(a) => libffi::middle::arg(a),
- ArgValue::U16(a) => libffi::middle::arg(a),
- ArgValue::I16(a) => libffi::middle::arg(a),
- ArgValue::U32(a) => libffi::middle::arg(a),
- ArgValue::I32(a) => libffi::middle::arg(a),
- ArgValue::U64(a) => libffi::middle::arg(a),
- ArgValue::I64(a) => libffi::middle::arg(a),
- ArgValue::F32(a) => libffi::middle::arg(a),
- ArgValue::F64(a) => libffi::middle::arg(a),
- ArgValue::Ptr(ptr, _) => unsafe { std::mem::transmute::<*mut c_void, Arg>(*ptr) },
- ArgValue::Struct(s) => unsafe {
- std::mem::transmute::<*mut c_void, Arg>(s.ptr.as_ptr())
- },
- })
- .collect();
+#[derive(Debug, Default)]
+pub struct ForeignFunctionTable {
+ table: HashMap<String, FunctionImpl>,
+ structs: HashMap<String, StructImpl>,
+}
- PointerArgs {
- memory: args,
- phantom: PhantomData,
- }
- }
+#[derive(Clone, Debug)]
+struct StructImpl {
+ ffi_type: Type,
+ fields: Vec<FfiType>,
+}
- fn build_struct(
- arg: &mut Value,
+impl StructImpl {
+ fn build(
+ &self,
structs_table: &HashMap<String, StructImpl>,
+ struct_args: &mut [Value],
) -> Result<FfiStruct, FfiError> {
- let Value::Struct(ref name, ref mut struct_args) = arg else {
- return Err(FfiError::ValueCast);
- };
+ let args = ArgValue::build_args(struct_args, &self.fields, structs_table)?;
- let Some(struct_type) = structs_table.get(name) else {
- return Err(FfiError::InvalidStructName);
- };
-
- let args = ArgValue::build_args(struct_args, &struct_type.fields, structs_table)?;
-
- let ffi_type = unsafe { *struct_type.ffi_type.as_raw_ptr() };
+ let ffi_type = unsafe { *self.ffi_type.as_raw_ptr() };
let alloc = FfiStruct::new(
Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
Ok(alloc)
}
- pub fn exec(
- &mut self,
- name: &str,
- mut args: Vec<Value>,
- arena: &mut Arena,
- ) -> Result<Value, FfiError> {
- let fn_impl = self.table.get(name).ok_or(FfiError::FunctionNotFound)?;
-
- let args = ArgValue::build_args(&mut args, &fn_impl.args, &self.structs)?;
-
- let args = Self::build_pointer_args(&args);
-
- unsafe fn call_int<T>(
- fn_impl: &FunctionImpl,
- args: &PointerArgs,
- arena: &mut Arena,
- ) -> Result<Value, FfiError>
- where
- Integer: From<T>,
- T: Copy + TryInto<i64> + MightNotFitInFixnum,
- {
- let n = fn_impl.cif.call::<T>(fn_impl.code_ptr, args);
- Ok(Value::Number(fixnum!(Number, n, arena)))
- }
-
- unsafe fn call_float<T>(
- fn_impl: &FunctionImpl,
- args: &PointerArgs,
- ) -> Result<Value, FfiError>
- where
- T: Into<f64>,
- {
- let n = fn_impl.cif.call::<T>(fn_impl.code_ptr, args);
- Ok(Value::Number(Number::Float(OrderedFloat(n.into()))))
- }
-
- let ffi_rtype = unsafe { *(*fn_impl.cif.as_raw_ptr()).rtype };
-
- match ffi_rtype.type_ as u32 {
- libffi::raw::FFI_TYPE_VOID => {
- unsafe { fn_impl.cif.call::<c_void>(fn_impl.code_ptr, &args) };
- Ok(Value::Number(Number::Fixnum(Fixnum::build_with(0))))
- }
- libffi::raw::FFI_TYPE_UINT8 => unsafe { call_int::<u8>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_SINT8 => unsafe { call_int::<i8>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_UINT16 => unsafe { call_int::<u16>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_SINT16 => unsafe { call_int::<i16>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_UINT32 => unsafe { call_int::<u32>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_SINT32 => unsafe { call_int::<i32>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_UINT64 => unsafe { call_int::<u64>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_SINT64 => unsafe { call_int::<i64>(fn_impl, &args, arena) },
- libffi::raw::FFI_TYPE_POINTER => {
- let ptr = unsafe { fn_impl.cif.call::<*mut c_void>(fn_impl.code_ptr, &args) };
- Ok(Value::Number(fixnum!(Number, ptr as isize, arena)))
- }
- libffi::raw::FFI_TYPE_FLOAT => unsafe { call_float::<f32>(fn_impl, &args) },
- libffi::raw::FFI_TYPE_DOUBLE => unsafe { call_float::<f64>(fn_impl, &args) },
- libffi::raw::FFI_TYPE_STRUCT => {
- let name = fn_impl
- .return_struct_name
- .as_ref()
- .ok_or(FfiError::StructNotFound)?;
- let struct_type = self.structs.get(name).ok_or(FfiError::InvalidStructName)?;
- let ffi_type = unsafe { *struct_type.ffi_type.as_raw_ptr() };
-
- let layout = Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
- .map_err(|_| FfiError::LayoutError)?;
-
- let alloc = FfiStruct::new(layout)?;
-
- let ptr_args: &[Arg] = &args;
-
- unsafe {
- libffi::raw::ffi_call(
- fn_impl.cif.as_raw_ptr(),
- Some(*fn_impl.code_ptr.as_safe_fun()),
- alloc.ptr.as_ptr(),
- ptr_args.as_ptr() as *mut *mut c_void,
- )
- };
- let struct_val = self.read_struct(alloc.ptr.as_ptr(), name, struct_type, arena);
-
- drop(alloc);
-
- struct_val
- }
- _ => unreachable!(),
- }
- }
-
- fn read_struct(
+ fn read(
&self,
ptr: *mut c_void,
- name: &str,
- struct_type: &StructImpl,
+ struct_name: &str,
+ struct_table: &HashMap<String, StructImpl>,
arena: &mut Arena,
) -> Result<Value, FfiError> {
unsafe {
let mut layout = Layout::from_size_align(0, 1).map_err(|_| FfiError::LayoutError)?;
- for (field, type_name) in struct_type.fields.iter().zip(&struct_type.atom_fields) {
- let val = match (*field.as_raw_ptr()).type_ as u32 {
- libffi::raw::FFI_TYPE_UINT8 => read_int::<u8>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_SINT8 => read_int::<i8>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_UINT16 => read_int::<u16>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_SINT16 => read_int::<i16>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_UINT32 => read_int::<u32>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_SINT32 => read_int::<i32>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_UINT64 => read_int::<u64>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_SINT64 => read_int::<i64>(ptr, &mut layout, arena),
- libffi::raw::FFI_TYPE_POINTER => {
+ for field_type in &self.fields {
+ let val = match field_type {
+ FfiType::U8 => read_int::<u8>(ptr, &mut layout, arena),
+ FfiType::I8 | FfiType::Bool => read_int::<i8>(ptr, &mut layout, arena),
+ FfiType::U16 => read_int::<u16>(ptr, &mut layout, arena),
+ FfiType::I16 => read_int::<i16>(ptr, &mut layout, arena),
+ FfiType::U32 => read_int::<u32>(ptr, &mut layout, arena),
+ FfiType::I32 => read_int::<i32>(ptr, &mut layout, arena),
+ FfiType::U64 => read_int::<u64>(ptr, &mut layout, arena),
+ FfiType::I64 => read_int::<i64>(ptr, &mut layout, arena),
+ FfiType::Ptr => {
let ptr = read_primitive::<*mut c_void>(ptr, &mut layout)?;
Ok(Value::Number(fixnum!(Number, ptr as isize, arena)))
}
- libffi::raw::FFI_TYPE_FLOAT => read_float::<f32>(ptr, &mut layout),
- libffi::raw::FFI_TYPE_DOUBLE => read_float::<f64>(ptr, &mut layout),
- libffi::raw::FFI_TYPE_STRUCT => {
- let substruct = type_name.as_str();
-
- let Some(struct_type) = self.structs.get(&*substruct) else {
- return Err(FfiError::InvalidStructName);
+ FfiType::CStr => {
+ let ptr = read_primitive::<*mut c_void>(ptr, &mut layout)?;
+ Ok(Value::CString(CStr::from_ptr(ptr.cast()).to_owned()))
+ }
+ FfiType::F32 => read_float::<f32>(ptr, &mut layout),
+ FfiType::F64 => read_float::<f64>(ptr, &mut layout),
+ FfiType::Struct(substruct) => {
+ let Some(substruct_type) = struct_table.get(&*substruct.as_str()) else {
+ return Err(FfiError::StructNotFound);
};
- let ffi_type = *struct_type.ffi_type.as_raw_ptr();
+ let ffi_type = *substruct_type.ffi_type.as_raw_ptr();
let field_layout =
Layout::from_size_align(ffi_type.size, ffi_type.alignment as usize)
.map_err(|_| FfiError::LayoutError)?;
.map_err(|_| FfiError::LayoutError)?;
layout = new_layout;
let field_ptr = ptr.byte_offset(offset as isize);
- let struct_val =
- self.read_struct(field_ptr, &substruct, struct_type, arena)?;
+ let struct_val = substruct_type.read(
+ field_ptr,
+ &substruct.as_str(),
+ struct_table,
+ arena,
+ )?;
Ok(struct_val)
}
- _ => {
- unreachable!()
- }
+ FfiType::Void => unreachable!("void is not a valid field type"),
};
returns.push(val?);
}
- Ok(Value::Struct(name.into(), returns))
+ Ok(Value::Struct(struct_name.to_string(), returns))
+ }
+ }
+}
+
+struct PointerArgs<'a, 'val> {
+ memory: Vec<Arg>,
+ phantom: PhantomData<&'a mut ArgValue<'val>>,
+}
+
+impl<'args, 'val> PointerArgs<'args, 'val> {
+ fn new(args: &'args [ArgValue<'val>]) -> Self {
+ let args = args
+ .iter()
+ .map(|arg| match arg {
+ ArgValue::U8(a) => libffi::middle::arg(a),
+ ArgValue::I8(a) => libffi::middle::arg(a),
+ ArgValue::U16(a) => libffi::middle::arg(a),
+ ArgValue::I16(a) => libffi::middle::arg(a),
+ ArgValue::U32(a) => libffi::middle::arg(a),
+ ArgValue::I32(a) => libffi::middle::arg(a),
+ ArgValue::U64(a) => libffi::middle::arg(a),
+ ArgValue::I64(a) => libffi::middle::arg(a),
+ ArgValue::F32(a) => libffi::middle::arg(a),
+ ArgValue::F64(a) => libffi::middle::arg(a),
+ ArgValue::Ptr(ptr, _) => unsafe { std::mem::transmute::<*mut c_void, Arg>(*ptr) },
+ ArgValue::Struct(s) => unsafe {
+ std::mem::transmute::<*mut c_void, Arg>(s.ptr.as_ptr())
+ },
+ })
+ .collect();
+
+ PointerArgs {
+ memory: args,
+ phantom: PhantomData,
+ }
+ }
+}
+
+impl Deref for PointerArgs<'_, '_> {
+ type Target = [Arg];
+
+ fn deref(&self) -> &Self::Target {
+ &self.memory
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum FfiType {
+ Void,
+ Bool,
+ U8,
+ I8,
+ U16,
+ I16,
+ U32,
+ I32,
+ U64,
+ I64,
+ F32,
+ F64,
+ Ptr,
+ CStr,
+ Struct(Atom),
+}
+
+impl FfiType {
+ fn from_atom(atom: &Atom) -> Self {
+ match atom {
+ atom!("sint64") | atom!("i64") => Self::I64,
+ atom!("sint32") | atom!("i32") => Self::I32,
+ atom!("sint16") | atom!("i16") => Self::I16,
+ atom!("sint8") | atom!("i8") => Self::I8,
+ atom!("uint64") | atom!("u64") => Self::U64,
+ atom!("uint32") | atom!("u32") => Self::U32,
+ atom!("uint16") | atom!("u16") => Self::U16,
+ atom!("uint8") | atom!("u8") => Self::U8,
+ atom!("bool") => Self::Bool,
+ atom!("void") => Self::Void,
+ atom!("cstr") => Self::CStr,
+ atom!("ptr") => Self::Ptr,
+ atom!("f32") => Self::F32,
+ atom!("f64") => Self::F64,
+ struct_name => Self::Struct(*struct_name),
}
}
+
+ fn to_type(self, structs_table: &HashMap<String, StructImpl>) -> Result<Type, FfiError> {
+ Ok(match self {
+ Self::I64 => libffi::middle::Type::i64(),
+ Self::I32 => libffi::middle::Type::i32(),
+ Self::I16 => libffi::middle::Type::i16(),
+ Self::I8 => libffi::middle::Type::i8(),
+ Self::U64 => libffi::middle::Type::u64(),
+ Self::U32 => libffi::middle::Type::u32(),
+ Self::U16 => libffi::middle::Type::u16(),
+ Self::U8 => libffi::middle::Type::u8(),
+ Self::Bool => libffi::middle::Type::i8(),
+ Self::Void => libffi::middle::Type::void(),
+ Self::CStr => libffi::middle::Type::pointer(),
+ Self::Ptr => libffi::middle::Type::pointer(),
+ Self::F32 => libffi::middle::Type::f32(),
+ Self::F64 => libffi::middle::Type::f64(),
+ Self::Struct(struct_name) => structs_table
+ .get(&*struct_name.as_str())
+ .ok_or(FfiError::StructNotFound)?
+ .ffi_type
+ .clone(),
+ })
+ }
+}
+
+enum ArgValue<'a> {
+ U8(u8),
+ I8(i8),
+ U16(u16),
+ I16(i16),
+ U32(u32),
+ I32(i32),
+ U64(u64),
+ I64(i64),
+ F32(f32),
+ F64(f64),
+ Ptr(*mut c_void, PhantomData<&'a CString>),
+ Struct(FfiStruct),
+}
+
+impl<'val> ArgValue<'val> {
+ fn new(
+ val: &'val mut Value,
+ arg_type: &FfiType,
+ structs_table: &HashMap<String, StructImpl>,
+ ) -> Result<Self, FfiError> {
+ match arg_type {
+ FfiType::U8 => Ok(Self::U8(val.as_int()?)),
+ FfiType::I8 | FfiType::Bool => Ok(Self::I8(val.as_int()?)),
+ FfiType::U16 => Ok(Self::U16(val.as_int()?)),
+ FfiType::I16 => Ok(Self::I16(val.as_int()?)),
+ FfiType::U32 => Ok(Self::U32(val.as_int()?)),
+ FfiType::I32 => Ok(Self::I32(val.as_int()?)),
+ FfiType::U64 => Ok(Self::U64(val.as_int()?)),
+ FfiType::I64 => Ok(Self::I64(val.as_int()?)),
+ FfiType::F32 => Ok(Self::F32(val.as_float()? as f32)),
+ FfiType::F64 => Ok(Self::F64(val.as_float()?)),
+ FfiType::Ptr => Ok(Self::Ptr(val.as_ptr()?, PhantomData)),
+ FfiType::CStr => Ok(Self::Ptr(val.as_ptr()?, PhantomData)),
+ FfiType::Struct(atom) => {
+ let (name, args) = val.as_struct()?;
+
+ if &*atom.as_str() != name {
+ return Err(FfiError::ValueCast);
+ }
+
+ let Some(struct_type) = structs_table.get(name) else {
+ return Err(FfiError::StructNotFound);
+ };
+
+ Ok(Self::Struct(struct_type.build(structs_table, args)?))
+ }
+ FfiType::Void => Err(FfiError::InvalidArgumentType),
+ }
+ }
+
+ fn build_args(
+ args: &'val mut [Value],
+ types: &[FfiType],
+ structs_table: &HashMap<String, StructImpl>,
+ ) -> Result<Vec<Self>, FfiError> {
+ if types.len() != args.len() {
+ return Err(FfiError::ArgCountMismatch);
+ }
+
+ args.iter_mut()
+ .zip(types)
+ .map(|(arg, arg_type)| ArgValue::new(arg, arg_type, structs_table))
+ .collect::<Result<Vec<_>, _>>()
+ }
+}
+
+struct FfiStruct {
+ ptr: NonNull<c_void>,
+ layout: Layout,
+}
+
+impl FfiStruct {
+ fn new(layout: Layout) -> Result<Self, FfiError> {
+ if let Some(ptr) = NonNull::new(unsafe { alloc::alloc(layout) as *mut c_void }) {
+ Ok(FfiStruct { ptr, layout })
+ } else {
+ Err(FfiError::AllocationFailed)
+ }
+ }
+}
+
+impl Drop for FfiStruct {
+ fn drop(&mut self) {
+ unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) };
+ }
+}
+
+impl ForeignFunctionTable {
+ pub fn merge(&mut self, other: ForeignFunctionTable) {
+ self.table.extend(other.table);
+ }
+
+ pub fn define_struct(&mut self, name: &str, atom_fields: Vec<Atom>) -> Result<(), FfiError> {
+ let fields: Vec<_> = atom_fields.iter().map(FfiType::from_atom).collect();
+ let struct_type = libffi::middle::Type::structure(
+ fields
+ .iter()
+ .map(|field| field.to_type(&self.structs))
+ .collect::<Result<Vec<_>, _>>()?,
+ );
+
+ unsafe {
+ // ensure that size and alignment of struct_type are set properly
+ use libffi::low::{ffi_abi_FFI_DEFAULT_ABI, prep_cif};
+ prep_cif(
+ &mut Default::default(),
+ ffi_abi_FFI_DEFAULT_ABI,
+ 1,
+ struct_type.as_raw_ptr(),
+ [struct_type.as_raw_ptr()].as_mut_ptr(),
+ )?;
+ };
+
+ self.structs.insert(
+ name.to_string(),
+ StructImpl {
+ ffi_type: struct_type,
+ fields,
+ },
+ );
+ Ok(())
+ }
+
+ pub(crate) fn load_library(
+ &mut self,
+ library_name: &str,
+ functions: &Vec<FunctionDefinition>,
+ ) -> Result<(), Box<dyn Error>> {
+ let mut ff_table: ForeignFunctionTable = Default::default();
+ let library = unsafe { Library::new(library_name) }?;
+ for function in functions {
+ let symbol_name: CString = CString::new(function.name.clone())?;
+ let code_ptr: Symbol<*mut c_void> =
+ unsafe { library.get(symbol_name.as_bytes_with_nul()) }?;
+ let args: Vec<_> = function.args.iter().map(FfiType::from_atom).collect();
+ let return_type = FfiType::from_atom(&function.return_value);
+
+ let cif = libffi::middle::Cif::new(
+ args.iter()
+ .map(|arg| arg.to_type(&self.structs))
+ .collect::<Result<Vec<_>, _>>()?,
+ return_type.to_type(&self.structs)?,
+ );
+
+ ff_table.table.insert(
+ function.name.clone(),
+ FunctionImpl {
+ cif,
+ args,
+ code_ptr: CodePtr(unsafe { code_ptr.into_raw() }.as_raw_ptr()),
+ return_type,
+ },
+ );
+ }
+ std::mem::forget(library);
+ self.merge(ff_table);
+ Ok(())
+ }
+
+ pub fn exec(
+ &mut self,
+ fn_name: &str,
+ mut args: Vec<Value>,
+ arena: &mut Arena,
+ ) -> Result<Value, FfiError> {
+ let fn_impl = self.table.get(fn_name).ok_or(FfiError::FunctionNotFound)?;
+
+ let args = ArgValue::build_args(&mut args, &fn_impl.args, &self.structs)?;
+
+ let args = PointerArgs::new(&args);
+
+ fn_impl.call(&args, arena, &self.structs)
+ }
}
#[derive(Clone, Debug)]
match self {
Value::Number(Number::Integer(ibig_ptr)) => {
let ibig: &Integer = ibig_ptr;
- ibig.clone().try_into().map_err(|_| FfiError::ValueDontFit)
+ ibig.clone()
+ .try_into()
+ .map_err(|_| FfiError::ValueOutOfRange)
}
Value::Number(Number::Fixnum(fixnum)) => fixnum
.get_num()
.try_into()
- .map_err(|_| FfiError::ValueDontFit),
+ .map_err(|_| FfiError::ValueOutOfRange),
_ => Err(FfiError::ValueCast),
}
}
_ => Err(FfiError::ValueCast),
}
}
+
+ fn as_struct(&mut self) -> Result<(&str, &mut [Self]), FfiError> {
+ match self {
+ Value::Struct(name, values) => Ok((name, values)),
+ _ => Err(FfiError::ValueCast),
+ }
+ }
}
#[derive(Debug)]
pub enum FfiError {
ValueCast,
- ValueDontFit,
+ ValueOutOfRange,
+ InvalidArgumentType,
+ InvalidArgument,
InvalidFfiType,
- InvalidStructName,
+ InvalidStruct,
FunctionNotFound,
StructNotFound,
ArgCountMismatch,