From: Bennet Bleßmann Date: Wed, 16 Jul 2025 23:28:52 +0000 (+0200) Subject: support cstr as an ffi return type and do some more restructuring X-Git-Tag: v0.10.0~29^2~4 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=0c2c6124ebe03d6a4357049d4d3aa95b91104ea9;p=scryer-prolog.git support cstr as an ffi return type and do some more restructuring --- diff --git a/src/ffi.rs b/src/ffi.rs index 5ef6ffe1..ad55e54e 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -31,7 +31,7 @@ use ordered_float::OrderedFloat; 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; @@ -46,258 +46,132 @@ pub struct FunctionDefinition { #[derive(Debug)] pub struct FunctionImpl { cif: Cif, - args: Vec, + args: Vec, code_ptr: CodePtr, - return_struct_name: Option, + return_type: FfiType, } -#[derive(Debug, Default)] -pub struct ForeignFunctionTable { - table: HashMap, - structs: HashMap, -} - -#[derive(Clone, Debug)] -struct StructImpl { - ffi_type: Type, - fields: Vec, - atom_fields: Vec, -} - -struct PointerArgs<'a, 'val> { - memory: Vec, - 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 { + 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, - ) -> Result { - 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(&self, args: &[Arg], arena: &mut Arena) -> Result + where + Integer: From, + T: Copy + TryInto + MightNotFitInFixnum, + { + let n = self.cif.call::(self.code_ptr, args); + Ok(Value::Number(fixnum!(Number, n, arena))) } - fn build_args( - args: &'val mut [Value], - types: &[Type], - structs_table: &HashMap, - ) -> Result, 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::, _>>() + unsafe fn call_float(&self, args: &[Arg], _: &mut Arena) -> Result + where + T: Into, + { + let n = self.cif.call::(self.code_ptr, args); + Ok(Value::Number(Number::Float(OrderedFloat(n.into())))) } -} -struct FfiStruct { - ptr: NonNull, - layout: Layout, -} - -impl FfiStruct { - fn new(layout: Layout) -> Result { - 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 { + 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 { + 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, + ) -> Result { + 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) -> Result<(), FfiError> { - let fields: Vec<_> = atom_fields - .iter() - .map(|x| self.map_type_ffi(x)) - .collect::>()?; - 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 { - 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, - ) -> Result<(), Box> { - 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::>()?; - 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, + ) -> Result { + let call_fn: unsafe fn(&Self, &[Arg], &mut Arena) -> Result = + match self.return_type { + FfiType::Void => FunctionImpl::call_void, + FfiType::U8 => FunctionImpl::call_int::, + FfiType::I8 | FfiType::Bool => FunctionImpl::call_int::, + FfiType::U16 => FunctionImpl::call_int::, + FfiType::I16 => FunctionImpl::call_int::, + FfiType::U32 => FunctionImpl::call_int::, + FfiType::I32 => FunctionImpl::call_int::, + FfiType::U64 => FunctionImpl::call_int::, + FfiType::I64 => FunctionImpl::call_int::, + FfiType::F32 => FunctionImpl::call_float::, + FfiType::F64 => FunctionImpl::call_float::, + 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, + structs: HashMap, +} - PointerArgs { - memory: args, - phantom: PhantomData, - } - } +#[derive(Clone, Debug)] +struct StructImpl { + ffi_type: Type, + fields: Vec, +} - fn build_struct( - arg: &mut Value, +impl StructImpl { + fn build( + &self, structs_table: &HashMap, + struct_args: &mut [Value], ) -> Result { - 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()) @@ -360,101 +234,11 @@ impl ForeignFunctionTable { Ok(alloc) } - pub fn exec( - &mut self, - name: &str, - mut args: Vec, - arena: &mut Arena, - ) -> Result { - 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( - fn_impl: &FunctionImpl, - args: &PointerArgs, - arena: &mut Arena, - ) -> Result - where - Integer: From, - T: Copy + TryInto + MightNotFitInFixnum, - { - let n = fn_impl.cif.call::(fn_impl.code_ptr, args); - Ok(Value::Number(fixnum!(Number, n, arena))) - } - - unsafe fn call_float( - fn_impl: &FunctionImpl, - args: &PointerArgs, - ) -> Result - where - T: Into, - { - let n = fn_impl.cif.call::(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::(fn_impl.code_ptr, &args) }; - Ok(Value::Number(Number::Fixnum(Fixnum::build_with(0)))) - } - libffi::raw::FFI_TYPE_UINT8 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_SINT8 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_UINT16 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_SINT16 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_UINT32 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_SINT32 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_UINT64 => unsafe { call_int::(fn_impl, &args, arena) }, - libffi::raw::FFI_TYPE_SINT64 => unsafe { call_int::(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::(fn_impl, &args) }, - libffi::raw::FFI_TYPE_DOUBLE => unsafe { call_float::(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, arena: &mut Arena, ) -> Result { unsafe { @@ -498,30 +282,32 @@ impl ForeignFunctionTable { 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::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_SINT8 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_UINT16 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_SINT16 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_UINT32 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_SINT32 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_UINT64 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_SINT64 => read_int::(ptr, &mut layout, arena), - libffi::raw::FFI_TYPE_POINTER => { + for field_type in &self.fields { + let val = match field_type { + FfiType::U8 => read_int::(ptr, &mut layout, arena), + FfiType::I8 | FfiType::Bool => read_int::(ptr, &mut layout, arena), + FfiType::U16 => read_int::(ptr, &mut layout, arena), + FfiType::I16 => read_int::(ptr, &mut layout, arena), + FfiType::U32 => read_int::(ptr, &mut layout, arena), + FfiType::I32 => read_int::(ptr, &mut layout, arena), + FfiType::U64 => read_int::(ptr, &mut layout, arena), + FfiType::I64 => read_int::(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::(ptr, &mut layout), - libffi::raw::FFI_TYPE_DOUBLE => read_float::(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::(ptr, &mut layout), + FfiType::F64 => read_float::(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)?; @@ -530,19 +316,304 @@ impl ForeignFunctionTable { .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, + 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) -> Result { + 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, + ) -> Result { + 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, + ) -> Result, 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::, _>>() + } +} + +struct FfiStruct { + ptr: NonNull, + layout: Layout, +} + +impl FfiStruct { + fn new(layout: Layout) -> Result { + 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) -> 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::, _>>()?, + ); + + 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, + ) -> Result<(), Box> { + 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::, _>>()?, + 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, + arena: &mut Arena, + ) -> Result { + 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)] @@ -561,12 +632,14 @@ impl Value { 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), } } @@ -587,14 +660,23 @@ impl Value { _ => 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, diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index 3e0ef8fd..8fe536d9 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -594,9 +594,11 @@ impl MachineState { pub(super) fn ffi_error(&self, err: FfiError) -> MachineError { let error_atom = match err { FfiError::ValueCast => atom!("value_cast"), - FfiError::ValueDontFit => atom!("value_dont_fit"), + FfiError::ValueOutOfRange => atom!("value_out_of_range"), FfiError::InvalidFfiType => atom!("invalid_ffi_type"), - FfiError::InvalidStructName => atom!("invalid_struct_name"), + FfiError::InvalidArgumentType => atom!("invalid_argument_type"), + FfiError::InvalidArgument => atom!("invalid_argument"), + FfiError::InvalidStruct => atom!("invalid_struct"), FfiError::FunctionNotFound => atom!("function_not_found"), FfiError::StructNotFound => atom!("struct_not_found"), FfiError::ArgCountMismatch => atom!("mismatched_argument_count"), diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index b7d5b595..753c915c 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5002,48 +5002,57 @@ impl Machine { #[cfg(feature = "ffi")] #[inline(always)] pub(crate) fn foreign_call(&mut self) -> CallResult { + fn stub_gen() -> Vec { + functor_stub(atom!("foreign_call"), 3) + } + + fn map_arg( + machine_st: &mut MachineState, + source: HeapCellValue, + ) -> Result { + if let Ok(number) = Number::try_from((source, &machine_st.arena.f64_tbl)) { + Ok(Value::Number(number)) + } else if let Some(string) = machine_st.value_to_str_like(source) { + Ok(Value::CString(CString::new(&*string.as_str()).unwrap())) + } else if let Ok(args) = machine_st.try_from_list(source, stub_gen) { + // structs are lists represented as lists + // the head is a string with the struct type name + // the tail are the struct field values + + let mut iter = args.into_iter(); + if let Some(struct_name) = machine_st.value_to_str_like(iter.next().unwrap()) { + Ok(Value::Struct( + struct_name.as_str().to_string(), + iter.map(|x| map_arg(machine_st, x)) + .collect::>()?, + )) + } else { + // empty list is an invalid struct repr + Err(FfiError::InvalidStruct) + } + } else { + Err(FfiError::InvalidArgument) + } + } + let function_name = self.deref_register(1); let args_reg = self.deref_register(2); let return_value = self.deref_register(3); if let Some(function_name) = self.machine_st.value_to_str_like(function_name) { - let stub_gen = || functor_stub(atom!("foreign_call"), 3); - fn map_arg(machine_st: &mut MachineState, source: HeapCellValue) -> crate::ffi::Value { - match Number::try_from((source, &machine_st.arena.f64_tbl)) { - Ok(number) => Value::Number(number), - _ => { - let stub_gen = || functor_stub(atom!("foreign_call"), 3); - if let Some(string) = machine_st.value_to_str_like(source) { - Value::CString(CString::new(&*string.as_str()).unwrap()) - } else { - match machine_st.try_from_list(source, stub_gen) { - Ok(args) => { - let mut iter = args.into_iter(); - if let Some(struct_name) = - machine_st.value_to_str_like(iter.next().unwrap()) - { - Value::Struct( - struct_name.as_str().to_string(), - iter.map(|x| map_arg(machine_st, x)).collect(), - ) - } else { - unreachable!() - } - } - _ => { - unreachable!() - } - } - } - } - } - } - match self.machine_st.try_from_list(args_reg, stub_gen) { Ok(args) => { - let args: Vec<_> = args + let args = match args .into_iter() .map(|x| map_arg(&mut self.machine_st, x)) - .collect(); + .collect::, _>>() + { + Ok(args) => args, + Err(err) => { + let err = self.machine_st.ffi_error(err); + return Err(self.machine_st.error_form(err, stub_gen())); + } + }; + match self.foreign_function_table.exec( &function_name.as_str(), args, @@ -5087,10 +5096,8 @@ impl Machine { return Ok(()); } Err(e) => { - let stub = functor_stub(atom!("current_input"), 1); let err = self.machine_st.ffi_error(e); - - return Err(self.machine_st.error_form(err, stub)); + return Err(self.machine_st.error_form(err, stub_gen())); } } } diff --git a/tests-pl/ffi_cstr.pl b/tests-pl/ffi_cstr.pl new file mode 100644 index 00000000..08de6a73 --- /dev/null +++ b/tests-pl/ffi_cstr.pl @@ -0,0 +1,16 @@ +:- use_module(library(os)). +:- use_module(library(ffi)). + +test :- + read(Body), + term_variables(Body, [LIB]), + Body, + use_foreign_module(LIB, [ + 'ffi_cstr_len'([cstr], u64), + 'ffi_example_cstr'([], cstr) + ]), + ffi:'ffi_cstr_len'("Scryer Prolog", Len), + ffi:'ffi_example_cstr'(Str), + write((Len-Str)). + +:- initialization(test). diff --git a/tests-pl/ffi_invalid_type.pl b/tests-pl/ffi_invalid_type.pl index 6e687240..7565792f 100644 --- a/tests-pl/ffi_invalid_type.pl +++ b/tests-pl/ffi_invalid_type.pl @@ -6,6 +6,7 @@ test :- term_variables(Body, [LIB]), Body, use_foreign_module(LIB, [ + %% should be void instead of c_void 'ffi_invalid_type'([], c_void) ]). diff --git a/tests/scryer/ffi.rs b/tests/scryer/ffi.rs index 107b5ff0..eb3167ad 100644 --- a/tests/scryer/ffi.rs +++ b/tests/scryer/ffi.rs @@ -19,6 +19,7 @@ fn build_dynamic_library(name: &str, src: &str) -> PathBuf { let mut child = std::process::Command::new("rustc") .stdin(Stdio::piped()) + .args(["--edition", "2024"]) .arg(format!("--target={CURRENT_PLATFORM}")) .arg("--crate-type=dylib") .arg(format!("--crate-name={name}")) @@ -46,7 +47,7 @@ fn ffi_f64_nan() { let dynlib_path = build_dynamic_library( "ffi_f64_nan", r##" - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_f64_nan() -> f64 { f64::NAN } @@ -66,12 +67,12 @@ fn ffi_f64_minus_zero() { let dynlib_path = build_dynamic_library( "ffi_f64_minus_zero", r##" - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_f64_minus_zero() -> f64 { -0.0 } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn signum(f: f64) -> f64 { f.signum() } @@ -92,63 +93,63 @@ fn ffi_return_values() { let dynlib_path = build_dynamic_library( "ffi_return_values", r##" - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_true() -> bool { true } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_false() -> bool { false } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_i8() -> i8 { -42 } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_u8() -> u8 { 73 } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_i16() -> i16 { -0xBEE } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_u16() -> u16 { 0xC0DE } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_i32() -> i32 { -0xBEEFBEE } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_u32() -> u32 { 0xC0DEB000 } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_i64() -> i64 { -0xBEEFBEE5C0DEB00 } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_u64() -> u64 { 0xFEDCBA9876543210 } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_f32() -> f32 { std::f32::consts::PI } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_return_values_f64() -> f64 { std::f64::consts::TAU } @@ -182,7 +183,7 @@ fn ffi_invalid_type() { let dynlib_path = build_dynamic_library( "ffi_invalid_type", r##" - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn ffi_invalid_type() -> () { } "##, @@ -212,7 +213,7 @@ fn ffi_struct() { f: f64, } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn construct(a: u8, b: u16, c: u32, d: u64, a2: u8, e: f32, f: f64) -> PaddingGalore { PaddingGalore { a, @@ -225,7 +226,7 @@ fn ffi_struct() { } } - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn modify(data: PaddingGalore) -> PaddingGalore { PaddingGalore { a: data.a2, @@ -246,3 +247,30 @@ fn ffi_struct() { "[P,G]-[pg,8,12,46,40,127,1.3680000305175781,-4.587]\n[P,G,2]-[pg,127,65523,4294967249,18446744073709551575,8,-1.3680000305175781,4.587]\n", ); } + +#[test] +#[cfg_attr(miri, ignore = "ffi")] +fn ffi_cstr() { + let dynlib_path = build_dynamic_library( + "ffi_cstr", + r##" + use std::ffi::CStr; + + #[unsafe(no_mangle)] + extern "C" fn ffi_cstr_len(c_str: *const core::ffi::c_char) -> u64 { + unsafe { CStr::from_ptr(c_str) }.count_bytes() as u64 + } + + #[unsafe(no_mangle)] + extern "C" fn ffi_example_cstr() -> *const core::ffi::c_char { + c"Rust Lang".as_ptr() + } + "##, + ); + + load_module_test_with_input( + "tests-pl/ffi_cstr.pl", + format!("LIB={dynlib_path:?}."), + r#"13-[R,u,s,t, ,L,a,n,g]"#, + ); +}