From 20d52c093a0a9cf7777a2cb880c26bb10f5f061c Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 02:14:02 +0200 Subject: [PATCH] add ffi:{allocate,read_ptr,deallocate} --- build/instructions_template.rs | 12 ++ src/ffi.rs | 307 +++++++++++++++++++++++++++++++-- src/lib/ffi.pl | 17 ++ src/machine/dispatch.rs | 30 ++++ src/machine/machine_errors.rs | 2 + src/machine/machine_state.rs | 2 +- src/machine/system_calls.rs | 214 +++++++++++++++-------- tests-pl/ffi_heap.pl | 19 ++ tests/scryer/ffi.rs | 22 +++ 9 files changed, 539 insertions(+), 86 deletions(-) create mode 100644 tests-pl/ffi_heap.pl diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 65fc971e..6b5ba80e 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -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 | diff --git a/src/ffi.rs b/src/ffi.rs index 36f290ff..0cb38189 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -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 { + 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, @@ -181,11 +190,9 @@ impl StructImpl { ) -> Result { 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, phantom: PhantomData<&'a mut ArgValue<'val>>, @@ -520,21 +528,67 @@ impl<'val> ArgValue<'val> { struct FfiStruct { ptr: NonNull, layout: Layout, + allocator: FfiAllocator, } -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) +#[derive(Debug, Clone, Copy)] +pub(crate) enum FfiAllocator { + Rust, + C +} + +impl TryFrom for FfiAllocator { + type Error = (); + + fn try_from(value: Atom) -> Result { + 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, 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) { + 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 { + 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 { + + fn allocate_primitive(allocator: FfiAllocator, initial_value: T, arena: &mut Arena) -> Result { + const { assert!(std::mem::size_of::() != 0)}; + let ptr = unsafe { allocator.alloc(Layout::new::()) }?; + unsafe { ptr.cast::().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::()?; + let init = match val { + 0 => false, + 1 => true, + _ => return Err(FfiError::ValueOutOfRange), + }; + allocate_primitive::(allocator, init, arena) + }, + FfiType::U8 => { + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::I8 => { + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::U16 => { + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::I16 => { + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::U32 => { + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::I32 => { + + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::U64 => { + + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::I64 => { + allocate_primitive::(allocator, args.as_int()?, arena) + }, + FfiType::F32 => { + allocate_primitive::(allocator, args.as_float()? as f32, arena) + }, + FfiType::F64 => { + allocate_primitive::(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 { + + unsafe fn read_int( + ptr: NonNull, + arena: &mut Arena, + ) -> Value + where + T: Copy + TryInto + MightNotFitInFixnum, + Integer: From, + { + let n = ptr.cast::().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::(ptr, arena)}) + }, + FfiType::I8 => { + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::U16 => { + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::I16 => { + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::U32 => { + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::I32 => { + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::U64 => { + + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::I64 => { + Ok(unsafe {read_int::(ptr, arena)}) + }, + FfiType::F32 => { + Ok(Value::Number(Number::Float((unsafe { ptr.cast::().read() } as f64) .into()))) + }, + FfiType::F64 => { + Ok(Value::Number(Number::Float(unsafe { ptr.cast::().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(allocator: FfiAllocator, ptr: NonNull) { + const { assert!(std::mem::size_of::() != 0)}; + unsafe { allocator.dealloc(Layout::new::(), 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::(allocator, ptr) + }, + FfiType::U8 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::I8 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::U16 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::I16 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::U32 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::I32 => { + + deallocate_primitive::(allocator, ptr) + }, + FfiType::U64 => { + + deallocate_primitive::(allocator, ptr) + }, + FfiType::I64 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::F32 => { + deallocate_primitive::(allocator, ptr) + }, + FfiType::F64 => { + deallocate_primitive::(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)] diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 165d5d04..22ff5595 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -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), diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 59db4e1c..9143f2e1 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -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); diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index f9b9318d..496d8085 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -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"), } } } diff --git a/src/machine/machine_state.rs b/src/machine/machine_state.rs index c77110c7..84e7759f 100644 --- a/src/machine/machine_state.rs +++ b/src/machine/machine_state.rs @@ -184,7 +184,7 @@ impl IndexMut for MachineState { } } -pub type CallResult = Result<(), Vec>; +pub type CallResult = Result>; // size may be an upper bound. // true_size is calculated to compute the exact offset. diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 60a4a515..88b0af4d 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5003,6 +5003,36 @@ impl Machine { Ok(()) } + fn map_ffi_arg( + machine_st: &mut MachineState, + source: HeapCellValue, + stub_gen: impl Copy + Fn() -> MachineStub + ) -> CallResult { + 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::>()?, + )) + } 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 { - 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); @@ -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::, _>>() - { - 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::, _>>()?; 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) -> Result { 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 index 00000000..6f3d80e1 --- /dev/null +++ b/tests-pl/ffi_heap.pl @@ -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)). diff --git a/tests/scryer/ffi.rs b/tests/scryer/ffi.rs index c9e0aa1e..c86360e9 100644 --- a/tests/scryer/ffi.rs +++ b/tests/scryer/ffi.rs @@ -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"#, + ); +} -- 2.54.0