From 35d34231f2bfe5788e6785fec5ca59344262758c Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 01:59:18 +0200 Subject: [PATCH 01/24] don't fail the build script if we couldn't parse a file this way we get to the actual compilation in which rustc shouldl fail with a more helpfull error message --- build/static_string_indexing.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build/static_string_indexing.rs b/build/static_string_indexing.rs index 7309f1cf..1a00e17b 100644 --- a/build/static_string_indexing.rs +++ b/build/static_string_indexing.rs @@ -133,7 +133,12 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea let syntax = match syn::parse_file(&src) { Ok(s) => s, Err(e) => { - panic!("parse error: {e} in file {path:?}"); + println!("cargo::warning=parse error: {e} in file {path:?}"); + syn::File{ + shebang: None, + attrs: vec![], + items: vec![], + } } }; Ok(syntax) From 20d52c093a0a9cf7777a2cb880c26bb10f5f061c Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 02:14:02 +0200 Subject: [PATCH 02/24] 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, +} + +#[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) -> Result { - if let Some(ptr) = NonNull::new(unsafe { alloc::alloc(layout) as *mut c_void }) { - Ok(FfiStruct { ptr, layout }) - } else { - Err(FfiError::AllocationFailed) - } + 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"#, + ); +} From d8346b1651b07c5cb50b11703ea10234a9ec6817 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 17:50:30 +0200 Subject: [PATCH 03/24] don't place allocate, read_ptr and deallocate between use_foreign_module and its helper predicates --- src/lib/ffi.pl | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 22ff5595..1a1bad84 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -73,23 +73,6 @@ 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), @@ -125,3 +108,22 @@ assert_predicate(PredicateDefinition) :- ), Predicate = (Head:-Body), assertz(ffi:Predicate). + + +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). + From 3a4dfc46da6be8c2c460b8036d3bb36eff2c00f4 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 17:50:58 +0200 Subject: [PATCH 04/24] add ffi helpers --- src/lib/ffi.pl | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 1a1bad84..acf1d193 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -1,4 +1,4 @@ -:- module(ffi, [use_foreign_module/2, foreign_struct/2]). +:- module(ffi, [use_foreign_module/2, foreign_struct/2, with_locals/2, allocate/4, deallocate/3, read_ptr/3]). /** Foreign Function Interface @@ -127,3 +127,45 @@ deallocate(Allocator, Type, Ptr) :- must_be(integer, Ptr), '$ffi_deallocate'(Allocator, Type, Ptr). +:- dynamic(is_array_type_defined/1). + +array_type(ElemType, Len, ArrayType) :- + phrase(format("$[~a;~d]", [ElemType, Len]), ArrayTypeName), + atom_chars(ArrayType, ArrayTypeName), + (is_array_type_defined(ArrayType) -> true + ; length(Fields, Len), + maplist('='(ElemType), Fields), + foreign_struct(ArrayType, Fields), + assertz(is_array_type_defined(ArrayType)) + ). + +with_locals(Locals, Goal) :- + verify_locals(Locals), + allocate_locals(Locals), + ( catch(Goal, E, (deallocate(Locals), throw(E), false)) -> Success = true + ; Success = false + ), + deallocate_locals(Locals). + +verify_locals(Locals) :- + must_be(list, Locals), + ( maplist(verify_local, Locals) -> true + ; domain_error(locals_decl_list, Locals, [verify_locals/1]) + ). + +verify_local(let(Var, Type, Init)) :- + must_be(var, Var), + must_be(atom, Type), + ground(Init). + +allocate_locals([]). +allocate_locals([let(Var, Type, Init) | Ls]) :- + allocate(rust, Type, Init , Var), + (catch(allocate_locals(Ls), E, (deallocate_locals([let(Var, Type, Init)]), throw(E))) -> true + ; deallocate_locals(let(Var, Type, Init)), false + ). + +deallocate_locals([]). +deallocate_locals([let(Var, Type, _) | Ls]) :- + deallocate(rust, Type, Var), + deallocate_locals(Ls). From 4b5e4a2745f017e6bc7a0bbaece5b4d050084039 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 18:35:25 +0200 Subject: [PATCH 05/24] fix some things in ffi.pl thanks triska for pointing out most of these --- src/lib/ffi.pl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index acf1d193..8e22e1b3 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -52,6 +52,8 @@ And a new window should pop up! :- use_module(library(lists)). :- use_module(library(error)). +:- use_module(library(format)). +:- use_module(library(dcgs)). %% foreign_struct(+Name, +Elements). % @@ -130,11 +132,11 @@ deallocate(Allocator, Type, Ptr) :- :- dynamic(is_array_type_defined/1). array_type(ElemType, Len, ArrayType) :- - phrase(format("$[~a;~d]", [ElemType, Len]), ArrayTypeName), + phrase(format_("$[~a;~d]", [ElemType, Len]), ArrayTypeName), atom_chars(ArrayType, ArrayTypeName), (is_array_type_defined(ArrayType) -> true ; length(Fields, Len), - maplist('='(ElemType), Fields), + maplist(=(ElemType), Fields), foreign_struct(ArrayType, Fields), assertz(is_array_type_defined(ArrayType)) ). @@ -142,10 +144,9 @@ array_type(ElemType, Len, ArrayType) :- with_locals(Locals, Goal) :- verify_locals(Locals), allocate_locals(Locals), - ( catch(Goal, E, (deallocate(Locals), throw(E), false)) -> Success = true - ; Success = false - ), - deallocate_locals(Locals). + ( catch(Goal, E, (deallocate(Locals), throw(E))) -> deallocate_locals(Locals) + ; deallocate_locals(Locals), false + ). verify_locals(Locals) :- must_be(list, Locals), @@ -162,7 +163,7 @@ allocate_locals([]). allocate_locals([let(Var, Type, Init) | Ls]) :- allocate(rust, Type, Init , Var), (catch(allocate_locals(Ls), E, (deallocate_locals([let(Var, Type, Init)]), throw(E))) -> true - ; deallocate_locals(let(Var, Type, Init)), false + ; deallocate_locals([let(Var, Type, Init)]), false ). deallocate_locals([]). From edd6d44bd664a36ef216941415ae52045d71666d Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 18:40:57 +0200 Subject: [PATCH 06/24] require array_length to be > 0 C does not have 0-sized types --- src/lib/ffi.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 8e22e1b3..e3394ca5 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -132,6 +132,7 @@ deallocate(Allocator, Type, Ptr) :- :- dynamic(is_array_type_defined/1). array_type(ElemType, Len, ArrayType) :- + (Len =< 0 -> domain_error(greater_than_zero, Len, array_type/3); true), phrase(format_("$[~a;~d]", [ElemType, Len]), ArrayTypeName), atom_chars(ArrayType, ArrayTypeName), (is_array_type_defined(ArrayType) -> true From 7b2bf73ba1505bf38840ae77be1e380745b61ca2 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 18:47:53 +0200 Subject: [PATCH 07/24] fix with_locals --- src/lib/ffi.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index e3394ca5..ab981e82 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -145,7 +145,7 @@ array_type(ElemType, Len, ArrayType) :- with_locals(Locals, Goal) :- verify_locals(Locals), allocate_locals(Locals), - ( catch(Goal, E, (deallocate(Locals), throw(E))) -> deallocate_locals(Locals) + ( catch(Goal, E, (deallocate_locals(Locals), throw(E))) -> deallocate_locals(Locals) ; deallocate_locals(Locals), false ). From d1356db7e34aad243aef0ec480841c648605204b Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 21:10:28 +0200 Subject: [PATCH 08/24] include a culprit in ffi_error --- src/machine/machine_errors.rs | 4 ++-- src/machine/system_calls.rs | 31 +++++++++++++++++-------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index 496d8085..208dc46e 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -613,7 +613,7 @@ impl MachineState { } #[cfg(feature = "ffi")] - pub(super) fn ffi_error(&self, err: FfiError) -> MachineError { + pub(super) fn ffi_error(&self, err: FfiError, culprit: HeapCellValue) -> MachineError { let error_atom = match err { FfiError::ValueCast => atom!("value_cast"), FfiError::ValueOutOfRange => atom!("value_out_of_range"), @@ -628,7 +628,7 @@ impl MachineState { FfiError::LayoutError => atom!("layout_error"), FfiError::UnsupportedAbi => atom!("unsupported_abi"), }; - let stub = functor!(atom!("ffi_error"), [atom_as_cell(error_atom)]); + let stub = functor!(atom!("ffi_error"), [atom_as_cell(error_atom), cell(culprit)]); MachineError { stub, diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 88b0af4d..2d8b230e 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5026,10 +5026,10 @@ impl Machine { )) } else { // empty list is an invalid struct repr - Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidStruct), stub_gen())) + Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidStruct, source), stub_gen())) } } else { - Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidArgument), stub_gen())) + Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidArgument, source), stub_gen())) } } @@ -5041,10 +5041,10 @@ impl Machine { } - let function_name = self.deref_register(1); + let function_name_arg = 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) { + if let Some(function_name) = self.machine_st.value_to_str_like(function_name_arg) { match self.machine_st.try_from_list(args_reg, stub_gen) { Ok(args) => { let args = args @@ -5061,7 +5061,7 @@ impl Machine { return self.unify_ffi_result(return_value, result); } Err(e) => { - let err = self.machine_st.ffi_error(e); + let err = self.machine_st.ffi_error(e, function_name_arg); return Err(self.machine_st.error_form(err, stub_gen())); } } @@ -5141,9 +5141,9 @@ impl Machine { #[cfg(feature = "ffi")] #[inline(always)] pub(crate) fn define_foreign_struct(&mut self) -> CallResult { - let struct_name = self.deref_register(1); + let struct_name_arg = self.deref_register(1); let fields_reg = self.deref_register(2); - if let Some(struct_name) = self.machine_st.value_to_str_like(struct_name) { + if let Some(struct_name) = self.machine_st.value_to_str_like(struct_name_arg) { let stub_gen = || functor_stub(atom!("define_foreign_struct"), 2); let fields: Vec = match self.machine_st.try_from_list(fields_reg, stub_gen) { Ok(addrs) => { @@ -5158,7 +5158,7 @@ impl Machine { self.foreign_function_table .define_struct(&struct_name.as_str(), fields) .map_err(|err| { - let ffi_error = self.machine_st.ffi_error(err); + let ffi_error = self.machine_st.ffi_error(err, struct_name_arg); self.machine_st.error_form(ffi_error, stub_gen()) })?; return Ok(()); @@ -5171,7 +5171,8 @@ impl Machine { 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 ffi_type_arg = self.deref_register(2); + let ffi_type = ffi_type_arg.to_atom().unwrap(); let args = self.deref_register(3); let return_value = self.deref_register(4); @@ -5185,7 +5186,7 @@ impl Machine { 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); + let machine_error = self.machine_st.ffi_error(ffi_error, ffi_type_arg); return Err(self.machine_st.error_form(machine_error, stub_gen())); }, }; @@ -5196,14 +5197,15 @@ impl Machine { 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 ffi_type_arg = self.deref_register(1); + let ffi_type = ffi_type_arg.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); + let machine_error = self.machine_st.ffi_error(ffi_error, ffi_type_arg); self.machine_st.error_form(machine_error, stub_gen()) })?; @@ -5214,7 +5216,8 @@ impl Machine { 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 ffi_type_arg = self.deref_register(2); + let ffi_type = ffi_type_arg.to_atom().unwrap(); let ptr = self.deref_register(3); @@ -5228,7 +5231,7 @@ impl Machine { 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); + let machine_error = self.machine_st.ffi_error(ffi_error, ffi_type_arg); return Err(self.machine_st.error_form(machine_error, stub_gen())); }, } From 3e929511bbb010f8478722b238dd00df5d7c218e Mon Sep 17 00:00:00 2001 From: Skgland Date: Sat, 9 Aug 2025 23:04:13 +0200 Subject: [PATCH 09/24] add a test for with_locals --- tests-pl/ffi_locals.pl | 27 ++++++++++++++++++++++++ tests/scryer/cli/src_tests/ffi_locals.md | 7 ++++++ 2 files changed, 34 insertions(+) create mode 100644 tests-pl/ffi_locals.pl create mode 100644 tests/scryer/cli/src_tests/ffi_locals.md diff --git a/tests-pl/ffi_locals.pl b/tests-pl/ffi_locals.pl new file mode 100644 index 00000000..abd83dc0 --- /dev/null +++ b/tests-pl/ffi_locals.pl @@ -0,0 +1,27 @@ +:- use_module(library(ffi)). +:- use_module(library(format)). + +test :- + ffi:array_type(u64, 1, ArrayType1), + ffi:with_locals( + [ + let(_Local1, ArrayType1, [ArrayType1 , 42]) + ], + format("With Locals 1~n", []) + ), + ffi:array_type(u8, 4, ArrayType2), + ffi:with_locals( + [ + let(_Local2, ArrayType2, [ArrayType2 , 42, 13, 4, 12]) + ], + format("With Locals 2~n", []) + ), + ffi:array_type(u64, 1, ArrayType3), + ffi:with_locals( + [ + let(_Local3, ArrayType3, [ArrayType3 , 42]) + ], + format("With Locals 3~n", []) + ). + +:- initialization(test). diff --git a/tests/scryer/cli/src_tests/ffi_locals.md b/tests/scryer/cli/src_tests/ffi_locals.md new file mode 100644 index 00000000..1925e231 --- /dev/null +++ b/tests/scryer/cli/src_tests/ffi_locals.md @@ -0,0 +1,7 @@ +```trycmd +$ scryer-prolog -f --no-add-history tests-pl/ffi_locals.pl -g halt +With Locals 1 +With Locals 2 +With Locals 3 + +``` From cd777294b990d8cab35d8c68fdb12616fe41e402 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 10 Aug 2025 01:57:28 +0200 Subject: [PATCH 10/24] use setup_call_cleanup/3 for with_locals as suggested by triska --- src/lib/ffi.pl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index ab981e82..86c7bdc9 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -54,6 +54,7 @@ And a new window should pop up! :- use_module(library(error)). :- use_module(library(format)). :- use_module(library(dcgs)). +:- use_module(library(iso_ext)). %% foreign_struct(+Name, +Elements). % @@ -144,9 +145,10 @@ array_type(ElemType, Len, ArrayType) :- with_locals(Locals, Goal) :- verify_locals(Locals), - allocate_locals(Locals), - ( catch(Goal, E, (deallocate_locals(Locals), throw(E))) -> deallocate_locals(Locals) - ; deallocate_locals(Locals), false + setup_call_cleanup( + allocate_locals(Locals), + Goal, + deallocate_locals(Locals) ). verify_locals(Locals) :- From f4297e365f26d468d61b8a7015121fe99eb30bcc Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 11 Aug 2025 23:20:48 +0200 Subject: [PATCH 11/24] don't crash on empty list and differentiate list head not being string like --- src/machine/system_calls.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 2d8b230e..8ba4fd4c 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5018,15 +5018,21 @@ impl Machine { // 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::>()?, - )) + + if let Some(head) = iter.next() { + if let Some(struct_name) = machine_st.value_to_str_like(head) { + Ok(Value::Struct( + struct_name.as_str().to_string(), + iter.map(|x| Self::map_ffi_arg(machine_st, x, stub_gen)) + .collect::>()?, + )) + } else { + // first element of a struct needs to be the type + Err(machine_st.error_form(machine_st.ffi_error(FfiError::ValueOutOfRange, head), stub_gen())) + } } else { // empty list is an invalid struct repr - Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidStruct, source), stub_gen())) + Err(machine_st.error_form(machine_st.ffi_error(FfiError::ValueOutOfRange, source), stub_gen())) } } else { Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidArgument, source), stub_gen())) From eba681786d214f423abcdb876b74b6b5f872ca2a Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 12 Aug 2025 23:29:13 +0200 Subject: [PATCH 12/24] make map_ffi_args a method and throw an instantiation error when encountering a variable --- src/machine/system_calls.rs | 46 ++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 8ba4fd4c..038f4d67 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5004,15 +5004,15 @@ impl Machine { } fn map_ffi_arg( - machine_st: &mut MachineState, + &mut self, source: HeapCellValue, stub_gen: impl Copy + Fn() -> MachineStub ) -> CallResult { - if let Ok(number) = Number::try_from((source, &machine_st.arena.f64_tbl)) { + if let Ok(number) = Number::try_from((source, &self.machine_st.arena.f64_tbl)) { Ok(Value::Number(number)) - } else if let Some(string) = machine_st.value_to_str_like(source) { + } else if let Some(string) = self.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) { + } else if let Ok(args) = self.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 @@ -5020,22 +5020,42 @@ impl Machine { let mut iter = args.into_iter(); if let Some(head) = iter.next() { - if let Some(struct_name) = machine_st.value_to_str_like(head) { + if let Some(struct_name) = self.machine_st.value_to_str_like(head) { Ok(Value::Struct( struct_name.as_str().to_string(), - iter.map(|x| Self::map_ffi_arg(machine_st, x, stub_gen)) + iter.map(|x| self.map_ffi_arg( x, stub_gen)) .collect::>()?, )) + } else if self.machine_st.deref(head).is_var() { + let err = self.machine_st.instantiation_error(); + + let src = stub_gen(); + + let culprit = functor!(atom!("-"), [atom_as_cell((atom!("var"))), cell(head)]); + + let src = functor!(atom!("."), [functor(culprit), list([functor(src)])]); + + Err(self.machine_st.error_form(err, src)) } else { // first element of a struct needs to be the type - Err(machine_st.error_form(machine_st.ffi_error(FfiError::ValueOutOfRange, head), stub_gen())) + Err(self.machine_st.error_form(self.machine_st.ffi_error(FfiError::ValueOutOfRange, head), stub_gen())) } } else { // empty list is an invalid struct repr - Err(machine_st.error_form(machine_st.ffi_error(FfiError::ValueOutOfRange, source), stub_gen())) + Err(self.machine_st.error_form(self.machine_st.ffi_error(FfiError::ValueOutOfRange, source), stub_gen())) } + } else if self.machine_st.deref(source).is_var() { + let err = self.machine_st.instantiation_error(); + + let src = stub_gen(); + + let culprit = functor!(atom!("-"), [atom_as_cell((atom!("var"))), cell(source)]); + + let src = functor!(atom!("."), [functor(culprit), list([functor(src)])]); + + Err(self.machine_st.error_form(err, src)) } else { - Err(machine_st.error_form(machine_st.ffi_error(FfiError::InvalidArgument, source), stub_gen())) + Err(self.machine_st.error_form(self.machine_st.ffi_error(FfiError::InvalidArgument, source), stub_gen())) } } @@ -5055,7 +5075,7 @@ impl Machine { Ok(args) => { let args = args .into_iter() - .map(|x| Self::map_ffi_arg(&mut self.machine_st, x, stub_gen)) + .map(|x| self.map_ffi_arg( x, stub_gen)) .collect::, _>>()?; match self.foreign_function_table.exec( @@ -5187,7 +5207,7 @@ impl Machine { self.machine_st.error_form(machine_error, stub_gen()) })?; - let args = Self::map_ffi_arg(&mut self.machine_st, args, stub_gen)?; + let args = self.map_ffi_arg( args, stub_gen)?; let value = match self.foreign_function_table.allocate(allocator, ffi_type, args, &mut self.machine_st.arena) { Ok(value) => value, @@ -5208,7 +5228,7 @@ impl Machine { 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 ptr = self.map_ffi_arg(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, ffi_type_arg); @@ -5232,7 +5252,7 @@ impl Machine { self.machine_st.error_form(machine_error, stub_gen()) })?; - let ptr = Self::map_ffi_arg(&mut self.machine_st, ptr, stub_gen)?; + let ptr = self.map_ffi_arg(ptr, stub_gen)?; match self.foreign_function_table.deallocate(allocator, ffi_type, ptr) { Ok(value) => value, From 176858ad42807d6ae6237b5e111caa33eff8a4ac Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 17 Aug 2025 01:39:12 +0200 Subject: [PATCH 13/24] fix map_ffi_arg --- src/machine/system_calls.rs | 70 ++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 038f4d67..442fd131 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5006,8 +5006,9 @@ impl Machine { fn map_ffi_arg( &mut self, source: HeapCellValue, - stub_gen: impl Copy + Fn() -> MachineStub + stub_gen: impl Copy + Fn() -> MachineStub, ) -> CallResult { + let source = self.machine_st.store(self.machine_st.deref(source)); if let Ok(number) = Number::try_from((source, &self.machine_st.arena.f64_tbl)) { Ok(Value::Number(number)) } else if let Some(string) = self.machine_st.value_to_str_like(source) { @@ -5020,13 +5021,14 @@ impl Machine { let mut iter = args.into_iter(); if let Some(head) = iter.next() { + let head = self.machine_st.store(self.machine_st.deref(head)); if let Some(struct_name) = self.machine_st.value_to_str_like(head) { Ok(Value::Struct( struct_name.as_str().to_string(), - iter.map(|x| self.map_ffi_arg( x, stub_gen)) + iter.map(|x| self.map_ffi_arg(x, stub_gen)) .collect::>()?, )) - } else if self.machine_st.deref(head).is_var() { + } else if head.is_var() { let err = self.machine_st.instantiation_error(); let src = stub_gen(); @@ -5038,11 +5040,17 @@ impl Machine { Err(self.machine_st.error_form(err, src)) } else { // first element of a struct needs to be the type - Err(self.machine_st.error_form(self.machine_st.ffi_error(FfiError::ValueOutOfRange, head), stub_gen())) + Err(self.machine_st.error_form( + self.machine_st.ffi_error(FfiError::ValueOutOfRange, head), + stub_gen(), + )) } } else { // empty list is an invalid struct repr - Err(self.machine_st.error_form(self.machine_st.ffi_error(FfiError::ValueOutOfRange, source), stub_gen())) + Err(self.machine_st.error_form( + self.machine_st.ffi_error(FfiError::ValueOutOfRange, source), + stub_gen(), + )) } } else if self.machine_st.deref(source).is_var() { let err = self.machine_st.instantiation_error(); @@ -5055,7 +5063,10 @@ impl Machine { Err(self.machine_st.error_form(err, src)) } else { - Err(self.machine_st.error_form(self.machine_st.ffi_error(FfiError::InvalidArgument, source), stub_gen())) + Err(self.machine_st.error_form( + self.machine_st.ffi_error(FfiError::InvalidArgument, source), + stub_gen(), + )) } } @@ -5066,7 +5077,6 @@ impl Machine { functor_stub(atom!("foreign_call"), 3) } - let function_name_arg = self.deref_register(1); let args_reg = self.deref_register(2); let return_value = self.deref_register(3); @@ -5075,7 +5085,7 @@ impl Machine { Ok(args) => { let args = args .into_iter() - .map(|x| self.map_ffi_arg( x, stub_gen)) + .map(|x| self.map_ffi_arg(x, stub_gen)) .collect::, _>>()?; match self.foreign_function_table.exec( @@ -5114,15 +5124,11 @@ impl Machine { self.machine_st .unify_rational(typed_arena_ptr, return_value); } - Number::Fixnum(fixnum) => { - self.machine_st.unify_fixnum(fixnum, 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) - ); + let struct_value = + resource_error_call_result!(self.machine_st, self.build_struct(&name, args)); unify!(self.machine_st, return_value, struct_value); } @@ -5203,18 +5209,25 @@ impl Machine { 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); + 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( args, stub_gen)?; + let args = self.map_ffi_arg(args, stub_gen)?; - let value = match self.foreign_function_table.allocate(allocator, ffi_type, args, &mut self.machine_st.arena) { + 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, ffi_type_arg); return Err(self.machine_st.error_form(machine_error, stub_gen())); - }, + } }; self.unify_ffi_result(return_value, value) @@ -5230,10 +5243,13 @@ impl Machine { let ptr = self.map_ffi_arg(ptr, stub_gen)?; - let value = self.foreign_function_table.read_ptr(ffi_type, ptr, &mut self.machine_st.arena).map_err(|ffi_error| { + 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, ffi_type_arg); self.machine_st.error_form(machine_error, stub_gen()) - })?; + })?; self.unify_ffi_result(return_value, value) } @@ -5246,20 +5262,24 @@ impl Machine { let ffi_type = ffi_type_arg.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); + 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(ptr, stub_gen)?; - match self.foreign_function_table.deallocate(allocator, ffi_type, ptr) { + 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, ffi_type_arg); return Err(self.machine_st.error_form(machine_error, stub_gen())); - }, + } } Ok(()) From 3b5b768f4a1d3b3d3cf71f1c6e68a73aeba1a237 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 17 Aug 2025 17:35:57 +0200 Subject: [PATCH 14/24] make ffi error structure not found point to the correct culprit --- src/ffi.rs | 254 +++++++++++++--------------------- src/machine/machine_errors.rs | 20 ++- 2 files changed, 111 insertions(+), 163 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 0cb38189..3bd27557 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -104,7 +104,7 @@ impl FunctionImpl { ) -> Result { let struct_type = structs_table .get(&*return_type_name.as_str()) - .ok_or(FfiError::StructNotFound)?; + .ok_or(FfiError::StructNotFound(return_type_name))?; let ffi_type = unsafe { *struct_type.ffi_type.as_raw_ptr() }; let layout = Layout::from_size_align(ffi_type.size, ffi_type.alignment.into()) @@ -175,10 +175,8 @@ struct StructImpl { } impl StructImpl { - - fn layout(&self) -> Result { - let ffi_type = unsafe {*self.ffi_type.as_raw_ptr()}; + 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) } @@ -190,10 +188,7 @@ impl StructImpl { ) -> Result { let args = ArgValue::build_args(struct_args, &self.fields, structs_table)?; - let alloc = FfiStruct::new( - self.layout()?, - FfiAllocator::Rust - )?; + let alloc = FfiStruct::new(self.layout()?, FfiAllocator::Rust)?; let Ok(mut current_layout) = Layout::from_size_align(0, 1) else { return Err(FfiError::LayoutError); @@ -321,7 +316,7 @@ impl StructImpl { FfiType::F64 => read_float::(ptr, &mut layout), FfiType::Struct(substruct) => { let Some(substruct_type) = struct_table.get(&*substruct.as_str()) else { - return Err(FfiError::StructNotFound); + return Err(FfiError::StructNotFound(*substruct)); }; let ffi_type = *substruct_type.ffi_type.as_raw_ptr(); @@ -350,7 +345,6 @@ impl StructImpl { } } - struct PointerArgs<'a, 'val> { memory: Vec, phantom: PhantomData<&'a mut ArgValue<'val>>, @@ -451,7 +445,7 @@ impl FfiType { Self::F64 => libffi::middle::Type::f64(), Self::Struct(struct_name) => structs_table .get(&*struct_name.as_str()) - .ok_or(FfiError::StructNotFound)? + .ok_or(FfiError::StructNotFound(struct_name))? .ffi_type .clone(), }) @@ -500,7 +494,7 @@ impl<'val> ArgValue<'val> { } let Some(struct_type) = structs_table.get(name) else { - return Err(FfiError::StructNotFound); + return Err(FfiError::StructNotFound(*atom)); }; Ok(Self::Struct(struct_type.build(structs_table, args)?)) @@ -534,7 +528,7 @@ struct FfiStruct { #[derive(Debug, Clone, Copy)] pub(crate) enum FfiAllocator { Rust, - C + C, } impl TryFrom for FfiAllocator { @@ -544,24 +538,19 @@ impl TryFrom for FfiAllocator { match value { atom!("rust") => Ok(Self::Rust), atom!("c") => Ok(Self::C), - _ => Err(()) + _ => 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()) } - }, + FfiAllocator::Rust => unsafe { alloc::alloc(layout).cast() }, + FfiAllocator::C => unsafe { libc::malloc(layout.size()) }, }; NonNull::new(ptr).ok_or(FfiError::AllocationFailed) @@ -574,15 +563,19 @@ impl FfiAllocator { 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())}, + 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}) + assert_ne!(layout.size(), 0); + Ok(FfiStruct { + ptr: unsafe { allocator.alloc(layout) }?, + layout, + allocator, + }) } } @@ -684,17 +677,23 @@ impl ForeignFunctionTable { allocator: FfiAllocator, kind: Atom, mut args: Value, - arena: &mut Arena + arena: &mut Arena, ) -> Result { - - fn allocate_primitive(allocator: FfiAllocator, initial_value: T, arena: &mut Arena) -> Result { - const { assert!(std::mem::size_of::() != 0)}; + 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))) + Ok(Value::Number(fixnum!( + Number, + ptr.as_ptr().expose_provenance(), + arena + ))) } - match FfiType::from_atom(&kind) { FfiType::Void => Err(FfiError::InvalidFfiType), FfiType::Bool => { @@ -705,72 +704,46 @@ impl ForeignFunctionTable { _ => 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::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) + 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))) - }, + 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 + arena: &mut Arena, ) -> Result { - - unsafe fn read_int( - ptr: NonNull, - arena: &mut Arena, - ) -> Value + unsafe fn read_int(ptr: NonNull, arena: &mut Arena) -> Value where T: Copy + TryInto + MightNotFitInFixnum, Integer: From, @@ -782,128 +755,87 @@ impl ForeignFunctionTable { let ptr = ptr.as_ptr()?; let Some(ptr) = NonNull::new(ptr) else { - return Err(FfiError::ValueOutOfRange) + 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::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::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) + 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)}; + 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) + 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::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) + return Err(FfiError::InvalidStruct); }; let layout = struct_impl.layout()?; - drop(FfiStruct { ptr, layout, allocator}) - }, + drop(FfiStruct { + ptr, + layout, + allocator, + }) + } } Ok(()) } @@ -971,7 +903,7 @@ pub enum FfiError { InvalidFfiType, InvalidStruct, FunctionNotFound, - StructNotFound, + StructNotFound(Atom), ArgCountMismatch, AllocationFailed, // LayoutError should never occour diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index 208dc46e..b8e23981 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -622,13 +622,29 @@ impl MachineState { FfiError::InvalidArgument => atom!("invalid_argument"), FfiError::InvalidStruct => atom!("invalid_struct"), FfiError::FunctionNotFound => atom!("function_not_found"), - FfiError::StructNotFound => atom!("struct_not_found"), + FfiError::StructNotFound(culprit) => { + let stub = functor!( + atom!("ffi_error"), + [ + atom_as_cell((atom!("struct_not_found"))), + atom_as_cell(culprit) + ] + ); + + return MachineError { + stub, + location: None, + }; + } FfiError::ArgCountMismatch => atom!("mismatched_argument_count"), FfiError::AllocationFailed => atom!("allocation_failed"), FfiError::LayoutError => atom!("layout_error"), FfiError::UnsupportedAbi => atom!("unsupported_abi"), }; - let stub = functor!(atom!("ffi_error"), [atom_as_cell(error_atom), cell(culprit)]); + let stub = functor!( + atom!("ffi_error"), + [atom_as_cell(error_atom), cell(culprit)] + ); MachineError { stub, From 8bc8171ec78f34ad196c356b1f16cb7268228e2b Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 17 Aug 2025 18:22:22 +0200 Subject: [PATCH 15/24] fix define_foreign_struct --- src/machine/system_calls.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 442fd131..3f6d12ea 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -5173,15 +5173,29 @@ impl Machine { #[cfg(feature = "ffi")] #[inline(always)] pub(crate) fn define_foreign_struct(&mut self) -> CallResult { + fn stub_gen() -> MachineStub { + functor_stub(atom!("$define_foreign_struct"), 2) + } + let struct_name_arg = self.deref_register(1); let fields_reg = self.deref_register(2); if let Some(struct_name) = self.machine_st.value_to_str_like(struct_name_arg) { - let stub_gen = || functor_stub(atom!("define_foreign_struct"), 2); let fields: Vec = match self.machine_st.try_from_list(fields_reg, stub_gen) { Ok(addrs) => { let mut args = Vec::new(); for heap_cell in addrs { - args.push(cell_as_atom_cell!(heap_cell).get_name()); + let arg_cell = self.machine_st.store(self.machine_st.deref(heap_cell)); + let Some(arg) = arg_cell.to_atom() else { + let err = if arg_cell.is_var() { + self.machine_st.instantiation_error() + } else { + self.machine_st.type_error(ValidType::Atom, heap_cell) + }; + + return Err(self.machine_st.error_form(err, stub_gen())); + }; + + args.push(arg); } args } From 80bf276e09c36411f576803d00831e2f56328313 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 24 Aug 2025 19:57:54 +0200 Subject: [PATCH 16/24] run rustfmt --- build/static_string_indexing.rs | 2 +- src/machine/machine_state.rs | 2 +- tests/scryer/ffi.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/build/static_string_indexing.rs b/build/static_string_indexing.rs index 1a00e17b..b9c4d1cd 100644 --- a/build/static_string_indexing.rs +++ b/build/static_string_indexing.rs @@ -134,7 +134,7 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea Ok(s) => s, Err(e) => { println!("cargo::warning=parse error: {e} in file {path:?}"); - syn::File{ + syn::File { shebang: None, attrs: vec![], items: vec![], diff --git a/src/machine/machine_state.rs b/src/machine/machine_state.rs index 84e7759f..c1de6bf8 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>; +pub type CallResult = Result>; // size may be an upper bound. // true_size is calculated to compute the exact offset. diff --git a/tests/scryer/ffi.rs b/tests/scryer/ffi.rs index c86360e9..7340d508 100644 --- a/tests/scryer/ffi.rs +++ b/tests/scryer/ffi.rs @@ -284,8 +284,6 @@ fn ffi_cstr() { ); } - - #[test] #[cfg_attr(miri, ignore = "ffi")] fn ffi_heap() { From ce9b41815d4643492e379a431235215abf586e18 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 24 Aug 2025 20:23:17 +0200 Subject: [PATCH 17/24] add documentation --- src/lib/ffi.pl | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 86c7bdc9..8bbc55ff 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -72,6 +72,21 @@ And a new window should pop up! foreign_struct(Name, Elements) :- '$define_foreign_struct'(Name, Elements). + +%% use_foreign_module(+LibName, +Predicates) +% +% - LibName the path to the shared library to load/bind +% - Predicates list of function definitions +% +% Each function definition is a functor of arity 2. +% The functor name is the name of the function to bind, +% the first argument is the list of arguments of the function, +% the second argument is the return type of the function. +% +% This will define a predicate in the ffi module with the defined name, +% for void and bool return type functions the arity will match the length of the arguments list, +% for other return types there will be an additional out parameter. +% use_foreign_module(LibName, Predicates) :- '$load_foreign_lib'(LibName, Predicates), maplist(assert_predicate, Predicates). @@ -112,19 +127,34 @@ assert_predicate(PredicateDefinition) :- Predicate = (Head:-Body), assertz(ffi:Predicate). - +%% allocate(+Allocator, +Type, +Args, -Ptr) +% +% Using the Allocator allocate Type initialized with Args and +% unify Ptr with a pointer to that allocation. +% 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) +% +% Read a value of Type from the pointer Ptr and unify the read value with Value +% +% For type cstr take read a nul-terminated utf-8 string starting at 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) +% +% Deallocate the allocation at Ptr of Type allocated with Allocator +% deallocate(Allocator, Type, Ptr) :- must_be(atom, Allocator), must_be(integer, Ptr), @@ -132,6 +162,10 @@ deallocate(Allocator, Type, Ptr) :- :- dynamic(is_array_type_defined/1). +%% array_type(+ElemType, +Len, -ArrayType) +% +% unify the ffi type for an array of lenth Len with element type ElemType with ArrayType +% array_type(ElemType, Len, ArrayType) :- (Len =< 0 -> domain_error(greater_than_zero, Len, array_type/3); true), phrase(format_("$[~a;~d]", [ElemType, Len]), ArrayTypeName), @@ -143,6 +177,14 @@ array_type(ElemType, Len, ArrayType) :- assertz(is_array_type_defined(ArrayType)) ). +%% with_locals(+Locals, :Goal) +% +% Allocate the Locals, evaluate the Goal and deallocate the Locals. +% The Locals will also be cleandup when Goal fails or throws an error. +% +% Locals is a list of local variable definitions let(-Ptr, +Type, +Args). +% Ptr will be unified with the pointer to the local of Type initialized with Args. +% with_locals(Locals, Goal) :- verify_locals(Locals), setup_call_cleanup( From 1f7a60dec91fa499eb7b8e8c2a94e83a87387bb5 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 24 Aug 2025 20:25:41 +0200 Subject: [PATCH 18/24] add meta_predicate declaration for with_locals/2 --- src/lib/ffi.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 8bbc55ff..a03308a7 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -177,6 +177,8 @@ array_type(ElemType, Len, ArrayType) :- assertz(is_array_type_defined(ArrayType)) ). +:- meta_predicate(with_locals(?, 0)). + %% with_locals(+Locals, :Goal) % % Allocate the Locals, evaluate the Goal and deallocate the Locals. From 6f3f66c4076968c8065b219f409b5f8c681731b9 Mon Sep 17 00:00:00 2001 From: Skgland Date: Sun, 24 Aug 2025 21:08:52 +0200 Subject: [PATCH 19/24] fix builds without ffi feature --- src/machine/dispatch.rs | 12 - src/machine/machine_errors.rs | 14 ++ src/machine/system_calls.rs | 413 +++++++++++++++++++--------------- 3 files changed, 251 insertions(+), 188 deletions(-) diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 9143f2e1..4d3c7666 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4302,62 +4302,50 @@ impl Machine { step_or_fail!(self, self.machine_st.p = self.machine_st.cp); } &Instruction::CallLoadForeignLib => { - #[cfg(feature = "ffi")] try_or_throw!(self.machine_st, self.load_foreign_lib()); step_or_fail!(self, self.machine_st.p += 1); } &Instruction::ExecuteLoadForeignLib => { - #[cfg(feature = "ffi")] try_or_throw!(self.machine_st, self.load_foreign_lib()); step_or_fail!(self, self.machine_st.p = self.machine_st.cp); } &Instruction::CallForeignCall => { - #[cfg(feature = "ffi")] try_or_throw!(self.machine_st, self.foreign_call()); step_or_fail!(self, self.machine_st.p += 1); } &Instruction::ExecuteForeignCall => { - #[cfg(feature = "ffi")] try_or_throw!(self.machine_st, self.foreign_call()); step_or_fail!(self, self.machine_st.p = self.machine_st.cp); } &Instruction::CallDefineForeignStruct => { - #[cfg(feature = "ffi")] try_or_throw!(self.machine_st, self.define_foreign_struct()); step_or_fail!(self, self.machine_st.p += 1); } &Instruction::ExecuteDefineForeignStruct => { - #[cfg(feature = "ffi")] 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); } diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index b8e23981..db16d377 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -603,6 +603,20 @@ impl MachineState { } } + pub(super) fn missing_feature_error(&self, feature: Atom) -> MachineError { + let stub = functor!( + atom!("resource_error"), + [functor( + (functor!(atom!("feature"), [atom_as_cell((feature))])) + )] + ); + + MachineError { + stub, + location: None, + } + } + pub(super) fn unreachable_error(&self) -> MachineError { let stub = functor!(atom!("system_error")); diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 3f6d12ea..6d98a0bb 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4952,57 +4952,69 @@ impl Machine { Ok(()) } - #[cfg(feature = "ffi")] #[inline(always)] pub(crate) fn load_foreign_lib(&mut self) -> CallResult { - let library_name = self.deref_register(1); - let args_reg = self.deref_register(2); - if let Some(library_name) = self.machine_st.value_to_str_like(library_name) { - let stub_gen = || functor_stub(atom!("use_foreign_module"), 2); - match self.machine_st.try_from_list(args_reg, stub_gen) { - Ok(addrs) => { - let mut functions = Vec::new(); - for heap_cell in addrs { - read_heap_cell!(heap_cell, - (HeapCellValueTag::Str, s) => { - let name = cell_as_atom_cell!(self.machine_st.heap[s]).get_name(); - let args: Vec = match self.machine_st.try_from_list(self.machine_st.heap[s + 1], stub_gen) { - Ok(addrs) => { - let mut args = Vec::new(); - for heap_cell in addrs { - args.push(cell_as_atom_cell!(heap_cell).get_name()); - } - args - } - Err(e) => return Err(e) - }; - let return_value = cell_as_atom_cell!(self.machine_st.heap[s + 2]); - functions.push(FunctionDefinition { - name: name.as_str().to_string(), - args, - return_value: return_value.get_name(), - }); - } - _ => { - unreachable!() - } - ) - } - if self - .foreign_function_table - .load_library(&library_name.as_str(), &functions) - .is_ok() - { - return Ok(()); - } - } - Err(e) => return Err(e), - }; + fn stub_gen() -> MachineStub { + functor_stub(atom!("$load_foreign_lib"), 2) + } + + #[cfg(feature = "ffi")] + { + let library_name = self.deref_register(1); + let args_reg = self.deref_register(2); + if let Some(library_name) = self.machine_st.value_to_str_like(library_name) { + match self.machine_st.try_from_list(args_reg, stub_gen) { + Ok(addrs) => { + let mut functions = Vec::new(); + for heap_cell in addrs { + read_heap_cell!(heap_cell, + (HeapCellValueTag::Str, s) => { + let name = cell_as_atom_cell!(self.machine_st.heap[s]).get_name(); + let args: Vec = match self.machine_st.try_from_list(self.machine_st.heap[s + 1], stub_gen) { + Ok(addrs) => { + let mut args = Vec::new(); + for heap_cell in addrs { + args.push(cell_as_atom_cell!(heap_cell).get_name()); + } + args + } + Err(e) => return Err(e) + }; + let return_value = cell_as_atom_cell!(self.machine_st.heap[s + 2]); + functions.push(FunctionDefinition { + name: name.as_str().to_string(), + args, + return_value: return_value.get_name(), + }); + } + _ => { + unreachable!() + } + ) + } + if self + .foreign_function_table + .load_library(&library_name.as_str(), &functions) + .is_ok() + { + return Ok(()); + } + } + Err(e) => return Err(e), + }; + } + self.machine_st.fail = true; + Ok(()) + } + + #[cfg(not(feature = "ffi"))] + { + let err = self.machine_st.missing_feature_error(atom!("ffi")); + Err(self.machine_st.error_form(err, stub_gen())) } - self.machine_st.fail = true; - Ok(()) } + #[cfg(feature = "ffi")] fn map_ffi_arg( &mut self, source: HeapCellValue, @@ -5070,46 +5082,55 @@ impl Machine { } } - #[cfg(feature = "ffi")] #[inline(always)] pub(crate) fn foreign_call(&mut self) -> CallResult { fn stub_gen() -> Vec { - functor_stub(atom!("foreign_call"), 3) + functor_stub(atom!("$foreign_call"), 3) } - let function_name_arg = 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_arg) { - match self.machine_st.try_from_list(args_reg, stub_gen) { - Ok(args) => { - let args = args - .into_iter() - .map(|x| self.map_ffi_arg(x, stub_gen)) - .collect::, _>>()?; + #[cfg(feature = "ffi")] + { + let function_name_arg = 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_arg) { + match self.machine_st.try_from_list(args_reg, stub_gen) { + Ok(args) => { + let args = args + .into_iter() + .map(|x| self.map_ffi_arg(x, stub_gen)) + .collect::, _>>()?; - match self.foreign_function_table.exec( - &function_name.as_str(), - args, - &mut self.machine_st.arena, - ) { - Ok(result) => { - return self.unify_ffi_result(return_value, result); - } - Err(e) => { - let err = self.machine_st.ffi_error(e, function_name_arg); - return Err(self.machine_st.error_form(err, stub_gen())); + match self.foreign_function_table.exec( + &function_name.as_str(), + args, + &mut self.machine_st.arena, + ) { + Ok(result) => { + return self.unify_ffi_result(return_value, result); + } + Err(e) => { + let err = self.machine_st.ffi_error(e, function_name_arg); + return Err(self.machine_st.error_form(err, stub_gen())); + } } } + Err(e) => return Err(e), } - Err(e) => return Err(e), } + + self.machine_st.fail = true; + Ok(()) } - self.machine_st.fail = true; - Ok(()) + #[cfg(not(feature = "ffi"))] + { + let err = self.machine_st.missing_feature_error(atom!("ffi")); + Err(self.machine_st.error_form(err, stub_gen())) + } } + #[cfg(feature = "ffi")] fn unify_ffi_result(&mut self, return_value: HeapCellValue, result: Value) -> CallResult { match result { Value::Number(n) => match n { @@ -5170,133 +5191,173 @@ impl Machine { sized_iter_to_heap_list(&mut self.machine_st.heap, cells.len(), cells.into_iter()) } - #[cfg(feature = "ffi")] #[inline(always)] pub(crate) fn define_foreign_struct(&mut self) -> CallResult { fn stub_gen() -> MachineStub { functor_stub(atom!("$define_foreign_struct"), 2) } - let struct_name_arg = self.deref_register(1); - let fields_reg = self.deref_register(2); - if let Some(struct_name) = self.machine_st.value_to_str_like(struct_name_arg) { - let fields: Vec = match self.machine_st.try_from_list(fields_reg, stub_gen) { - Ok(addrs) => { - let mut args = Vec::new(); - for heap_cell in addrs { - let arg_cell = self.machine_st.store(self.machine_st.deref(heap_cell)); - let Some(arg) = arg_cell.to_atom() else { - let err = if arg_cell.is_var() { - self.machine_st.instantiation_error() - } else { - self.machine_st.type_error(ValidType::Atom, heap_cell) + #[cfg(feature = "ffi")] + { + let struct_name_arg = self.deref_register(1); + let fields_reg = self.deref_register(2); + if let Some(struct_name) = self.machine_st.value_to_str_like(struct_name_arg) { + let fields: Vec = match self.machine_st.try_from_list(fields_reg, stub_gen) { + Ok(addrs) => { + let mut args = Vec::new(); + for heap_cell in addrs { + let arg_cell = self.machine_st.store(self.machine_st.deref(heap_cell)); + let Some(arg) = arg_cell.to_atom() else { + let err = if arg_cell.is_var() { + self.machine_st.instantiation_error() + } else { + self.machine_st.type_error(ValidType::Atom, heap_cell) + }; + + return Err(self.machine_st.error_form(err, stub_gen())); }; - return Err(self.machine_st.error_form(err, stub_gen())); - }; - - args.push(arg); + args.push(arg); + } + args } - args - } - Err(e) => return Err(e), - }; - self.foreign_function_table - .define_struct(&struct_name.as_str(), fields) - .map_err(|err| { - let ffi_error = self.machine_st.ffi_error(err, struct_name_arg); - self.machine_st.error_form(ffi_error, stub_gen()) - })?; - return Ok(()); + Err(e) => return Err(e), + }; + self.foreign_function_table + .define_struct(&struct_name.as_str(), fields) + .map_err(|err| { + let ffi_error = self.machine_st.ffi_error(err, struct_name_arg); + self.machine_st.error_form(ffi_error, stub_gen()) + })?; + return Ok(()); + } + self.machine_st.fail = true; + Ok(()) + } + + #[cfg(not(feature = "ffi"))] + { + let err = self.machine_st.missing_feature_error(atom!("ffi")); + Err(self.machine_st.error_form(err, stub_gen())) } - self.machine_st.fail = true; - Ok(()) } pub(crate) fn ffi_allocate(&mut self) -> CallResult { - let stub_gen = || functor_stub(atom!("$ffi_allocate"), 4); + fn stub_gen() -> MachineStub { + functor_stub(atom!("$ffi_allocate"), 4) + } - let allocator = self.deref_register(1); - let ffi_type_arg = self.deref_register(2); - let ffi_type = ffi_type_arg.to_atom().unwrap(); - let args = self.deref_register(3); - let return_value = self.deref_register(4); + #[cfg(feature = "ffi")] + { + let allocator = self.deref_register(1); + let ffi_type_arg = self.deref_register(2); + let ffi_type = ffi_type_arg.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(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, ffi_type_arg); - 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_arg = self.deref_register(1); - let ffi_type = ffi_type_arg.to_atom().unwrap(); - let ptr = self.deref_register(2); - let return_value = self.deref_register(3); - - let ptr = self.map_ffi_arg(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, ffi_type_arg); + 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()) })?; - self.unify_ffi_result(return_value, value) + let args = self.map_ffi_arg(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, ffi_type_arg); + return Err(self.machine_st.error_form(machine_error, stub_gen())); + } + }; + + self.unify_ffi_result(return_value, value) + } + + #[cfg(not(feature = "ffi"))] + { + let err = self.machine_st.missing_feature_error(atom!("ffi")); + Err(self.machine_st.error_form(err, stub_gen())) + } + } + + pub(crate) fn ffi_read_ptr(&mut self) -> CallResult { + fn stub_gen() -> MachineStub { + functor_stub(atom!("$ffi_read_ptr"), 3) + } + + #[cfg(feature = "ffi")] + { + let ffi_type_arg = self.deref_register(1); + let ffi_type = ffi_type_arg.to_atom().unwrap(); + let ptr = self.deref_register(2); + let return_value = self.deref_register(3); + + let ptr = self.map_ffi_arg(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, ffi_type_arg); + self.machine_st.error_form(machine_error, stub_gen()) + })?; + + self.unify_ffi_result(return_value, value) + } + + #[cfg(not(feature = "ffi"))] + { + let err = self.machine_st.missing_feature_error(atom!("ffi")); + Err(self.machine_st.error_form(err, stub_gen())) + } } 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_arg = self.deref_register(2); - let ffi_type = ffi_type_arg.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(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, ffi_type_arg); - return Err(self.machine_st.error_form(machine_error, stub_gen())); - } + fn stub_gen() -> MachineStub { + functor_stub(atom!("$ffi_deallocate"), 3) } - Ok(()) + #[cfg(feature = "ffi")] + { + let allocator = self.deref_register(1); + let ffi_type_arg = self.deref_register(2); + let ffi_type = ffi_type_arg.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(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, ffi_type_arg); + return Err(self.machine_st.error_form(machine_error, stub_gen())); + } + } + Ok(()) + } + + #[cfg(not(feature = "ffi"))] + { + let err = self.machine_st.missing_feature_error(atom!("ffi")); + Err(self.machine_st.error_form(err, stub_gen())) + } } #[cfg(not(target_arch = "wasm32"))] From a140720c6f78ffb1c4eb82664dd2e07fb9bdb933 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 25 Aug 2025 20:14:14 +0200 Subject: [PATCH 20/24] use representation_error rather than resource_error --- src/machine/machine_errors.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index db16d377..22afc03a 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -603,9 +603,10 @@ impl MachineState { } } + #[allow(dead_code)] // not used when all features are enabled pub(super) fn missing_feature_error(&self, feature: Atom) -> MachineError { let stub = functor!( - atom!("resource_error"), + atom!("representation_error"), [functor( (functor!(atom!("feature"), [atom_as_cell((feature))])) )] From 78c08b87b63d5c016b62f1bf59ca69aeb2d10ba5 Mon Sep 17 00:00:00 2001 From: Skgland Date: Mon, 25 Aug 2025 23:54:18 +0200 Subject: [PATCH 21/24] cleanup ffi error handling and use Atom instead of &str in appropriate places --- src/ffi.rs | 163 ++++++++++++++++++---------------- src/machine/machine_errors.rs | 133 ++++++++++++++++++++------- src/machine/machine_state.rs | 3 +- src/machine/system_calls.rs | 103 +++++++++++---------- 4 files changed, 241 insertions(+), 161 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 3bd27557..b780e7b3 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -22,6 +22,7 @@ and finally we add the pointer the size of what we've written. use crate::arena::Arena; use crate::atom_table::Atom; use crate::forms::Number; +use crate::machine::machine_errors::DomainErrorType; use crate::parser::ast::{Fixnum, MightNotFitInFixnum}; use dashu::Integer; @@ -39,7 +40,7 @@ use std::ops::Deref; use std::ptr::NonNull; pub struct FunctionDefinition { - pub name: String, + pub name: Atom, pub return_value: Atom, pub args: Vec, } @@ -100,10 +101,10 @@ impl FunctionImpl { return_type_name: Atom, args: &[Arg], arena: &mut Arena, - structs_table: &HashMap, + structs_table: &HashMap, ) -> Result { let struct_type = structs_table - .get(&*return_type_name.as_str()) + .get(&return_type_name) .ok_or(FfiError::StructNotFound(return_type_name))?; let ffi_type = unsafe { *struct_type.ffi_type.as_raw_ptr() }; @@ -121,12 +122,8 @@ impl FunctionImpl { ) }; - let struct_val = struct_type.read( - alloc.ptr.as_ptr(), - &return_type_name.as_str(), - structs_table, - arena, - ); + let struct_val = + struct_type.read(alloc.ptr.as_ptr(), return_type_name, structs_table, arena); drop(alloc); @@ -137,7 +134,7 @@ impl FunctionImpl { &self, args: &[Arg], arena: &mut Arena, - structs_table: &HashMap, + structs_table: &HashMap, ) -> Result { let call_fn: unsafe fn(&Self, &[Arg], &mut Arena) -> Result = match self.return_type { @@ -164,8 +161,8 @@ impl FunctionImpl { #[derive(Debug, Default)] pub struct ForeignFunctionTable { - table: HashMap, - structs: HashMap, + table: HashMap, + structs: HashMap, } #[derive(Clone, Debug)] @@ -183,7 +180,7 @@ impl StructImpl { fn build( &self, - structs_table: &HashMap, + structs_table: &HashMap, struct_args: &mut [Value], ) -> Result { let args = ArgValue::build_args(struct_args, &self.fields, structs_table)?; @@ -249,8 +246,8 @@ impl StructImpl { fn read( &self, ptr: *mut c_void, - struct_name: &str, - struct_table: &HashMap, + struct_name: Atom, + struct_table: &HashMap, arena: &mut Arena, ) -> Result { unsafe { @@ -315,7 +312,7 @@ impl StructImpl { FfiType::F32 => read_float::(ptr, &mut layout), FfiType::F64 => read_float::(ptr, &mut layout), FfiType::Struct(substruct) => { - let Some(substruct_type) = struct_table.get(&*substruct.as_str()) else { + let Some(substruct_type) = struct_table.get(substruct) else { return Err(FfiError::StructNotFound(*substruct)); }; @@ -328,19 +325,15 @@ impl StructImpl { .map_err(|_| FfiError::LayoutError)?; layout = new_layout; let field_ptr = ptr.byte_offset(offset as isize); - let struct_val = substruct_type.read( - field_ptr, - &substruct.as_str(), - struct_table, - arena, - )?; + let struct_val = + substruct_type.read(field_ptr, *substruct, struct_table, arena)?; Ok(struct_val) } - FfiType::Void => unreachable!("void is not a valid field type"), + FfiType::Void => return Err(FfiError::VoidArgumentType), }; returns.push(val?); } - Ok(Value::Struct(struct_name.to_string(), returns)) + Ok(Value::Struct(struct_name, returns)) } } } @@ -427,7 +420,7 @@ impl FfiType { } } - fn to_type(self, structs_table: &HashMap) -> Result { + fn to_type(self, structs_table: &HashMap) -> Result { Ok(match self { Self::I64 => libffi::middle::Type::i64(), Self::I32 => libffi::middle::Type::i32(), @@ -444,7 +437,7 @@ impl FfiType { Self::F32 => libffi::middle::Type::f32(), Self::F64 => libffi::middle::Type::f64(), Self::Struct(struct_name) => structs_table - .get(&*struct_name.as_str()) + .get(&struct_name) .ok_or(FfiError::StructNotFound(struct_name))? .ffi_type .clone(), @@ -471,7 +464,7 @@ impl<'val> ArgValue<'val> { fn new( val: &'val mut Value, arg_type: &FfiType, - structs_table: &HashMap, + structs_table: &HashMap, ) -> Result { match arg_type { FfiType::U8 => Ok(Self::U8(val.as_int()?)), @@ -486,27 +479,27 @@ impl<'val> ArgValue<'val> { 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()?; + FfiType::Struct(arg_type_name) => { + let (val_type_name, args) = val.as_struct()?; - if &*atom.as_str() != name { - return Err(FfiError::ValueCast); + if *arg_type_name != val_type_name { + return Err(FfiError::ValueCast(*arg_type_name, val_type_name)); } - let Some(struct_type) = structs_table.get(name) else { - return Err(FfiError::StructNotFound(*atom)); + let Some(struct_type) = structs_table.get(&val_type_name) else { + return Err(FfiError::StructNotFound(*arg_type_name)); }; Ok(Self::Struct(struct_type.build(structs_table, args)?)) } - FfiType::Void => Err(FfiError::InvalidArgumentType), + FfiType::Void => Err(FfiError::VoidArgumentType), } } fn build_args( args: &'val mut [Value], types: &[FfiType], - structs_table: &HashMap, + structs_table: &HashMap, ) -> Result, FfiError> { if types.len() != args.len() { return Err(FfiError::ArgCountMismatch); @@ -590,7 +583,7 @@ impl ForeignFunctionTable { self.table.extend(other.table); } - pub fn define_struct(&mut self, name: &str, atom_fields: Vec) -> Result<(), FfiError> { + pub fn define_struct(&mut self, name: Atom, atom_fields: Vec) -> Result<(), FfiError> { let fields: Vec<_> = atom_fields.iter().map(FfiType::from_atom).collect(); let struct_type = libffi::middle::Type::structure( fields @@ -612,7 +605,7 @@ impl ForeignFunctionTable { }; self.structs.insert( - name.to_string(), + name, StructImpl { ffi_type: struct_type, fields, @@ -629,7 +622,7 @@ impl ForeignFunctionTable { 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 symbol_name: CString = CString::new(&*function.name.as_str())?; 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(); @@ -643,7 +636,7 @@ impl ForeignFunctionTable { ); ff_table.table.insert( - function.name.clone(), + function.name, FunctionImpl { cif, args, @@ -659,11 +652,14 @@ impl ForeignFunctionTable { pub fn exec( &mut self, - fn_name: &str, + fn_name: Atom, mut args: Vec, arena: &mut Arena, ) -> Result { - let fn_impl = self.table.get(fn_name).ok_or(FfiError::FunctionNotFound)?; + let fn_impl = self + .table + .get(&fn_name) + .ok_or(FfiError::FunctionNotFound(fn_name))?; let args = ArgValue::build_args(&mut args, &fn_impl.args, &self.structs)?; @@ -695,13 +691,13 @@ impl ForeignFunctionTable { } match FfiType::from_atom(&kind) { - FfiType::Void => Err(FfiError::InvalidFfiType), + FfiType::Void => Err(FfiError::VoidArgumentType), FfiType::Bool => { let val = args.as_int::()?; let init = match val { 0 => false, 1 => true, - _ => return Err(FfiError::ValueOutOfRange), + _ => return Err(FfiError::ValueOutOfRange(DomainErrorType::ZeroOrOne, args)), }; allocate_primitive::(allocator, init, arena) } @@ -716,10 +712,10 @@ impl ForeignFunctionTable { 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::CStr => Err(FfiError::CStrFieldType), FfiType::Struct(_) => { - let Some(struct_impl) = self.structs.get(&*kind.as_str()) else { - return Err(FfiError::InvalidStruct); + let Some(struct_impl) = self.structs.get(&kind) else { + return Err(FfiError::StructNotFound(kind)); }; let (_, args) = args.as_struct()?; @@ -755,11 +751,11 @@ impl ForeignFunctionTable { let ptr = ptr.as_ptr()?; let Some(ptr) = NonNull::new(ptr) else { - return Err(FfiError::ValueOutOfRange); + return Err(FfiError::NullPtr); }; match FfiType::from_atom(&kind) { - FfiType::Void => Err(FfiError::InvalidFfiType), + FfiType::Void => Err(FfiError::VoidArgumentType), 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) }), @@ -782,11 +778,11 @@ impl ForeignFunctionTable { 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); + let Some(struct_impl) = self.structs.get(&kind) else { + return Err(FfiError::StructNotFound(kind)); }; - struct_impl.read(ptr.as_ptr(), &kind.as_str(), &self.structs, arena) + struct_impl.read(ptr.as_ptr(), kind, &self.structs, arena) } } } @@ -805,11 +801,11 @@ impl ForeignFunctionTable { let ptr = ptr.as_ptr()?; let Some(ptr) = NonNull::new(ptr) else { - return Err(FfiError::ValueOutOfRange); + return Err(FfiError::NullPtr); }; match FfiType::from_atom(&kind) { - FfiType::Void => return Err(FfiError::InvalidFfiType), + FfiType::Void => return Err(FfiError::VoidArgumentType), FfiType::Bool => deallocate_primitive::(allocator, ptr), FfiType::U8 => deallocate_primitive::(allocator, ptr), FfiType::I8 => deallocate_primitive::(allocator, ptr), @@ -822,10 +818,10 @@ impl ForeignFunctionTable { 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::CStr => return Err(FfiError::CStrFieldType), FfiType::Struct(_) => { - let Some(struct_impl) = self.structs.get(&*kind.as_str()) else { - return Err(FfiError::InvalidStruct); + let Some(struct_impl) = self.structs.get(&kind) else { + return Err(FfiError::StructNotFound(kind)); }; let layout = struct_impl.layout()?; @@ -845,7 +841,7 @@ impl ForeignFunctionTable { pub enum Value { Number(Number), CString(CString), - Struct(String, Vec), + Struct(Atom, Vec), } impl Value { @@ -857,22 +853,27 @@ impl Value { match self { Value::Number(Number::Integer(ibig_ptr)) => { let ibig: &Integer = ibig_ptr; - ibig.clone() - .try_into() - .map_err(|_| FfiError::ValueOutOfRange) + ibig.clone().try_into().map_err(|_| { + FfiError::ValueOutOfRange(DomainErrorType::FixedSizedInt, self.clone()) + }) } - Value::Number(Number::Fixnum(fixnum)) => fixnum - .get_num() - .try_into() - .map_err(|_| FfiError::ValueOutOfRange), - _ => Err(FfiError::ValueCast), + Value::Number(Number::Fixnum(fixnum)) => fixnum.get_num().try_into().map_err(|_| { + FfiError::ValueOutOfRange(DomainErrorType::FixedSizedInt, self.clone()) + }), + _ => Err(FfiError::ValueOutOfRange( + DomainErrorType::FixedSizedInt, + self.clone(), + )), } } fn as_float(&self) -> Result { match self { &Value::Number(Number::Float(OrderedFloat(f))) => Ok(f), - _ => Err(FfiError::ValueCast), + _ => Err(FfiError::ValueOutOfRange( + DomainErrorType::F64, + self.clone(), + )), } } @@ -882,33 +883,39 @@ impl Value { Value::Number(Number::Fixnum(fixnum)) => Ok(std::ptr::with_exposed_provenance_mut( fixnum.get_num() as usize, )), - _ => Err(FfiError::ValueCast), + _ => Err(FfiError::ValueOutOfRange( + DomainErrorType::PtrLike, + self.clone(), + )), } } - fn as_struct(&mut self) -> Result<(&str, &mut [Self]), FfiError> { + fn as_struct(&mut self) -> Result<(Atom, &mut [Self]), FfiError> { match self { - Value::Struct(name, values) => Ok((name, values)), - _ => Err(FfiError::ValueCast), + Value::Struct(name, values) => Ok((*name, values)), + _ => Err(FfiError::ValueOutOfRange( + DomainErrorType::FfiStruct, + self.clone(), + )), } } } #[derive(Debug)] pub enum FfiError { - ValueCast, - ValueOutOfRange, - InvalidArgumentType, - InvalidArgument, - InvalidFfiType, - InvalidStruct, - FunctionNotFound, + ValueCast(Atom, Atom), + ValueOutOfRange(DomainErrorType, Value), + VoidArgumentType, + FunctionNotFound(Atom), StructNotFound(Atom), ArgCountMismatch, AllocationFailed, // LayoutError should never occour LayoutError, + UnsupportedTypedef, UnsupportedAbi, + CStrFieldType, + NullPtr, } impl std::fmt::Display for FfiError { @@ -922,7 +929,7 @@ impl Error for FfiError {} impl From for FfiError { fn from(value: libffi::low::Error) -> Self { match value { - libffi::low::Error::Typedef => FfiError::InvalidFfiType, + libffi::low::Error::Typedef => FfiError::UnsupportedTypedef, libffi::low::Error::Abi => FfiError::UnsupportedAbi, } } diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index 22afc03a..1c28783c 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -3,7 +3,7 @@ use crate::atom_table::*; use crate::parser::ast::*; #[cfg(feature = "ffi")] -use crate::ffi::FfiError; +use crate::ffi::{self, FfiError}; use crate::forms::*; use crate::functor_macro::*; use crate::machine::heap::*; @@ -275,6 +275,30 @@ impl DomainError for MachineStub { } } +#[cfg(feature = "ffi")] +impl DomainError for ffi::Value { + fn domain_error(self, machine_st: &mut MachineState, error: DomainErrorType) -> MachineError { + use ffi::Value; + + match self { + Value::Number(number) => number.domain_error(machine_st, error), + Value::CString(cstring) => { + let str = cstring.to_string_lossy().into_owned(); + let stub = functor!( + atom!("domain_error"), + [atom_as_cell((error.as_atom())), string(str)] + ); + + MachineError { + stub, + location: None, + } + } + Value::Struct(atom, _values) => atom_as_cell!(atom).domain_error(machine_st, error), + } + } +} + #[inline(always)] pub(super) fn functor_stub(name: Atom, arity: usize) -> MachineStub { functor!(atom!("/"), [atom_as_cell(name), fixnum(arity)]) @@ -418,6 +442,28 @@ impl MachineState { [atom_as_cell((atom!("process"))), cell(culprit)] ); + MachineError { + stub, + location: None, + } + } + ExistenceError::FfiFunction(atom) => { + let stub = functor!( + atom!("existence_error"), + [atom_as_cell((atom!("ffi_function"))), atom_as_cell(atom)] + ); + + MachineError { + stub, + location: None, + } + } + ExistenceError::FfiStructType(atom) => { + let stub = functor!( + atom!("existence_error"), + [atom_as_cell((atom!("ffi_struct_type"))), atom_as_cell(atom)] + ); + MachineError { stub, location: None, @@ -628,42 +674,43 @@ impl MachineState { } #[cfg(feature = "ffi")] - pub(super) fn ffi_error(&self, err: FfiError, culprit: HeapCellValue) -> MachineError { - let error_atom = match err { - FfiError::ValueCast => atom!("value_cast"), - FfiError::ValueOutOfRange => atom!("value_out_of_range"), - FfiError::InvalidFfiType => atom!("invalid_ffi_type"), - FfiError::InvalidArgumentType => atom!("invalid_argument_type"), - FfiError::InvalidArgument => atom!("invalid_argument"), - FfiError::InvalidStruct => atom!("invalid_struct"), - FfiError::FunctionNotFound => atom!("function_not_found"), - FfiError::StructNotFound(culprit) => { + pub(super) fn ffi_error(&mut self, err: FfiError) -> MachineError { + match err { + FfiError::ValueCast(expected, actual) => { let stub = functor!( - atom!("ffi_error"), - [ - atom_as_cell((atom!("struct_not_found"))), - atom_as_cell(culprit) - ] + atom!("domain_error"), + [atom_as_cell(expected), atom_as_cell(actual)] ); - return MachineError { + MachineError { stub, location: None, - }; + } } - FfiError::ArgCountMismatch => atom!("mismatched_argument_count"), - FfiError::AllocationFailed => atom!("allocation_failed"), - FfiError::LayoutError => atom!("layout_error"), - FfiError::UnsupportedAbi => atom!("unsupported_abi"), - }; - let stub = functor!( - atom!("ffi_error"), - [atom_as_cell(error_atom), cell(culprit)] - ); - - MachineError { - stub, - location: None, + FfiError::ValueOutOfRange(domain, culprit) => self.domain_error(domain, culprit), + FfiError::FunctionNotFound(name) => { + self.existence_error(ExistenceError::FfiFunction(name)) + } + FfiError::StructNotFound(name) => { + self.existence_error(ExistenceError::FfiStructType(name)) + } + FfiError::ArgCountMismatch => self.unreachable_error(), + FfiError::AllocationFailed => MachineError { + stub: functor!(atom!("resource_error"), [atom_as_cell((atom!("heap")))]), + location: None, + }, + FfiError::LayoutError => self.representation_error(RepFlag::FfiLayout), + FfiError::UnsupportedTypedef => self.representation_error(RepFlag::FfiLayout), + FfiError::UnsupportedAbi => self.representation_error(RepFlag::FfiAbi), + FfiError::VoidArgumentType => self.domain_error( + DomainErrorType::FfiArgumentType, + atom_as_cell!(atom!("void")), + ), + FfiError::CStrFieldType => todo!(), + FfiError::NullPtr => self.domain_error( + DomainErrorType::NonNullPtr, + fixnum_as_cell!(Fixnum::build_with(0)), + ), } } @@ -845,6 +892,14 @@ pub(crate) enum DomainErrorType { OperatorPriority, Directive, Allocator, + FfiStruct, + ZeroOrOne, + NonNullPtr, + PtrLike, + F64, + FfiArgument, + FfiArgumentType, + FixedSizedInt, } impl DomainErrorType { @@ -860,6 +915,14 @@ impl DomainErrorType { DomainErrorType::OperatorPriority => atom!("operator_priority"), DomainErrorType::Directive => atom!("directive"), DomainErrorType::Allocator => atom!("allocator"), + DomainErrorType::ZeroOrOne => atom!("zero_or_one"), + DomainErrorType::FfiStruct => atom!("ffi_struct"), + DomainErrorType::NonNullPtr => atom!("non_null_pointer"), + DomainErrorType::PtrLike => atom!("pointer_like"), + DomainErrorType::F64 => atom!("f64"), + DomainErrorType::FfiArgument => atom!("ffi_argument"), + DomainErrorType::FfiArgumentType => atom!("ffi_argument_type"), + DomainErrorType::FixedSizedInt => atom!("fixed_sized_int"), } } } @@ -874,6 +937,8 @@ pub(crate) enum RepFlag { // MaxInteger, // MinInteger, Term, + FfiLayout, + FfiAbi, } impl RepFlag { @@ -885,7 +950,9 @@ impl RepFlag { RepFlag::MaxArity => atom!("max_arity"), RepFlag::Term => atom!("term"), // RepFlag::MaxInteger => atom!("max_integer"), - // RepFlag::MinInteger => atom!("min_integer") + // RepFlag::MinInteger => atom!("min_integer"), + RepFlag::FfiLayout => atom!("ffi_layout"), + RepFlag::FfiAbi => atom!("ffi_abi"), } } } @@ -1065,6 +1132,8 @@ pub enum ExistenceError { SourceSink(HeapCellValue), Stream(HeapCellValue), Process(HeapCellValue), + FfiFunction(Atom), + FfiStructType(Atom), } #[derive(Debug)] diff --git a/src/machine/machine_state.rs b/src/machine/machine_state.rs index c1de6bf8..148a9d8f 100644 --- a/src/machine/machine_state.rs +++ b/src/machine/machine_state.rs @@ -1,7 +1,6 @@ use crate::arena::*; use crate::atom_table::*; use crate::forms::*; -use crate::functor_macro::*; use crate::heap_iter::*; use crate::heap_print::*; use crate::machine::attributed_variables::*; @@ -184,7 +183,7 @@ impl IndexMut for MachineState { } } -pub type CallResult = Result>; +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 6d98a0bb..3958bdbc 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4982,29 +4982,35 @@ impl Machine { }; let return_value = cell_as_atom_cell!(self.machine_st.heap[s + 2]); functions.push(FunctionDefinition { - name: name.as_str().to_string(), + name, args, return_value: return_value.get_name(), }); } _ => { - unreachable!() - } + let err = self.machine_st.unreachable_error(); + return Err(self.machine_st.error_form(err, stub_gen())) + } ) } if self .foreign_function_table .load_library(&library_name.as_str(), &functions) - .is_ok() + .is_err() { - return Ok(()); + self.machine_st.fail = true; } + + Ok(()) } - Err(e) => return Err(e), - }; + Err(e) => Err(e), + } + } else { + let err = self + .machine_st + .type_error(ValidType::InCharacter, library_name); + Err(self.machine_st.error_form(err, stub_gen())) } - self.machine_st.fail = true; - Ok(()) } #[cfg(not(feature = "ffi"))] @@ -5034,9 +5040,9 @@ impl Machine { if let Some(head) = iter.next() { let head = self.machine_st.store(self.machine_st.deref(head)); - if let Some(struct_name) = self.machine_st.value_to_str_like(head) { + if let Some(struct_name) = head.to_atom() { Ok(Value::Struct( - struct_name.as_str().to_string(), + struct_name, iter.map(|x| self.map_ffi_arg(x, stub_gen)) .collect::>()?, )) @@ -5052,17 +5058,15 @@ impl Machine { Err(self.machine_st.error_form(err, src)) } else { // first element of a struct needs to be the type - Err(self.machine_st.error_form( - self.machine_st.ffi_error(FfiError::ValueOutOfRange, head), - stub_gen(), - )) + let err = self.machine_st.type_error(ValidType::Atom, head); + Err(self.machine_st.error_form(err, stub_gen())) } } else { // empty list is an invalid struct repr - Err(self.machine_st.error_form( - self.machine_st.ffi_error(FfiError::ValueOutOfRange, source), - stub_gen(), - )) + let err = self + .machine_st + .domain_error(DomainErrorType::FfiStruct, source); + Err(self.machine_st.error_form(err, stub_gen())) } } else if self.machine_st.deref(source).is_var() { let err = self.machine_st.instantiation_error(); @@ -5075,10 +5079,10 @@ impl Machine { Err(self.machine_st.error_form(err, src)) } else { - Err(self.machine_st.error_form( - self.machine_st.ffi_error(FfiError::InvalidArgument, source), - stub_gen(), - )) + let err = self + .machine_st + .domain_error(DomainErrorType::FfiArgument, source); + Err(self.machine_st.error_form(err, stub_gen())) } } @@ -5090,10 +5094,10 @@ impl Machine { #[cfg(feature = "ffi")] { - let function_name_arg = self.deref_register(1); + let function_name_arg = self.machine_st.store(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_arg) { + if let Some(function_name) = function_name_arg.to_atom() { match self.machine_st.try_from_list(args_reg, stub_gen) { Ok(args) => { let args = args @@ -5102,25 +5106,25 @@ impl Machine { .collect::, _>>()?; match self.foreign_function_table.exec( - &function_name.as_str(), + function_name, args, &mut self.machine_st.arena, ) { - Ok(result) => { - return self.unify_ffi_result(return_value, result); - } + Ok(result) => self.unify_ffi_result(return_value, result), Err(e) => { - let err = self.machine_st.ffi_error(e, function_name_arg); - return Err(self.machine_st.error_form(err, stub_gen())); + let err = self.machine_st.ffi_error(e); + Err(self.machine_st.error_form(err, stub_gen())) } } } - Err(e) => return Err(e), + Err(e) => Err(e), } + } else { + let err = self + .machine_st + .type_error(ValidType::Atom, function_name_arg); + Err(self.machine_st.error_form(err, stub_gen())) } - - self.machine_st.fail = true; - Ok(()) } #[cfg(not(feature = "ffi"))] @@ -5149,7 +5153,7 @@ impl Machine { }, Value::Struct(name, args) => { let struct_value = - resource_error_call_result!(self.machine_st, self.build_struct(&name, args)); + resource_error_call_result!(self.machine_st, self.build_struct(name, args)); unify!(self.machine_st, return_value, struct_value); } @@ -5166,8 +5170,8 @@ impl Machine { } #[cfg(feature = "ffi")] - fn build_struct(&mut self, name: &str, mut args: Vec) -> Result { - args.insert(0, Value::CString(CString::new(name).unwrap())); + fn build_struct(&mut self, name: Atom, mut args: Vec) -> Result { + args.insert(0, Value::CString(CString::new(&*name.as_str()).unwrap())); let cells: Vec<_> = args .into_iter() @@ -5183,7 +5187,7 @@ impl Machine { &self.machine_st.atom_tbl, &cstr.into_string().unwrap() )), - Value::Struct(name, struct_args) => self.build_struct(&name, struct_args)?, + Value::Struct(name, struct_args) => self.build_struct(name, struct_args)?, }) }) .collect::>()?; @@ -5199,9 +5203,9 @@ impl Machine { #[cfg(feature = "ffi")] { - let struct_name_arg = self.deref_register(1); + let struct_name_arg = self.machine_st.store(self.deref_register(1)); let fields_reg = self.deref_register(2); - if let Some(struct_name) = self.machine_st.value_to_str_like(struct_name_arg) { + if let Some(struct_name) = struct_name_arg.to_atom() { let fields: Vec = match self.machine_st.try_from_list(fields_reg, stub_gen) { Ok(addrs) => { let mut args = Vec::new(); @@ -5224,15 +5228,16 @@ impl Machine { Err(e) => return Err(e), }; self.foreign_function_table - .define_struct(&struct_name.as_str(), fields) + .define_struct(struct_name, fields) .map_err(|err| { - let ffi_error = self.machine_st.ffi_error(err, struct_name_arg); + let ffi_error = self.machine_st.ffi_error(err); self.machine_st.error_form(ffi_error, stub_gen()) })?; - return Ok(()); + Ok(()) + } else { + let err = self.machine_st.type_error(ValidType::Atom, struct_name_arg); + Err(self.machine_st.error_form(err, stub_gen())) } - self.machine_st.fail = true; - Ok(()) } #[cfg(not(feature = "ffi"))] @@ -5272,7 +5277,7 @@ impl Machine { ) { Ok(value) => value, Err(ffi_error) => { - let machine_error = self.machine_st.ffi_error(ffi_error, ffi_type_arg); + let machine_error = self.machine_st.ffi_error(ffi_error); return Err(self.machine_st.error_form(machine_error, stub_gen())); } }; @@ -5305,7 +5310,7 @@ impl Machine { .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, ffi_type_arg); + let machine_error = self.machine_st.ffi_error(ffi_error); self.machine_st.error_form(machine_error, stub_gen()) })?; @@ -5346,7 +5351,7 @@ impl Machine { { Ok(value) => value, Err(ffi_error) => { - let machine_error = self.machine_st.ffi_error(ffi_error, ffi_type_arg); + let machine_error = self.machine_st.ffi_error(ffi_error); return Err(self.machine_st.error_form(machine_error, stub_gen())); } } From 252361fc20b9e01ff5c92f2df29f345bba11cd12 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 26 Aug 2025 00:10:55 +0200 Subject: [PATCH 22/24] fix left over todo --- src/machine/machine_errors.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index 1c28783c..d43a8cfa 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -706,7 +706,10 @@ impl MachineState { DomainErrorType::FfiArgumentType, atom_as_cell!(atom!("void")), ), - FfiError::CStrFieldType => todo!(), + FfiError::CStrFieldType => self.domain_error( + DomainErrorType::NonCStrFfiArgumentType, + atom_as_cell!(atom!("cstr")), + ), FfiError::NullPtr => self.domain_error( DomainErrorType::NonNullPtr, fixnum_as_cell!(Fixnum::build_with(0)), @@ -900,6 +903,7 @@ pub(crate) enum DomainErrorType { FfiArgument, FfiArgumentType, FixedSizedInt, + NonCStrFfiArgumentType, } impl DomainErrorType { @@ -923,6 +927,7 @@ impl DomainErrorType { DomainErrorType::FfiArgument => atom!("ffi_argument"), DomainErrorType::FfiArgumentType => atom!("ffi_argument_type"), DomainErrorType::FixedSizedInt => atom!("fixed_sized_int"), + DomainErrorType::NonCStrFfiArgumentType => atom!("non_cstr_ffi_argument_type"), } } } From 14ce052bf2787ee3211f833d131580f4691094f6 Mon Sep 17 00:00:00 2001 From: Skgland Date: Tue, 26 Aug 2025 00:33:16 +0200 Subject: [PATCH 23/24] export array_type/3 --- src/lib/ffi.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index a03308a7..35a56195 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -1,4 +1,4 @@ -:- module(ffi, [use_foreign_module/2, foreign_struct/2, with_locals/2, allocate/4, deallocate/3, read_ptr/3]). +:- module(ffi, [use_foreign_module/2, foreign_struct/2, with_locals/2, allocate/4, deallocate/3, read_ptr/3, array_type/3]). /** Foreign Function Interface From c067bfa8320a0fa7f86dcbba190e48dcace61a33 Mon Sep 17 00:00:00 2001 From: Skgland Date: Fri, 29 Aug 2025 00:36:35 +0200 Subject: [PATCH 24/24] allow 3rd argument of ffi:read_ptr to not be a variable --- src/lib/ffi.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index 35a56195..385d8c79 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -146,7 +146,6 @@ allocate(Allocator, Type, Args, Ptr) :- % For type cstr take read a nul-terminated utf-8 string starting at Ptr. % read_ptr(Type, Ptr, Value) :- - must_be(var, Value), must_be(atom, Type), must_be(integer, Ptr), '$ffi_read_ptr'(Type, Ptr, Value).