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/build/static_string_indexing.rs b/build/static_string_indexing.rs index 7309f1cf..b9c4d1cd 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) diff --git a/src/ffi.rs b/src/ffi.rs index 36f290ff..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; @@ -34,11 +35,12 @@ 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; pub struct FunctionDefinition { - pub name: String, + pub name: Atom, pub return_value: Atom, pub args: Vec, } @@ -99,17 +101,17 @@ 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()) - .ok_or(FfiError::StructNotFound)?; + .get(&return_type_name) + .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()) .map_err(|_| FfiError::LayoutError)?; - let alloc = FfiStruct::new(layout)?; + let alloc = FfiStruct::new(layout, FfiAllocator::Rust)?; unsafe { libffi::raw::ffi_call( @@ -120,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); @@ -136,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 { @@ -163,8 +161,8 @@ impl FunctionImpl { #[derive(Debug, Default)] pub struct ForeignFunctionTable { - table: HashMap, - structs: HashMap, + table: HashMap, + structs: HashMap, } #[derive(Clone, Debug)] @@ -174,19 +172,20 @@ 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, + structs_table: &HashMap, struct_args: &mut [Value], ) -> 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)?, - )?; + let alloc = FfiStruct::new(self.layout()?, FfiAllocator::Rust)?; let Ok(mut current_layout) = Layout::from_size_align(0, 1) else { return Err(FfiError::LayoutError); @@ -247,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 { @@ -313,8 +312,8 @@ 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 { - return Err(FfiError::StructNotFound); + let Some(substruct_type) = struct_table.get(substruct) else { + return Err(FfiError::StructNotFound(*substruct)); }; let ffi_type = *substruct_type.ffi_type.as_raw_ptr(); @@ -326,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)) } } } @@ -425,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(), @@ -442,8 +437,8 @@ 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()) - .ok_or(FfiError::StructNotFound)? + .get(&struct_name) + .ok_or(FfiError::StructNotFound(struct_name))? .ffi_type .clone(), }) @@ -469,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()?)), @@ -484,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); + 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); @@ -520,21 +515,66 @@ 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) }; } } @@ -543,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 @@ -565,7 +605,7 @@ impl ForeignFunctionTable { }; self.structs.insert( - name.to_string(), + name, StructImpl { ffi_type: struct_type, fields, @@ -582,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(); @@ -596,7 +636,7 @@ impl ForeignFunctionTable { ); ff_table.table.insert( - function.name.clone(), + function.name, FunctionImpl { cif, args, @@ -612,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)?; @@ -624,13 +667,181 @@ 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::VoidArgumentType), + FfiType::Bool => { + let val = args.as_int::()?; + let init = match val { + 0 => false, + 1 => true, + _ => return Err(FfiError::ValueOutOfRange(DomainErrorType::ZeroOrOne, args)), + }; + 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::CStrFieldType), + FfiType::Struct(_) => { + let Some(struct_impl) = self.structs.get(&kind) else { + return Err(FfiError::StructNotFound(kind)); + }; + + 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::NullPtr); + }; + + match FfiType::from_atom(&kind) { + 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) }), + 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) else { + return Err(FfiError::StructNotFound(kind)); + }; + + struct_impl.read(ptr.as_ptr(), kind, &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::NullPtr); + }; + + match FfiType::from_atom(&kind) { + FfiType::Void => return Err(FfiError::VoidArgumentType), + 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::CStrFieldType), + FfiType::Struct(_) => { + let Some(struct_impl) = self.structs.get(&kind) else { + return Err(FfiError::StructNotFound(kind)); + }; + + let layout = struct_impl.layout()?; + + drop(FfiStruct { + ptr, + layout, + allocator, + }) + } + } + Ok(()) + } } #[derive(Clone, Debug)] pub enum Value { Number(Number), CString(CString), - Struct(String, Vec), + Struct(Atom, Vec), } impl Value { @@ -642,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(), + )), } } @@ -667,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, - StructNotFound, + 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 { @@ -707,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/lib/ffi.pl b/src/lib/ffi.pl index 165d5d04..385d8c79 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, array_type/3]). /** Foreign Function Interface @@ -52,6 +52,9 @@ And a new window should pop up! :- use_module(library(lists)). :- use_module(library(error)). +:- use_module(library(format)). +:- use_module(library(dcgs)). +:- use_module(library(iso_ext)). %% foreign_struct(+Name, +Elements). % @@ -69,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). @@ -108,3 +126,93 @@ 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(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), + '$ffi_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), + 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)) + ). + +:- meta_predicate(with_locals(?, 0)). + +%% 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( + allocate_locals(Locals), + Goal, + 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). diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 59db4e1c..4d3c7666 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4302,35 +4302,53 @@ 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 => { + try_or_throw!(self.machine_st, self.ffi_allocate()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteFfiAllocate => { + try_or_throw!(self.machine_st, self.ffi_allocate()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } + &Instruction::CallFfiReadPtr => { + try_or_throw!(self.machine_st, self.ffi_read_ptr()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteFfiReadPtr => { + try_or_throw!(self.machine_st, self.ffi_read_ptr()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } + &Instruction::CallFfiDeallocate => { + try_or_throw!(self.machine_st, self.ffi_deallocate()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteFfiDeallocate => { + 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..d43a8cfa 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, @@ -603,6 +649,21 @@ 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!("representation_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")); @@ -613,26 +674,46 @@ impl MachineState { } #[cfg(feature = "ffi")] - pub(super) fn ffi_error(&self, err: FfiError) -> 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 => atom!("struct_not_found"), - 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)]); + pub(super) fn ffi_error(&mut self, err: FfiError) -> MachineError { + match err { + FfiError::ValueCast(expected, actual) => { + let stub = functor!( + atom!("domain_error"), + [atom_as_cell(expected), atom_as_cell(actual)] + ); - MachineError { - stub, - location: None, + 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 => self.domain_error( + DomainErrorType::NonCStrFfiArgumentType, + atom_as_cell!(atom!("cstr")), + ), + FfiError::NullPtr => self.domain_error( + DomainErrorType::NonNullPtr, + fixnum_as_cell!(Fixnum::build_with(0)), + ), } } @@ -813,6 +894,16 @@ pub(crate) enum DomainErrorType { OperatorSpecifier, OperatorPriority, Directive, + Allocator, + FfiStruct, + ZeroOrOne, + NonNullPtr, + PtrLike, + F64, + FfiArgument, + FfiArgumentType, + FixedSizedInt, + NonCStrFfiArgumentType, } impl DomainErrorType { @@ -827,6 +918,16 @@ impl DomainErrorType { DomainErrorType::OperatorSpecifier => atom!("operator_specifier"), 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"), + DomainErrorType::NonCStrFfiArgumentType => atom!("non_cstr_ffi_argument_type"), } } } @@ -841,6 +942,8 @@ pub(crate) enum RepFlag { // MaxInteger, // MinInteger, Term, + FfiLayout, + FfiAbi, } impl RepFlag { @@ -852,7 +955,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"), } } } @@ -1032,6 +1137,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 c77110c7..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<(), 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 06bf481e..8d0c8700 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4959,170 +4959,226 @@ 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, + args, + return_value: return_value.get_name(), + }); + } + _ => { + 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_err() + { + self.machine_st.fail = true; + } + + Ok(()) + } + 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())) + } + } + + #[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, + 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) { + Ok(Value::CString(CString::new(&*string.as_str()).unwrap())) + } 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 + + 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) = head.to_atom() { + Ok(Value::Struct( + struct_name, + iter.map(|x| self.map_ffi_arg(x, stub_gen)) + .collect::>()?, + )) + } else if 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 + 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 + 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(); + + 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 { + let err = self + .machine_st + .domain_error(DomainErrorType::FfiArgument, source); + Err(self.machine_st.error_form(err, stub_gen())) + } + } + #[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) } - 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 + #[cfg(feature = "ffi")] + { + 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) = function_name_arg.to_atom() { + 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::, _>>()?; - let mut iter = args.into_iter(); - if let Some(struct_name) = machine_st.value_to_str_like(iter.next().unwrap()) { - Ok(Value::Struct( - struct_name.as_str().to_string(), - iter.map(|x| map_arg(machine_st, x)) - .collect::>()?, - )) - } else { - // empty list is an invalid struct repr - Err(FfiError::InvalidStruct) - } - } else { - Err(FfiError::InvalidArgument) - } - } - - let function_name = self.deref_register(1); - let args_reg = self.deref_register(2); - let return_value = self.deref_register(3); - if let Some(function_name) = self.machine_st.value_to_str_like(function_name) { - match self.machine_st.try_from_list(args_reg, stub_gen) { - Ok(args) => { - let args = match 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())); - } - }; - - match self.foreign_function_table.exec( - &function_name.as_str(), - args, - &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); - } + match self.foreign_function_table.exec( + function_name, + args, + &mut self.machine_st.arena, + ) { + Ok(result) => self.unify_ffi_result(return_value, result), + Err(e) => { + let err = self.machine_st.ffi_error(e); + Err(self.machine_st.error_form(err, stub_gen())) } - return Ok(()); - } - Err(e) => { - let err = self.machine_st.ffi_error(e); - return Err(self.machine_st.error_form(err, stub_gen())); } } + Err(e) => Err(e), } - Err(e) => return 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; + #[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 { + 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())); + 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() @@ -5138,7 +5194,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::>()?; @@ -5146,33 +5202,174 @@ 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 { - let struct_name = 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) { - 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()); - } - 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); - self.machine_st.error_form(ffi_error, stub_gen()) - })?; - return Ok(()); + fn stub_gen() -> MachineStub { + functor_stub(atom!("$define_foreign_struct"), 2) + } + + #[cfg(feature = "ffi")] + { + let struct_name_arg = self.machine_st.store(self.deref_register(1)); + let fields_reg = self.deref_register(2); + 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(); + 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())); + }; + + args.push(arg); + } + args + } + Err(e) => return Err(e), + }; + self.foreign_function_table + .define_struct(struct_name, fields) + .map_err(|err| { + let ffi_error = self.machine_st.ffi_error(err); + self.machine_st.error_form(ffi_error, stub_gen()) + })?; + Ok(()) + } else { + let err = self.machine_st.type_error(ValidType::Atom, struct_name_arg); + Err(self.machine_st.error_form(err, stub_gen())) + } + } + + #[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_allocate(&mut self) -> CallResult { + fn stub_gen() -> MachineStub { + functor_stub(atom!("$ffi_allocate"), 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); + 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); + 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 { + fn stub_gen() -> MachineStub { + functor_stub(atom!("$ffi_deallocate"), 3) + } + + #[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); + 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())) } - self.machine_st.fail = true; - Ok(()) } #[cfg(not(target_arch = "wasm32"))] 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-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 + +``` diff --git a/tests/scryer/ffi.rs b/tests/scryer/ffi.rs index c9e0aa1e..7340d508 100644 --- a/tests/scryer/ffi.rs +++ b/tests/scryer/ffi.rs @@ -283,3 +283,23 @@ 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"#, + ); +}