]> Repositorios git - scryer-prolog.git/commitdiff
add ffi:{allocate,read_ptr,deallocate}
authorSkgland <[email protected]>
Sat, 9 Aug 2025 00:14:02 +0000 (02:14 +0200)
committerBennet Bleßmann <[email protected]>
Sun, 24 Aug 2025 17:56:23 +0000 (19:56 +0200)
build/instructions_template.rs
src/ffi.rs
src/lib/ffi.pl
src/machine/dispatch.rs
src/machine/machine_errors.rs
src/machine/machine_state.rs
src/machine/system_calls.rs
tests-pl/ffi_heap.pl [new file with mode: 0644]
tests/scryer/ffi.rs

index 65fc971e94e182b3e8513ac7ec54fb11e5d5bde7..6b5ba80e9248a9c7e275a7d5978c7675823cfee0 100644 (file)
@@ -609,6 +609,12 @@ enum SystemClauseType {
     ForeignCall,
     #[strum_discriminants(strum(props(Arity = "2", Name = "$define_foreign_struct")))]
     DefineForeignStruct,
+    #[strum_discriminants(strum(props(Arity = "4", Name = "$ffi_allocate")))]
+    FfiAllocate,
+    #[strum_discriminants(strum(props(Arity = "3", Name = "$ffi_read_ptr")))]
+    FfiReadPtr,
+    #[strum_discriminants(strum(props(Arity = "3", Name = "$ffi_deallocate")))]
+    FfiDeallocate,
     #[strum_discriminants(strum(props(Arity = "2", Name = "$js_eval")))]
     JsEval,
     #[strum_discriminants(strum(props(Arity = "3", Name = "$predicate_defined")))]
@@ -1806,6 +1812,9 @@ fn generate_instruction_preface() -> TokenStream {
                     &Instruction::CallLoadForeignLib |
                     &Instruction::CallForeignCall |
                     &Instruction::CallDefineForeignStruct |
+                    &Instruction::CallFfiAllocate |
+                    &Instruction::CallFfiReadPtr |
+                    &Instruction::CallFfiDeallocate |
                     &Instruction::CallJsEval |
                     &Instruction::CallPredicateDefined |
                     &Instruction::CallStripModule |
@@ -2064,6 +2073,9 @@ fn generate_instruction_preface() -> TokenStream {
                     &Instruction::ExecuteLoadForeignLib |
                     &Instruction::ExecuteForeignCall |
                     &Instruction::ExecuteDefineForeignStruct |
+                    &Instruction::ExecuteFfiAllocate |
+                    &Instruction::ExecuteFfiReadPtr |
+                    &Instruction::ExecuteFfiDeallocate |
                     &Instruction::ExecuteJsEval |
                     &Instruction::ExecutePredicateDefined |
                     &Instruction::ExecuteStripModule |
index 36f290ffd9b62d62496e0f7cb8371ad7e233babd..0cb3818976c429c8ae7578452ff814cfbc860029 100644 (file)
@@ -34,6 +34,7 @@ use std::error::Error;
 use std::ffi::{c_char, c_void, CStr, CString};
 use std::fmt::Debug;
 use std::marker::PhantomData;
+use std::mem::ManuallyDrop;
 use std::ops::Deref;
 use std::ptr::NonNull;
 
@@ -109,7 +110,7 @@ impl FunctionImpl {
         let layout = Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
             .map_err(|_| FfiError::LayoutError)?;
 
-        let alloc = FfiStruct::new(layout)?;
+        let alloc = FfiStruct::new(layout, FfiAllocator::Rust)?;
 
         unsafe {
             libffi::raw::ffi_call(
@@ -174,6 +175,14 @@ struct StructImpl {
 }
 
 impl StructImpl {
+
+
+    fn layout(&self) -> Result<Layout, FfiError> {
+        let ffi_type = unsafe {*self.ffi_type.as_raw_ptr()};
+        Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
+            .map_err(|_| FfiError::LayoutError)
+    }
+
     fn build(
         &self,
         structs_table: &HashMap<String, StructImpl>,
@@ -181,11 +190,9 @@ impl StructImpl {
     ) -> Result<FfiStruct, FfiError> {
         let args = ArgValue::build_args(struct_args, &self.fields, structs_table)?;
 
-        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())
-                .map_err(|_| FfiError::LayoutError)?,
+                self.layout()?,
+                FfiAllocator::Rust
         )?;
 
         let Ok(mut current_layout) = Layout::from_size_align(0, 1) else {
@@ -343,6 +350,7 @@ impl StructImpl {
     }
 }
 
+
 struct PointerArgs<'a, 'val> {
     memory: Vec<Arg>,
     phantom: PhantomData<&'a mut ArgValue<'val>>,
@@ -520,21 +528,67 @@ impl<'val> ArgValue<'val> {
 struct FfiStruct {
     ptr: NonNull<c_void>,
     layout: Layout,
+    allocator: FfiAllocator,
 }
 
-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)
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum FfiAllocator {
+    Rust,
+    C
+}
+
+impl TryFrom<Atom> for FfiAllocator {
+    type Error = ();
+
+    fn try_from(value: Atom) -> Result<Self, Self::Error> {
+        match value {
+            atom!("rust") => Ok(Self::Rust),
+            atom!("c") => Ok(Self::C),
+            _ => Err(())
+        }
+    }
+}
+
+impl FfiAllocator {
+
+    /// # Safety
+    ///
+    /// - layout must not have a size of 0
+    unsafe fn alloc(self, layout: Layout) -> Result<NonNull<c_void>, FfiError> {
+        let ptr = match self {
+            FfiAllocator::Rust => {
+                unsafe { alloc::alloc(layout).cast() }
+            },
+            FfiAllocator::C => {
+                unsafe { libc::malloc(layout.size()) }
+            },
+        };
+
+        NonNull::new(ptr).ok_or(FfiError::AllocationFailed)
+    }
+
+    /// # Safety
+    ///
+    /// - ptr must point to an allocation currently allocated by this allocator
+    /// - layout must match the layout that was used to allocate the allocation pointed to by ptr
+    unsafe fn dealloc(self, layout: Layout, ptr: NonNull<c_void>) {
+        match self {
+            FfiAllocator::Rust => unsafe { alloc::dealloc(ptr.as_ptr().cast(), layout) },
+            FfiAllocator::C => unsafe {libc::free(ptr.as_ptr())},
         }
     }
 }
 
+impl FfiStruct {
+    fn new(layout: Layout, allocator: FfiAllocator) -> Result<Self, FfiError> {
+        assert_ne!(layout.size() , 0);
+        Ok(FfiStruct { ptr: unsafe { allocator.alloc(layout) }?, layout , allocator})
+    }
+}
+
 impl Drop for FfiStruct {
     fn drop(&mut self) {
-        unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) };
+        unsafe { self.allocator.dealloc(self.layout, self.ptr) };
     }
 }
 
@@ -624,6 +678,235 @@ impl ForeignFunctionTable {
 
         fn_impl.call(&args, arena, &self.structs)
     }
+
+    pub fn allocate(
+        &mut self,
+        allocator: FfiAllocator,
+        kind: Atom,
+        mut args: Value,
+        arena: &mut Arena
+    ) -> Result<Value, FfiError> {
+
+        fn allocate_primitive<T: Copy>(allocator: FfiAllocator, initial_value: T, arena: &mut Arena) -> Result<Value, FfiError> {
+            const { assert!(std::mem::size_of::<T>() != 0)};
+            let ptr = unsafe { allocator.alloc(Layout::new::<T>()) }?;
+            unsafe { ptr.cast::<T>().write(initial_value) };
+            Ok(Value::Number(fixnum!(Number, ptr.as_ptr().expose_provenance(), arena)))
+        }
+
+
+        match FfiType::from_atom(&kind) {
+            FfiType::Void => Err(FfiError::InvalidFfiType),
+            FfiType::Bool => {
+                let val = args.as_int::<u8>()?;
+                let init = match val {
+                    0 => false,
+                    1 => true,
+                    _ => return Err(FfiError::ValueOutOfRange),
+                };
+                allocate_primitive::<bool>(allocator, init, arena)
+            },
+            FfiType::U8 => {
+                allocate_primitive::<u8>(allocator, args.as_int()?, arena)
+            },
+            FfiType::I8 => {
+                allocate_primitive::<i8>(allocator, args.as_int()?, arena)
+            },
+            FfiType::U16 => {
+                allocate_primitive::<u16>(allocator, args.as_int()?, arena)
+            },
+            FfiType::I16 => {
+                allocate_primitive::<i16>(allocator, args.as_int()?, arena)
+            },
+            FfiType::U32 => {
+                allocate_primitive::<u32>(allocator, args.as_int()?, arena)
+            },
+            FfiType::I32 => {
+
+                allocate_primitive::<i32>(allocator, args.as_int()?, arena)
+            },
+            FfiType::U64 => {
+
+                allocate_primitive::<u64>(allocator, args.as_int()?, arena)
+            },
+            FfiType::I64 => {
+                allocate_primitive::<i64>(allocator, args.as_int()?, arena)
+            },
+            FfiType::F32 => {
+                allocate_primitive::<f32>(allocator, args.as_float()? as f32, arena)
+            },
+            FfiType::F64 => {
+                allocate_primitive::<f64>(allocator, args.as_float()?, arena)
+            },
+            FfiType::Ptr => {
+                allocate_primitive::<*mut c_void>(allocator, args.as_ptr()?, arena)
+            },
+            FfiType::CStr => Err(FfiError::InvalidFfiType),
+            FfiType::Struct(_) => {
+                let Some(struct_impl) = self.structs.get(&*kind.as_str()) else {
+                    return Err(FfiError::InvalidStruct)
+                };
+
+
+                let (_, args) = args.as_struct()?;
+
+                let ffi_struct = struct_impl.build(&self.structs, args)?;
+
+                let ptr = ManuallyDrop::new(ffi_struct).ptr;
+
+                Ok(Value::Number(fixnum!(Number, ptr.as_ptr().expose_provenance(), arena)))
+            },
+        }
+    }
+
+
+    pub fn read_ptr(
+        &mut self,
+        kind: Atom,
+        mut ptr: Value,
+        arena: &mut Arena
+    ) -> Result<Value, FfiError> {
+
+        unsafe fn read_int<T>(
+            ptr: NonNull<c_void>,
+            arena: &mut Arena,
+        ) -> Value
+        where
+            T: Copy + TryInto<i64> + MightNotFitInFixnum,
+            Integer: From<T>,
+        {
+            let n = ptr.cast::<T>().read();
+            Value::Number(fixnum!(Number, n, arena))
+        }
+
+        let ptr = ptr.as_ptr()?;
+
+        let Some(ptr) = NonNull::new(ptr) else {
+            return Err(FfiError::ValueOutOfRange)
+        };
+
+        match FfiType::from_atom(&kind) {
+            FfiType::Void => Err(FfiError::InvalidFfiType),
+            FfiType::Bool | FfiType::U8 => {
+                Ok(unsafe {read_int::<u8>(ptr, arena)})
+            },
+            FfiType::I8 => {
+                Ok(unsafe {read_int::<i8>(ptr, arena)})
+            },
+            FfiType::U16 => {
+                Ok(unsafe {read_int::<u16>(ptr, arena)})
+            },
+            FfiType::I16 => {
+                Ok(unsafe {read_int::<i16>(ptr, arena)})
+            },
+            FfiType::U32 => {
+                Ok(unsafe {read_int::<u32>(ptr, arena)})
+            },
+            FfiType::I32 => {
+                Ok(unsafe {read_int::<i32>(ptr, arena)})
+            },
+            FfiType::U64 => {
+
+                Ok(unsafe {read_int::<u64>(ptr, arena)})
+            },
+            FfiType::I64 => {
+                Ok(unsafe {read_int::<i64>(ptr, arena)})
+            },
+            FfiType::F32 => {
+                Ok(Value::Number(Number::Float((unsafe { ptr.cast::<f32>().read() } as f64) .into())))
+            },
+            FfiType::F64 => {
+                Ok(Value::Number(Number::Float(unsafe { ptr.cast::<f64>().read() }.into())))
+            },
+            FfiType::Ptr => {
+                let addr = unsafe { ptr.cast::<*mut c_void>().read() }.expose_provenance();
+                Ok(Value::Number(fixnum!(Number, addr, arena)))
+            },
+            FfiType::CStr => {
+                Ok(Value::CString(unsafe { CStr::from_ptr(ptr.as_ptr().cast()) }.to_owned()))
+            },
+            FfiType::Struct(_) => {
+                let Some(struct_impl) = self.structs.get(&*kind.as_str()) else {
+                    return Err(FfiError::InvalidStruct)
+                };
+
+                struct_impl.read(ptr.as_ptr(), &kind.as_str(), &self.structs, arena)
+            },
+        }
+    }
+
+
+    pub fn deallocate(
+        &mut self,
+        allocator: FfiAllocator,
+        kind: Atom,
+        mut ptr: Value,
+    ) -> Result<(), FfiError> {
+
+        fn deallocate_primitive<T: Copy>(allocator: FfiAllocator, ptr: NonNull<c_void>) {
+            const { assert!(std::mem::size_of::<T>() != 0)};
+            unsafe { allocator.dealloc(Layout::new::<T>(), ptr) };
+        }
+
+        let ptr = ptr.as_ptr()?;
+
+        let Some(ptr) = NonNull::new(ptr) else {
+            return Err(FfiError::ValueOutOfRange)
+        };
+
+        match FfiType::from_atom(&kind) {
+            FfiType::Void => return Err(FfiError::InvalidFfiType),
+            FfiType::Bool => {
+                deallocate_primitive::<bool>(allocator, ptr)
+            },
+            FfiType::U8 => {
+                deallocate_primitive::<u8>(allocator, ptr)
+            },
+            FfiType::I8 => {
+                deallocate_primitive::<i8>(allocator, ptr)
+            },
+            FfiType::U16 => {
+                deallocate_primitive::<u16>(allocator, ptr)
+            },
+            FfiType::I16 => {
+                deallocate_primitive::<i16>(allocator, ptr)
+            },
+            FfiType::U32 => {
+                deallocate_primitive::<u32>(allocator, ptr)
+            },
+            FfiType::I32 => {
+
+                deallocate_primitive::<i32>(allocator, ptr)
+            },
+            FfiType::U64 => {
+
+                deallocate_primitive::<u64>(allocator, ptr)
+            },
+            FfiType::I64 => {
+                deallocate_primitive::<i64>(allocator, ptr)
+            },
+            FfiType::F32 => {
+                deallocate_primitive::<f32>(allocator, ptr)
+            },
+            FfiType::F64 => {
+                deallocate_primitive::<f64>(allocator, ptr)
+            },
+            FfiType::Ptr => {
+                deallocate_primitive::<*mut c_void>(allocator, ptr)
+            },
+            FfiType::CStr => return Err(FfiError::InvalidFfiType),
+            FfiType::Struct(_) => {
+                let Some(struct_impl) = self.structs.get(&*kind.as_str()) else {
+                    return Err(FfiError::InvalidStruct)
+                };
+
+                let layout = struct_impl.layout()?;
+
+                drop(FfiStruct { ptr, layout, allocator})
+            },
+        }
+        Ok(())
+    }
 }
 
 #[derive(Clone, Debug)]
index 165d5d04ae78d04310baa86621ee30e43f5e4511..22ff55959ed74188d466022e37d2cc6c89ae4613 100644 (file)
@@ -73,6 +73,23 @@ use_foreign_module(LibName, Predicates) :-
     '$load_foreign_lib'(LibName, Predicates),
     maplist(assert_predicate, Predicates).
 
+allocate(Allocator, Type, Args, Ptr) :-
+    must_be(var, Ptr),
+    must_be(atom, Type),
+    must_be(atom, Allocator),
+    '$ffi_allocate'(Allocator, Type, Args, Ptr).
+
+read_ptr(Type, Ptr, Value) :-
+    must_be(var, Value),
+    must_be(atom, Type),
+    must_be(integer, Ptr),
+    '$ffi_read_ptr'(Type, Ptr, Value).
+
+deallocate(Allocator, Type, Ptr) :-
+    must_be(atom, Allocator),
+    must_be(integer, Ptr),
+    '$ffi_deallocate'(Allocator, Type, Ptr).
+
 assert_predicate(PredicateDefinition) :-
     PredicateDefinition =.. [Name, Inputs, void],
     length(Inputs, NumInputs),
index 59db4e1c175088bcb3a4780875fa0f23bd4eba2d..9143f2e10e438f5239469e02c989ae54d83ce343 100644 (file)
@@ -4331,6 +4331,36 @@ impl Machine {
                         try_or_throw!(self.machine_st, self.define_foreign_struct());
                         step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
                     }
+                    &Instruction::CallFfiAllocate => {
+                        #[cfg(feature = "ffi")]
+                        try_or_throw!(self.machine_st, self.ffi_allocate());
+                        step_or_fail!(self, self.machine_st.p += 1);
+                    }
+                    &Instruction::ExecuteFfiAllocate => {
+                        #[cfg(feature = "ffi")]
+                        try_or_throw!(self.machine_st, self.ffi_allocate());
+                        step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
+                    }
+                    &Instruction::CallFfiReadPtr => {
+                        #[cfg(feature = "ffi")]
+                        try_or_throw!(self.machine_st, self.ffi_read_ptr());
+                        step_or_fail!(self, self.machine_st.p += 1);
+                    }
+                    &Instruction::ExecuteFfiReadPtr => {
+                        #[cfg(feature = "ffi")]
+                        try_or_throw!(self.machine_st, self.ffi_read_ptr());
+                        step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
+                    }
+                    &Instruction::CallFfiDeallocate => {
+                        #[cfg(feature = "ffi")]
+                        try_or_throw!(self.machine_st, self.ffi_deallocate());
+                        step_or_fail!(self, self.machine_st.p += 1);
+                    }
+                    &Instruction::ExecuteFfiDeallocate => {
+                        #[cfg(feature = "ffi")]
+                        try_or_throw!(self.machine_st, self.ffi_deallocate());
+                        step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
+                    }
                     &Instruction::CallJsEval => {
                         try_or_throw!(self.machine_st, self.js_eval());
                         step_or_fail!(self, self.machine_st.p += 1);
index f9b9318d3ee48e10294ec11c2d9a32e3823b92db..496d8085c8411ab53df177c2ab277f3a534e5316 100644 (file)
@@ -813,6 +813,7 @@ pub(crate) enum DomainErrorType {
     OperatorSpecifier,
     OperatorPriority,
     Directive,
+    Allocator,
 }
 
 impl DomainErrorType {
@@ -827,6 +828,7 @@ impl DomainErrorType {
             DomainErrorType::OperatorSpecifier => atom!("operator_specifier"),
             DomainErrorType::OperatorPriority => atom!("operator_priority"),
             DomainErrorType::Directive => atom!("directive"),
+            DomainErrorType::Allocator => atom!("allocator"),
         }
     }
 }
index c77110c79ebff81bf9924da016c1484bf56a5170..84e7759fb7ac63136da774ba07793a88b65604df 100644 (file)
@@ -184,7 +184,7 @@ impl IndexMut<RegType> for MachineState {
     }
 }
 
-pub type CallResult = Result<(), Vec<FunctorElement>>;
+pub type CallResult<Ok=()> = Result<Ok, Vec<FunctorElement>>;
 
 // size may be an upper bound.
 // true_size is calculated to compute the exact offset.
index 60a4a515ecdefff9c0575bc9e74997cf015366db..88b0af4d5199becdb9bd32c5abd1b8be60b9becd 100644 (file)
@@ -5003,6 +5003,36 @@ impl Machine {
         Ok(())
     }
 
+    fn map_ffi_arg(
+        machine_st: &mut MachineState,
+        source: HeapCellValue,
+        stub_gen: impl Copy + Fn() -> MachineStub
+    ) -> CallResult<Value> {
+        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| Self::map_ffi_arg(machine_st, x, stub_gen))
+                        .collect::<Result<_, _>>()?,
+                ))
+            } else {
+                // empty list is an invalid struct repr
+                Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidStruct), stub_gen()))
+            }
+        } else {
+            Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidArgument), stub_gen()))
+        }
+    }
+
     #[cfg(feature = "ffi")]
     #[inline(always)]
     pub(crate) fn foreign_call(&mut self) -> CallResult {
@@ -5010,34 +5040,6 @@ impl Machine {
             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);
@@ -5045,17 +5047,10 @@ impl Machine {
         if let Some(function_name) = self.machine_st.value_to_str_like(function_name) {
             match self.machine_st.try_from_list(args_reg, stub_gen) {
                 Ok(args) => {
-                    let args = match args
+                    let args = args
                         .into_iter()
-                        .map(|x| map_arg(&mut self.machine_st, x))
-                        .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()));
-                        }
-                    };
+                        .map(|x| Self::map_ffi_arg(&mut self.machine_st, x, stub_gen))
+                        .collect::<Result<Vec<_>, _>>()?;
 
                     match self.foreign_function_table.exec(
                         &function_name.as_str(),
@@ -5063,41 +5058,7 @@ impl Machine {
                         &mut self.machine_st.arena,
                     ) {
                         Ok(result) => {
-                            match result {
-                                Value::Number(n) => match n {
-                                    Number::Float(OrderedFloat(n)) => {
-                                        let n = float_alloc!(n, self.machine_st.arena);
-                                        self.machine_st.unify_f64(n, return_value)
-                                    }
-                                    Number::Integer(typed_arena_ptr) => {
-                                        self.machine_st.unify_big_int(typed_arena_ptr, return_value)
-                                    }
-                                    Number::Rational(typed_arena_ptr) => {
-                                        self.machine_st
-                                            .unify_rational(typed_arena_ptr, return_value);
-                                    }
-                                    Number::Fixnum(fixnum) => {
-                                        self.machine_st.unify_fixnum(fixnum, return_value)
-                                    }
-                                },
-                                Value::Struct(name, args) => {
-                                    let struct_value = resource_error_call_result!(
-                                        self.machine_st,
-                                        self.build_struct(&name, args)
-                                    );
-
-                                    unify!(self.machine_st, return_value, struct_value);
-                                }
-                                Value::CString(cstr) => {
-                                    let str_cell = resource_error_call_result!(
-                                        self.machine_st,
-                                        self.machine_st.heap.allocate_cstr(cstr.to_str().unwrap())
-                                    );
-
-                                    unify!(self.machine_st, str_cell, return_value);
-                                }
-                            }
-                            return Ok(());
+                            return self.unify_ffi_result(return_value, result);
                         }
                         Err(e) => {
                             let err = self.machine_st.ffi_error(e);
@@ -5113,6 +5074,44 @@ impl Machine {
         Ok(())
     }
 
+    fn unify_ffi_result(&mut self, return_value: HeapCellValue, result: Value) -> CallResult {
+        match result {
+            Value::Number(n) => match n {
+                Number::Float(OrderedFloat(n)) => {
+                    let n = float_alloc!(n, self.machine_st.arena);
+                    self.machine_st.unify_f64(n, return_value)
+                }
+                Number::Integer(typed_arena_ptr) => {
+                    self.machine_st.unify_big_int(typed_arena_ptr, return_value)
+                }
+                Number::Rational(typed_arena_ptr) => {
+                    self.machine_st
+                        .unify_rational(typed_arena_ptr, return_value);
+                }
+                Number::Fixnum(fixnum) => {
+                    self.machine_st.unify_fixnum(fixnum, return_value)
+                }
+            },
+            Value::Struct(name, args) => {
+                let struct_value = resource_error_call_result!(
+                    self.machine_st,
+                    self.build_struct(&name, args)
+                );
+
+                unify!(self.machine_st, return_value, struct_value);
+            }
+            Value::CString(cstr) => {
+                let str_cell = resource_error_call_result!(
+                    self.machine_st,
+                    self.machine_st.heap.allocate_cstr(cstr.to_str().unwrap())
+                );
+
+                unify!(self.machine_st, str_cell, return_value);
+            }
+        }
+        Ok(())
+    }
+
     #[cfg(feature = "ffi")]
     fn build_struct(&mut self, name: &str, mut args: Vec<Value>) -> Result<HeapCellValue, usize> {
         args.insert(0, Value::CString(CString::new(name).unwrap()));
@@ -5168,6 +5167,75 @@ impl Machine {
         Ok(())
     }
 
+    pub(crate) fn ffi_allocate(&mut self) -> CallResult {
+        let stub_gen = || functor_stub(atom!("$ffi_allocate"), 4);
+
+        let allocator = self.deref_register(1);
+        let ffi_type = self.deref_register(2).to_atom().unwrap();
+        let args = self.deref_register(3);
+        let return_value = self.deref_register(4);
+
+        let allocator = FfiAllocator::try_from(allocator.to_atom().unwrap()).map_err(|_| {
+            let machine_error = self.machine_st.domain_error(DomainErrorType::Allocator, allocator);
+            self.machine_st.error_form(machine_error, stub_gen())
+        })?;
+
+        let args = Self::map_ffi_arg(&mut self.machine_st, args, stub_gen)?;
+
+        let value = match self.foreign_function_table.allocate(allocator, ffi_type, args, &mut self.machine_st.arena) {
+            Ok(value) => value,
+            Err(ffi_error) => {
+                let machine_error = self.machine_st.ffi_error(ffi_error);
+                return Err(self.machine_st.error_form(machine_error, stub_gen()));
+            },
+        };
+
+        self.unify_ffi_result(return_value, value)
+    }
+
+    pub(crate) fn ffi_read_ptr(&mut self) -> CallResult {
+        let stub_gen = || functor_stub(atom!("$ffi_read_ptr"), 3);
+
+        let ffi_type = self.deref_register(1).to_atom().unwrap();
+        let ptr = self.deref_register(2);
+        let return_value = self.deref_register(3);
+
+        let ptr = Self::map_ffi_arg(&mut self.machine_st, ptr, stub_gen)?;
+
+        let value = self.foreign_function_table.read_ptr(ffi_type, ptr, &mut self.machine_st.arena).map_err(|ffi_error| {
+                let machine_error = self.machine_st.ffi_error(ffi_error);
+                self.machine_st.error_form(machine_error, stub_gen())
+        })?;
+
+        self.unify_ffi_result(return_value, value)
+    }
+
+    pub(crate) fn ffi_deallocate(&mut self) -> CallResult {
+        let stub_gen = || functor_stub(atom!("$ffi_deallocate"), 3);
+
+        let allocator = self.deref_register(1);
+        let ffi_type = self.deref_register(2).to_atom().unwrap();
+        let ptr = self.deref_register(3);
+
+
+        let allocator = FfiAllocator::try_from(allocator.to_atom().unwrap()).map_err(|_| {
+            let machine_error = self.machine_st.domain_error(DomainErrorType::Allocator, allocator);
+            self.machine_st.error_form(machine_error, stub_gen())
+        })?;
+
+        let ptr = Self::map_ffi_arg(&mut self.machine_st, ptr, stub_gen)?;
+
+        match self.foreign_function_table.deallocate(allocator, ffi_type, ptr) {
+            Ok(value) => value,
+            Err(ffi_error) => {
+                let machine_error = self.machine_st.ffi_error(ffi_error);
+                return Err(self.machine_st.error_form(machine_error, stub_gen()));
+            },
+        }
+
+        Ok(())
+    }
+
     #[cfg(not(target_arch = "wasm32"))]
     #[inline(always)]
     pub(crate) fn js_eval(&mut self) -> CallResult {
diff --git a/tests-pl/ffi_heap.pl b/tests-pl/ffi_heap.pl
new file mode 100644 (file)
index 0000000..6f3d80e
--- /dev/null
@@ -0,0 +1,19 @@
+:- use_module(library(os)).
+:- use_module(library(ffi)).
+
+init :-
+    read(Body),
+    term_variables(Body, [LIB]),
+    Body,
+    use_foreign_module(LIB, [
+        'ffi_set_u64'([ptr], void)
+    ]).
+
+test :-
+    ffi:allocate(rust, u64, 0, Ptr),
+    ffi:'ffi_set_u64'(Ptr),
+    ffi:read_ptr(u64, Ptr, Val),
+    ffi:deallocate(rust, u64, Ptr),
+    write((Val)).
+
+:- initialization((init,test)).
index c9e0aa1ee801bd8e67e507082bdfb80610aa87ca..c86360e9ece75c03f78373f416e113a2783d4f7a 100644 (file)
@@ -283,3 +283,25 @@ fn ffi_cstr() {
         format!(r#"13-[R,u,s,t, ,L,a,n,g]-0-{}"#, u64::MAX).as_str(),
     );
 }
+
+
+
+#[test]
+#[cfg_attr(miri, ignore = "ffi")]
+fn ffi_heap() {
+    let dynlib_path = build_dynamic_library(
+        "ffi_heap",
+        r##"
+                #[unsafe(no_mangle)]
+                extern "C" fn ffi_set_u64(val: &mut u64) {
+                    *val = 133742
+                }
+            "##,
+    );
+
+    load_module_test_with_input(
+        "tests-pl/ffi_heap.pl",
+        format!("LIB={dynlib_path:?}."),
+        r#"133742"#,
+    );
+}