]> Repositorios git - scryer-prolog.git/commitdiff
support cstr as an ffi return type and do some more restructuring
authorBennet Bleßmann <[email protected]>
Wed, 16 Jul 2025 23:28:52 +0000 (01:28 +0200)
committerBennet Bleßmann <[email protected]>
Fri, 1 Aug 2025 17:41:56 +0000 (19:41 +0200)
src/ffi.rs
src/machine/machine_errors.rs
src/machine/system_calls.rs
tests-pl/ffi_cstr.pl [new file with mode: 0644]
tests-pl/ffi_invalid_type.pl
tests/scryer/ffi.rs

index 5ef6ffe1dba9c04050818694a61416da3449d381..ad55e54e3d473000578fe89ca03aadaeda3081f4 100644 (file)
@@ -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<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())
@@ -360,101 +234,11 @@ impl ForeignFunctionTable {
         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 {
@@ -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::<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)?;
@@ -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<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)]
@@ -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,
index 3e0ef8fdc9a83b963b324d20380a769e41d00a28..8fe536d9116616bade1f3b664a3c38b47185c0e4 100644 (file)
@@ -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"),
index b7d5b5955e4da6a8202305145fbe1604714f976c..753c915caefcbd2c4823ed854e11dd51dab5ba73 100644 (file)
@@ -5002,48 +5002,57 @@ impl Machine {
     #[cfg(feature = "ffi")]
     #[inline(always)]
     pub(crate) fn foreign_call(&mut self) -> CallResult {
+        fn stub_gen() -> Vec<FunctorElement> {
+            functor_stub(atom!("foreign_call"), 3)
+        }
+
+        fn map_arg(
+            machine_st: &mut MachineState,
+            source: HeapCellValue,
+        ) -> Result<crate::ffi::Value, FfiError> {
+            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::<Result<_, _>>()?,
+                    ))
+                } 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::<Result<Vec<_>, _>>()
+                    {
+                        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 (file)
index 0000000..08de6a7
--- /dev/null
@@ -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).
index 6e687240f87b053784fa999f9e50bf7fe6c8c7cb..7565792facf675ec11beb8d9481c13b6f9c32a46 100644 (file)
@@ -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)
     ]).
 
index 107b5ff094e9e2f15c2d0659d00c6343b5c4844e..eb3167ad5a948410125b332e697c11347367342a 100644 (file)
@@ -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]"#,
+    );
+}