From 9d52d2a653c5fe2fb64cd6d9eb555733f46d8d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arroyo=20Calle?= Date: Wed, 22 Feb 2023 23:10:03 +0100 Subject: [PATCH 1/5] MVP of Foreign Function Interface --- Cargo.lock | 31 +++ Cargo.toml | 2 + build/instructions_template.rs | 12 ++ src/ffi.rs | 369 +++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/lib/ffi.pl | 83 ++++++++ src/machine/dispatch.rs | 24 +++ src/machine/mock_wam.rs | 3 +- src/machine/mod.rs | 3 + src/machine/system_calls.rs | 152 ++++++++++++++ 10 files changed, 679 insertions(+), 1 deletion(-) create mode 100644 src/ffi.rs create mode 100644 src/lib/ffi.pl diff --git a/Cargo.lock b/Cargo.lock index 63784cd9..b7c06dbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,6 +952,35 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "libffi" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb06d5b4c428f3cd682943741c39ed4157ae989fffe1094a08eaf7c4014cf60" +dependencies = [ + "libc", + "libffi-sys", +] + +[[package]] +name = "libffi-sys" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c6f11e063a27ffe040a9d15f0b661bf41edc2383b7ae0e0ad5a7e7d53d9da3" +dependencies = [ + "cc", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libsodium-sys" version = "0.2.7" @@ -1833,6 +1862,8 @@ dependencies = [ "lazy_static", "lexical", "libc", + "libffi", + "libloading", "modular-bitfield", "native-tls", "ordered-float", diff --git a/Cargo.toml b/Cargo.toml index 6c98ea43..206b7150 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,8 @@ hyper = { version = "0.14", features = ["full"] } hyper-tls = "0.5.0" tokio = { version = "1.24.2", features = ["full"] } futures = "0.3" +libffi = "3.1.0" +libloading = "0.7" [dev-dependencies] assert_cmd = "1.0.3" diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 48166239..80614815 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -552,6 +552,12 @@ enum SystemClauseType { HttpAccept, #[strum_discriminants(strum(props(Arity = "4", Name = "$http_answer")))] HttpAnswer, + #[strum_discriminants(strum(props(Arity = "2", Name = "$load_foreign_lib")))] + LoadForeignLib, + #[strum_discriminants(strum(props(Arity = "3", Name = "$foreign_call")))] + ForeignCall, + #[strum_discriminants(strum(props(Arity = "2", Name = "$define_foreign_struct")))] + DefineForeignStruct, #[strum_discriminants(strum(props(Arity = "3", Name = "$predicate_defined")))] PredicateDefined, #[strum_discriminants(strum(props(Arity = "3", Name = "$strip_module")))] @@ -1701,6 +1707,9 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::CallHttpListen(_) | &Instruction::CallHttpAccept(_) | &Instruction::CallHttpAnswer(_) | + &Instruction::CallLoadForeignLib(_) | + &Instruction::CallForeignCall(_) | + &Instruction::CallDefineForeignStruct(_) | &Instruction::CallPredicateDefined(_) | &Instruction::CallStripModule(_) | &Instruction::CallCurrentTime(_) | @@ -1916,6 +1925,9 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::ExecuteHttpListen(_) | &Instruction::ExecuteHttpAccept(_) | &Instruction::ExecuteHttpAnswer(_) | + &Instruction::ExecuteLoadForeignLib(_) | + &Instruction::ExecuteForeignCall(_) | + &Instruction::ExecuteDefineForeignStruct(_) | &Instruction::ExecutePredicateDefined(_) | &Instruction::ExecuteStripModule(_) | &Instruction::ExecuteCurrentTime(_) | diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 00000000..e8f2bc4b --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,369 @@ +use crate::atom_table::Atom; + +use std::alloc::{alloc, Layout}; +use std::any::Any; +use std::collections::HashMap; +use std::error::Error; +use std::ffi::{CString, c_void}; +use std::convert::TryFrom; + +use libffi::low::{ffi_cif, types, CodePtr, ffi_abi_FFI_DEFAULT_ABI, prep_cif, ffi_type, type_tag}; +use libloading::{Symbol, Library}; + +pub struct FunctionDefinition { + pub name: String, + pub return_value: Atom, + pub args: Vec, +} + +#[derive(Debug)] +pub struct FunctionImpl { + cif: ffi_cif, + args: Vec<*mut ffi_type>, + code_ptr: CodePtr, + return_struct_name: Option, +} + +#[derive(Debug, Default)] +pub struct ForeignFunctionTable { + table: HashMap, + structs: HashMap, +} + +#[derive(Debug)] +struct StructImpl { + ffi_type: ffi_type, + fields: Vec<*mut ffi_type>, +} + +struct PointerArgs { + pointers: Vec<*mut c_void>, + memory: Vec>, +} + +impl ForeignFunctionTable { + pub fn merge(&mut self, other: ForeignFunctionTable) { + self.table.extend(other.table); + } + + pub fn define_struct(&mut self, name: &str, fields: Vec) { + let mut fields: Vec<_> = fields.iter().map(|x| self.map_type_ffi(&x)).collect(); + fields.push(std::ptr::null_mut::()); + let mut struct_type: ffi_type = Default::default(); + struct_type.type_ = type_tag::STRUCT; + struct_type.elements = fields.as_mut_ptr(); + self.structs.insert(name.to_string(), StructImpl { ffi_type: struct_type, fields}); + } + + fn map_type_ffi(&mut self, source: &Atom) -> *mut ffi_type { + unsafe { + match source { + atom!("sint64") => &mut types::sint64, + atom!("sint32") => &mut types::sint32, + atom!("sint16") => &mut types::sint16, + atom!("sint8") => &mut types::sint8, + atom!("uint64") => &mut types::uint64, + atom!("uint32") => &mut types::uint32, + atom!("uint16") => &mut types::uint16, + atom!("uint8") => &mut types::uint8, + atom!("bool") => &mut types::sint8, + atom!("void") => &mut types::void, + atom!("cstr") => &mut types::pointer, + atom!("ptr") => &mut types::pointer, + atom!("f32") => &mut types::float, + atom!("f64") => &mut types::double, + struct_name => { + match self.structs.get_mut(struct_name.as_str()) { + Some(ref mut struct_type) => { + &mut struct_type.ffi_type + }, + None => unreachable!() + } + } + } + } + } + + pub(crate) fn load_library(&mut self, library_name: &str, functions: &Vec) -> Result<(), Box> { + let mut ff_table: ForeignFunctionTable = Default::default(); + unsafe { + let library = Library::new(library_name)?; + for function in functions { + let symbol_name: CString = CString::new(function.name.clone())?; + let code_ptr: Symbol<*mut c_void> = library.get(&symbol_name.into_bytes_with_nul())?; + let mut args: Vec<_> = function.args.iter().map(|x| self.map_type_ffi(&x)).collect(); + let mut cif: ffi_cif = Default::default(); + prep_cif( + &mut cif, + ffi_abi_FFI_DEFAULT_ABI, + args.len(), + self.map_type_ffi(&function.return_value), + args.as_mut_ptr() + ).unwrap(); + + let return_struct_name = if (*self.map_type_ffi(&function.return_value)).type_ as u32 == libffi::raw::FFI_TYPE_STRUCT { + Some(function.return_value.as_str().to_string()) + } else { + None + }; + + ff_table.table.insert(function.name.clone(), FunctionImpl { + cif, + args, + code_ptr: CodePtr(code_ptr.into_raw().into_raw() as *mut _), + return_struct_name, + }); + } + std::mem::forget(library); + } + self.merge(ff_table); + Ok(()) + } + + fn build_pointer_args(mut args: &mut Vec, type_args: &Vec<*mut ffi_type>, structs_table: &mut HashMap) -> Result { + let mut pointers = Vec::with_capacity(args.len()); + let mut memory = Vec::new(); + for i in 0..args.len() { + let field_type = type_args[i]; + unsafe { + match (*field_type).type_ as u32 { + libffi::raw::FFI_TYPE_UINT8 => { + let n: u8 = u8::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_SINT8 => { + let n: i8 = i8::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_UINT16 => { + let n: u16 = u16::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_SINT16 => { + let n: i16 = i16::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_UINT32 => { + let n: u32 = u32::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_SINT32 => { + let n: i32 = i32::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_UINT64 => { + let n: u64 = u64::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_SINT64 => { + let n: i64 = args[i].as_int()?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_FLOAT => { + let n: f32 = args[i].as_float()? as f32; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_DOUBLE => { + let n: f64 = args[i].as_float()?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + }, + libffi::raw::FFI_TYPE_POINTER => { + let ptr: *mut c_void = args[i].as_ptr()?; + pointers.push(ptr); + }, + libffi::raw::FFI_TYPE_STRUCT => { + match args[i] { + Value::Struct(ref name, ref struct_args) => { + if let Some(ref mut struct_type) = structs_table.get_mut(name) { + let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); + let ptr = alloc(layout) as *mut c_void; + let mut field_ptr = ptr; + for i in 0..(struct_type.fields.len()-1) { + let field = struct_type.fields[i]; + match (*field).type_ as u32 { + libffi::raw::FFI_TYPE_UINT8 => { + let n: u8 = u8::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + std::ptr::write(field_ptr as *mut u8, n); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + libffi::raw::FFI_TYPE_UINT32 => { + let n: u32 = u32::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + std::ptr::write(field_ptr as *mut u32, n); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + libffi::raw::FFI_TYPE_SINT32 => { + let n: u32 = u32::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + std::ptr::write(field_ptr as *mut u32, n); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + libffi::raw::FFI_TYPE_FLOAT => { + let n: f32 = struct_args[i].as_float()? as f32; + std::ptr::write(field_ptr as *mut f32, n); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + _ => { + unreachable!() + } + } + } + pointers.push(ptr); + memory.push(Box::from_raw(ptr)); + } else { + return Err(FFIError::InvalidStructName); + } + } + _ => return Err(FFIError::ValueCast) + } + }, + _ => return Err(FFIError::InvalidFFIType) + } + } + } + Ok(PointerArgs { + pointers, + memory + }) + } + + pub fn exec(&mut self, name: &str, mut args: Vec) -> Result { + let function_impl = self.table.get_mut(name).ok_or(FFIError::FunctionNotFound)?; + let mut pointer_args = Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs).unwrap(); + return unsafe { + match (*function_impl.cif.rtype).type_ as u32 { + libffi::raw::FFI_TYPE_VOID => { + let mut _n: Box = Box::new(0); + libffi::raw::ffi_call( + &mut function_impl.cif, + Some(*function_impl.code_ptr.as_safe_fun()), + &mut *_n as *mut _ as *mut c_void, + pointer_args.pointers.as_mut_ptr() as *mut *mut c_void + ); + Ok(Value::Int(0)) + }, + libffi::raw::FFI_TYPE_SINT8 => { + let mut n: Box = Box::new(0); + libffi::raw::ffi_call( + &mut function_impl.cif, + Some(*function_impl.code_ptr.as_safe_fun()), + &mut *n as *mut _ as *mut c_void, + pointer_args.pointers.as_mut_ptr() as *mut *mut c_void + ); + Ok(Value::Int(i64::from(*n))) + }, + libffi::raw::FFI_TYPE_SINT32 => { + let mut n: Box = Box::new(0); + libffi::raw::ffi_call( + &mut function_impl.cif, + Some(*function_impl.code_ptr.as_safe_fun()), + &mut *n as *mut _ as *mut c_void, + pointer_args.pointers.as_mut_ptr() as *mut *mut c_void + ); + Ok(Value::Int(i64::from(*n))) + }, + libffi::raw::FFI_TYPE_STRUCT => { + let mut returns = Vec::new(); + let mut struct_type = self.structs.get_mut(&function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?).ok_or(FFIError::StructNotFound)?; + let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); + let ptr = alloc(layout) as *mut c_void; + libffi::raw::ffi_call( + &mut function_impl.cif, + Some(*function_impl.code_ptr.as_safe_fun()), + &mut *ptr as *mut _ as *mut c_void, + pointer_args.pointers.as_mut_ptr() as *mut *mut c_void + ); + + let mut field_ptr = ptr; + for i in 0..(struct_type.fields.len()-1) { + let field = struct_type.fields[i]; + match (*field).type_ as u32 { + libffi::raw::FFI_TYPE_UINT8 => { + let n = std::ptr::read(field_ptr as *mut u8); + returns.push(Value::Int(i64::from(n))); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + libffi::raw::FFI_TYPE_SINT32 => { + let n = std::ptr::read(field_ptr as *mut i32); + returns.push(Value::Int(i64::from(n))); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + libffi::raw::FFI_TYPE_UINT32 => { + let n = std::ptr::read(field_ptr as *mut u32); + returns.push(Value::Int(i64::from(n))); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + _ => { + unreachable!() + } + } + } + drop(Box::from_raw(ptr)); + Ok(Value::Struct("texture".into(), returns)) + }, + _ => unreachable!() + } + }; + } +} + +#[derive(Clone, Debug)] +pub enum Value { + Int(i64), + Float(f64), + CString(CString), + Struct(String, Vec), +} + +impl Value { + fn as_int(&self) -> Result { + match self { + Value::Int(n) => Ok(*n), + _ => Err(FFIError::ValueCast), + } + } + + fn as_float(&self) -> Result { + match self { + Value::Float(n) => Ok(*n), + Value::Int(n) => Ok(*n as f64), + _ => Err(FFIError::ValueCast), + } + } + + fn as_ptr(&mut self) -> Result<*mut c_void, FFIError> { + match self { + Value::CString(ref mut cstr) => Ok(&mut *cstr as *mut _ as *mut c_void), + Value::Int(n) => Ok(*n as *mut c_void), + _ => Err(FFIError::ValueCast) + } + } +} + +#[derive(Debug)] +pub enum FFIError { + ValueCast, + ValueDontFit, + InvalidFFIType, + InvalidStructName, + FunctionNotFound, + StructNotFound, +} diff --git a/src/lib.rs b/src/lib.rs index 2846fd0e..45dc2385 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ mod allocator; mod arithmetic; pub mod codegen; mod debray_allocator; +mod ffi; mod fixtures; mod forms; mod heap_iter; diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl new file mode 100644 index 00000000..c34cb4ad --- /dev/null +++ b/src/lib/ffi.pl @@ -0,0 +1,83 @@ +:- module(ffi, [use_foreign_module/2, foreign_struct/2]). + +:- use_module(library(lists)). +:- use_module(library(error)). + +foreign_struct(Name, Elements) :- + '$define_foreign_struct'(Name, Elements). + +use_foreign_module(LibName, Predicates) :- + '$load_foreign_lib'(LibName, Predicates), + maplist(assert_predicate, Predicates). + +assert_predicate(PredicateDefinition) :- + PredicateDefinition =.. [Name, Inputs, void], + length(Inputs, NumInputs), + functor(Head, Name, NumInputs), + term_variables(Head, TermList), + Body = ( + lists:maplist(ffi:check_input, Inputs, TermList), + '$foreign_call'(Name, TermList, _),! + ), + Predicate =.. [:-, Head, Body], + assertz(ffi:Predicate). + +assert_predicate(PredicateDefinition) :- + PredicateDefinition =.. [Name, Inputs, bool], + length(Inputs, NumInputs), + functor(Head, Name, NumInputs), + term_variables(Head, TermList), + Body = ( + lists:maplist(ffi:check_input, Inputs, TermList), + '$foreign_call'(Name, TermList, 1),! + ), + Predicate =.. [:-, Head, Body], + assertz(ffi:Predicate). + +assert_predicate(PredicateDefinition) :- + PredicateDefinition =.. [Name, Inputs, Return], + \+ member(Return, [void, bool]), + length(Inputs, NumInputs), + NumArgs is NumInputs + 1, + functor(Head, Name, NumArgs), + term_variables(Head, TermList), + Body = ( + lists:append(TermListInputs, [TermListReturn], TermList), + lists:maplist(ffi:check_input, Inputs, TermListInputs), + '$foreign_call'(Name, TermListInputs, TermListReturn),! + ), + Predicate =.. [:-, Head, Body], + assertz(ffi:Predicate). + +check_input(sint8, Var) :- + must_be(integer, Var), + ( + (Var > -129, Var < 128) -> + true + ; domain_error(integer_does_not_fit, Var, foreign_call/3) + ). +check_input(sint16, Var) :- + must_be(integer, Var), + ( + (Var > -32769, Var < 32768) -> + true + ; domain_error(integer_does_not_fit, Var, foreign_call/3) + ). +check_input(sint32, Var) :- + must_be(integer, Var), + ( + (Var > -2147483649, Var < 2147483648) -> + true + ; domain_error(integer_does_not_fit, Var, foreign_call/3) + ). +check_input(sint64, Var) :- + must_be(integer, Var). +check_input(f32, _Var). +check_input(f64, _Var). +check_input(cstr, Var) :- + must_be(chars, Var). +check_input(_, Var). +% must_be(list, Var). + +% TODO: assert native predicates. +% They MUST validate types diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 89fa348a..d8ace62d 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4221,6 +4221,30 @@ impl Machine { try_or_throw!(self.machine_st, self.http_answer()); step_or_fail!(self, self.machine_st.p = self.machine_st.cp); } + &Instruction::CallLoadForeignLib(_) => { + try_or_throw!(self.machine_st, self.load_foreign_lib()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteLoadForeignLib(_) => { + try_or_throw!(self.machine_st, self.load_foreign_lib()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } + &Instruction::CallForeignCall(_) => { + try_or_throw!(self.machine_st, self.foreign_call()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteForeignCall(_) => { + try_or_throw!(self.machine_st, self.foreign_call()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } + &Instruction::CallDefineForeignStruct(_) => { + try_or_throw!(self.machine_st, self.define_foreign_struct()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteDefineForeignStruct(_) => { + try_or_throw!(self.machine_st, self.define_foreign_struct()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } &Instruction::CallCurrentTime(_) => { self.current_time(); step_or_fail!(self, self.machine_st.p += 1); diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs index f18d9575..761590fb 100644 --- a/src/machine/mock_wam.rs +++ b/src/machine/mock_wam.rs @@ -236,7 +236,8 @@ impl Machine { user_output, user_error, load_contexts: vec![], - runtime + runtime, + foreign_function_table: Default::default(), }; let mut lib_path = current_dir(); diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 09883b99..77eb9c8b 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -26,6 +26,7 @@ use crate::arena::*; use crate::arithmetic::*; use crate::atom_table::*; use crate::forms::*; +use crate::ffi::ForeignFunctionTable; use crate::instructions::*; use crate::machine::args::*; use crate::machine::compile::*; @@ -65,6 +66,7 @@ pub struct Machine { pub(super) user_error: Stream, pub(super) load_contexts: Vec, pub(super) runtime: Runtime, + pub(super) foreign_function_table: ForeignFunctionTable, } #[derive(Debug)] @@ -443,6 +445,7 @@ impl Machine { user_error, load_contexts: vec![], runtime, + foreign_function_table: Default::default(), }; let mut lib_path = current_dir(); diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 93dcdb09..c68a532e 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -6,6 +6,7 @@ use lazy_static::lazy_static; use crate::arena::*; use crate::atom_table::*; use crate::forms::*; +use crate::ffi::*; use crate::heap_iter::*; use crate::heap_print::*; use crate::http::{self, HttpListener, HttpResponse}; @@ -40,6 +41,7 @@ use std::cmp::Ordering; use std::collections::BTreeSet; use std::convert::{TryFrom, Infallible}; use std::env; +use std::ffi::CString; use std::fs; use std::hash::{BuildHasher, BuildHasherDefault}; use std::io::{ErrorKind, Read, Write}; @@ -4172,6 +4174,156 @@ impl Machine { Ok(()) } + #[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 let Ok(_) = self.foreign_function_table.load_library(library_name.as_str(), &functions) { + return Ok(()); + } + } + Err(e) => return Err(e) + }; + } + self.machine_st.fail = true; + Ok(()) + } + + #[inline(always)] + pub(crate) fn foreign_call(&mut self) -> CallResult { + let function_name = self.deref_register(1); + let args_reg = self.deref_register(2); + let return_value = self.deref_register(3); + if let Some(function_name) = self.machine_st.value_to_str_like(function_name) { + let stub_gen = || functor_stub(atom!("foreign_call"), 3); + fn map_arg(mut machine_st: &mut MachineState, source: HeapCellValue) -> crate::ffi::Value { + match Number::try_from(source) { + Ok(Number::Fixnum(n)) => { + Value::Int(n.get_num()) + }, + Ok(Number::Float(n)) => { + Value::Float(n.into_inner()) + }, + _ => { + let stub_gen = || functor_stub(atom!("foreign_call"), 3); + if let Some(string) = machine_st.value_to_str_like(source) { + Value::CString(CString::new(string.as_str()).unwrap()) + } else { + match machine_st.try_from_list(source, stub_gen) { + Ok(args) => { + let mut iter = args.into_iter(); + if let Some(struct_name) = machine_st.value_to_str_like(iter.next().unwrap()) { + Value::Struct(struct_name.as_str().to_string(), iter.map(|x| map_arg(&mut machine_st, x)).collect()) + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + } + } + } + } + } + match self.machine_st.try_from_list(args_reg, stub_gen) { + Ok(args) => { + let args: Vec<_> = args.into_iter().map(|x| map_arg(&mut self.machine_st, x)).collect(); + match self.foreign_function_table.exec(function_name.as_str(), args) { + Ok(result) => { + match result { + Value::Int(n) => self.machine_st.unify_fixnum(Fixnum::build_with(n), return_value), + Value::Struct(name, mut args) => { + args.insert(0, Value::CString(CString::new(name).unwrap())); + let struct_list = heap_loc_as_cell!( + iter_to_heap_list( + &mut self.machine_st.heap, + args.into_iter() + .map(|val| { + match val { + Value::Int(n) => fixnum_as_cell!(Fixnum::build_with(n)), + Value::CString(cstr) => atom_as_cell!(self.machine_st.atom_tbl.build_with(&cstr.into_string().unwrap())), + _ => unreachable!() + } + }), + ) + ); + unify!(self.machine_st, return_value, struct_list); + } + _ => { + unreachable!(); + } + } + return Ok(()); + }, + Err(e) => { + // throw error + self.machine_st.fail = true; + return Ok(()); + } + } + } + Err(e) => return Err(e) + } + } + self.machine_st.fail = true; + Ok(()) + } + + #[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); + return Ok(()) + } + self.machine_st.fail = true; + Ok(()) + } + #[inline(always)] pub(crate) fn current_time(&mut self) { let timestamp = self.systemtime_to_timestamp(SystemTime::now()); From 92b262d599f83d629e9f75bd492c58aab34a3672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arroyo=20Calle?= Date: Sat, 25 Feb 2023 22:27:28 +0100 Subject: [PATCH 2/5] Macroization of the code --- src/ffi.rs | 211 ++++++++++++++++++---------------- src/machine/machine_errors.rs | 19 +++ src/machine/system_calls.rs | 7 +- 3 files changed, 136 insertions(+), 101 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index e8f2bc4b..8b0b5db9 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -120,61 +120,32 @@ impl ForeignFunctionTable { Ok(()) } - fn build_pointer_args(mut args: &mut Vec, type_args: &Vec<*mut ffi_type>, structs_table: &mut HashMap) -> Result { + fn build_pointer_args(args: &mut Vec, type_args: &Vec<*mut ffi_type>, structs_table: &mut HashMap) -> Result { let mut pointers = Vec::with_capacity(args.len()); let mut memory = Vec::new(); for i in 0..args.len() { let field_type = type_args[i]; unsafe { + macro_rules! push_int { + ($type:ty) => { + { + let n: $type = <$type>::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + let mut box_value = Box::new(n) as Box; + pointers.push(&mut *box_value as *mut _ as *mut c_void); + memory.push(box_value); + } + } + } + match (*field_type).type_ as u32 { - libffi::raw::FFI_TYPE_UINT8 => { - let n: u8 = u8::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_SINT8 => { - let n: i8 = i8::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_UINT16 => { - let n: u16 = u16::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_SINT16 => { - let n: i16 = i16::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_UINT32 => { - let n: u32 = u32::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_SINT32 => { - let n: i32 = i32::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_UINT64 => { - let n: u64 = u64::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, - libffi::raw::FFI_TYPE_SINT64 => { - let n: i64 = args[i].as_int()?; - let mut box_value = Box::new(n) as Box; - pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); - }, + libffi::raw::FFI_TYPE_UINT8 => push_int!(u8), + libffi::raw::FFI_TYPE_SINT8 => push_int!(i8), + libffi::raw::FFI_TYPE_UINT16 => push_int!(u16), + libffi::raw::FFI_TYPE_SINT16 => push_int!(i16), + libffi::raw::FFI_TYPE_UINT32 => push_int!(u32), + libffi::raw::FFI_TYPE_SINT32 => push_int!(i32), + libffi::raw::FFI_TYPE_UINT64 => push_int!(u64), + libffi::raw::FFI_TYPE_SINT64 => push_int!(i64), libffi::raw::FFI_TYPE_FLOAT => { let n: f32 = args[i].as_float()? as f32; let mut box_value = Box::new(n) as Box; @@ -193,34 +164,46 @@ impl ForeignFunctionTable { }, libffi::raw::FFI_TYPE_STRUCT => { match args[i] { - Value::Struct(ref name, ref struct_args) => { + Value::Struct(ref name, ref mut struct_args) => { if let Some(ref mut struct_type) = structs_table.get_mut(name) { let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); let ptr = alloc(layout) as *mut c_void; let mut field_ptr = ptr; + for i in 0..(struct_type.fields.len()-1) { + macro_rules! try_write_int { + ($type:ty) => { + { + let n: $type = <$type>::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + std::ptr::write(field_ptr as *mut $type, n); + field_ptr = field_ptr.add(std::mem::size_of::<$type>()); + } + } + } + + macro_rules! write { + ($type:ty, $value:expr) => { + { + let data: $type = $value; + std::ptr::write(field_ptr as *mut $type, data); + field_ptr = field_ptr.add(std::mem::size_of::<$type>()); + } + } + } + let field = struct_type.fields[i]; match (*field).type_ as u32 { - libffi::raw::FFI_TYPE_UINT8 => { - let n: u8 = u8::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - std::ptr::write(field_ptr as *mut u8, n); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, - libffi::raw::FFI_TYPE_UINT32 => { - let n: u32 = u32::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - std::ptr::write(field_ptr as *mut u32, n); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, - libffi::raw::FFI_TYPE_SINT32 => { - let n: u32 = u32::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - std::ptr::write(field_ptr as *mut u32, n); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, - libffi::raw::FFI_TYPE_FLOAT => { - let n: f32 = struct_args[i].as_float()? as f32; - std::ptr::write(field_ptr as *mut f32, n); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, + libffi::raw::FFI_TYPE_UINT8 => try_write_int!(u8), + libffi::raw::FFI_TYPE_SINT8 => try_write_int!(i8), + libffi::raw::FFI_TYPE_UINT16 => try_write_int!(u16), + libffi::raw::FFI_TYPE_SINT16 => try_write_int!(i16), + libffi::raw::FFI_TYPE_UINT32 => try_write_int!(u32), + libffi::raw::FFI_TYPE_SINT32 => try_write_int!(i32), + libffi::raw::FFI_TYPE_UINT64 => try_write_int!(u64), + libffi::raw::FFI_TYPE_SINT64 => try_write_int!(i64), + libffi::raw::FFI_TYPE_POINTER => write!(*mut c_void, struct_args[i].as_ptr()?), + libffi::raw::FFI_TYPE_FLOAT => write!(f32, struct_args[i].as_float()? as f32), + libffi::raw::FFI_TYPE_DOUBLE => write!(f64, struct_args[i].as_float()?), _ => { unreachable!() } @@ -247,42 +230,66 @@ impl ForeignFunctionTable { pub fn exec(&mut self, name: &str, mut args: Vec) -> Result { let function_impl = self.table.get_mut(name).ok_or(FFIError::FunctionNotFound)?; - let mut pointer_args = Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs).unwrap(); + let mut pointer_args = Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs)?; + return unsafe { + macro_rules! call_and_return { + ($type:ty) => { + { + let mut n: Box = Box::new(0); + libffi::raw::ffi_call( + &mut function_impl.cif, + Some(*function_impl.code_ptr.as_safe_fun()), + &mut *n as *mut _ as *mut c_void, + pointer_args.pointers.as_mut_ptr() as *mut *mut c_void + ); + Ok(Value::Int(i64::from(*n))) + } + } + } + match (*function_impl.cif.rtype).type_ as u32 { - libffi::raw::FFI_TYPE_VOID => { - let mut _n: Box = Box::new(0); + libffi::raw::FFI_TYPE_VOID => call_and_return!(i32), + libffi::raw::FFI_TYPE_UINT8 => call_and_return!(u8), + libffi::raw::FFI_TYPE_SINT8 => call_and_return!(i8), + libffi::raw::FFI_TYPE_UINT16 => call_and_return!(u16), + libffi::raw::FFI_TYPE_SINT16 => call_and_return!(i16), + libffi::raw::FFI_TYPE_UINT32 => call_and_return!(u32), + libffi::raw::FFI_TYPE_SINT32 => call_and_return!(i32), + libffi::raw::FFI_TYPE_UINT64 => { + let mut n: Box = Box::new(0); libffi::raw::ffi_call( &mut function_impl.cif, Some(*function_impl.code_ptr.as_safe_fun()), - &mut *_n as *mut _ as *mut c_void, + &mut *n as *mut _ as *mut c_void, pointer_args.pointers.as_mut_ptr() as *mut *mut c_void ); - Ok(Value::Int(0)) + Ok(Value::Int(i64::try_from(*n).map_err(|_| FFIError::ValueDontFit)?)) }, - libffi::raw::FFI_TYPE_SINT8 => { - let mut n: Box = Box::new(0); + libffi::raw::FFI_TYPE_SINT64 => call_and_return!(i64), + libffi::raw::FFI_TYPE_FLOAT => { + let mut n: Box = Box::new(0.0); libffi::raw::ffi_call( &mut function_impl.cif, Some(*function_impl.code_ptr.as_safe_fun()), &mut *n as *mut _ as *mut c_void, pointer_args.pointers.as_mut_ptr() as *mut *mut c_void ); - Ok(Value::Int(i64::from(*n))) - }, - libffi::raw::FFI_TYPE_SINT32 => { - let mut n: Box = Box::new(0); + Ok(Value::Float((*n).into())) + }, + libffi::raw::FFI_TYPE_DOUBLE => { + let mut n: Box = Box::new(0.0); libffi::raw::ffi_call( &mut function_impl.cif, Some(*function_impl.code_ptr.as_safe_fun()), &mut *n as *mut _ as *mut c_void, pointer_args.pointers.as_mut_ptr() as *mut *mut c_void ); - Ok(Value::Int(i64::from(*n))) + Ok(Value::Float(*n)) }, libffi::raw::FFI_TYPE_STRUCT => { let mut returns = Vec::new(); - let mut struct_type = self.structs.get_mut(&function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?).ok_or(FFIError::StructNotFound)?; + let struct_type = self.structs.get_mut(&function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?).ok_or(FFIError::StructNotFound)?; let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); let ptr = alloc(layout) as *mut c_void; libffi::raw::ffi_call( @@ -292,25 +299,33 @@ impl ForeignFunctionTable { pointer_args.pointers.as_mut_ptr() as *mut *mut c_void ); - let mut field_ptr = ptr; + let mut field_ptr = ptr; + + macro_rules! read_and_push_int { + ($type:ty) => { + { + let n = std::ptr::read(field_ptr as *mut $type); + returns.push(Value::Int(i64::from(n))); + field_ptr = field_ptr.add(std::mem::size_of::<$type>()); + } + } + } + for i in 0..(struct_type.fields.len()-1) { let field = struct_type.fields[i]; match (*field).type_ as u32 { - libffi::raw::FFI_TYPE_UINT8 => { - let n = std::ptr::read(field_ptr as *mut u8); - returns.push(Value::Int(i64::from(n))); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, - libffi::raw::FFI_TYPE_SINT32 => { - let n = std::ptr::read(field_ptr as *mut i32); - returns.push(Value::Int(i64::from(n))); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, - libffi::raw::FFI_TYPE_UINT32 => { - let n = std::ptr::read(field_ptr as *mut u32); - returns.push(Value::Int(i64::from(n))); - field_ptr = field_ptr.add(std::mem::size_of::()); + libffi::raw::FFI_TYPE_UINT8 => read_and_push_int!(u8), + libffi::raw::FFI_TYPE_SINT8 => read_and_push_int!(i8), + libffi::raw::FFI_TYPE_UINT16 => read_and_push_int!(u16), + libffi::raw::FFI_TYPE_SINT16 => read_and_push_int!(i16), + libffi::raw::FFI_TYPE_UINT32 => read_and_push_int!(u32), + libffi::raw::FFI_TYPE_SINT32 => read_and_push_int!(i32), + libffi::raw::FFI_TYPE_UINT64 => { + let n = std::ptr::read(field_ptr as *mut u64); + returns.push(Value::Int(i64::try_from(n).map_err(|_| FFIError::ValueDontFit)?)); + field_ptr = field_ptr.add(std::mem::size_of::()); }, + libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64), _ => { unreachable!() } diff --git a/src/machine/machine_errors.rs b/src/machine/machine_errors.rs index ca2af22e..194d3ccc 100644 --- a/src/machine/machine_errors.rs +++ b/src/machine/machine_errors.rs @@ -1,6 +1,7 @@ use crate::atom_table::*; use crate::parser::ast::*; +use crate::ffi::FFIError; use crate::forms::*; use crate::machine::heap::*; use crate::machine::loader::CompilationTarget; @@ -515,6 +516,24 @@ impl MachineState { } } + pub(super) fn ffi_error(&mut self, err: FFIError) -> MachineError { + let error_atom = match err { + FFIError::ValueCast => atom!("value_cast"), + FFIError::ValueDontFit => atom!("value_dont_fit"), + FFIError::InvalidFFIType => atom!("invalid_ffi_type"), + FFIError::InvalidStructName => atom!("invalid_struct_name"), + FFIError::FunctionNotFound => atom!("function_not_found"), + FFIError::StructNotFound => atom!("struct_not_found"), + }; + let stub = functor!(atom!("ffi_error"),[atom(error_atom)]); + + MachineError { + stub, + location: None, + from: ErrorProvenance::Constructed, + } + } + pub(super) fn error_form(&mut self, err: MachineError, src: FunctorStub) -> MachineStub { let h = self.heap.len(); let location = err.location; diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index c68a532e..e8536035 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4288,9 +4288,10 @@ impl Machine { return Ok(()); }, Err(e) => { - // throw error - self.machine_st.fail = true; - return Ok(()); + let stub = functor_stub(atom!("current_input"), 1); + let err = self.machine_st.ffi_error(e); + + return Err(self.machine_st.error_form(err, stub)); } } } From f94294dbd91111849a40ade6797a546ab62fb48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arroyo=20Calle?= Date: Sun, 26 Feb 2023 20:48:00 +0100 Subject: [PATCH 3/5] FFI: Nested structs --- src/ffi.rs | 283 ++++++++++++++++++++---------------- src/machine/system_calls.rs | 40 +++-- 2 files changed, 184 insertions(+), 139 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 8b0b5db9..2910a20d 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -30,15 +30,16 @@ pub struct ForeignFunctionTable { structs: HashMap, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct StructImpl { ffi_type: ffi_type, fields: Vec<*mut ffi_type>, + atom_fields: Vec, } struct PointerArgs { pointers: Vec<*mut c_void>, - memory: Vec>, + _memory: Vec>, } impl ForeignFunctionTable { @@ -46,42 +47,42 @@ impl ForeignFunctionTable { self.table.extend(other.table); } - pub fn define_struct(&mut self, name: &str, fields: Vec) { - let mut fields: Vec<_> = fields.iter().map(|x| self.map_type_ffi(&x)).collect(); + pub fn define_struct(&mut self, name: &str, atom_fields: Vec) { + let mut fields: Vec<_> = atom_fields.iter().map(|x| self.map_type_ffi(&x)).collect(); fields.push(std::ptr::null_mut::()); let mut struct_type: ffi_type = Default::default(); struct_type.type_ = type_tag::STRUCT; struct_type.elements = fields.as_mut_ptr(); - self.structs.insert(name.to_string(), StructImpl { ffi_type: struct_type, fields}); + self.structs.insert(name.to_string(), StructImpl { ffi_type: struct_type, fields, atom_fields}); } fn map_type_ffi(&mut self, source: &Atom) -> *mut ffi_type { unsafe { - match source { - atom!("sint64") => &mut types::sint64, - atom!("sint32") => &mut types::sint32, - atom!("sint16") => &mut types::sint16, - atom!("sint8") => &mut types::sint8, - atom!("uint64") => &mut types::uint64, - atom!("uint32") => &mut types::uint32, - atom!("uint16") => &mut types::uint16, - atom!("uint8") => &mut types::uint8, - atom!("bool") => &mut types::sint8, - atom!("void") => &mut types::void, - atom!("cstr") => &mut types::pointer, - atom!("ptr") => &mut types::pointer, - atom!("f32") => &mut types::float, - atom!("f64") => &mut types::double, - struct_name => { - match self.structs.get_mut(struct_name.as_str()) { - Some(ref mut struct_type) => { - &mut struct_type.ffi_type - }, - None => unreachable!() + match source { + atom!("sint64") => &mut types::sint64, + atom!("sint32") => &mut types::sint32, + atom!("sint16") => &mut types::sint16, + atom!("sint8") => &mut types::sint8, + atom!("uint64") => &mut types::uint64, + atom!("uint32") => &mut types::uint32, + atom!("uint16") => &mut types::uint16, + atom!("uint8") => &mut types::uint8, + atom!("bool") => &mut types::sint8, + atom!("void") => &mut types::void, + atom!("cstr") => &mut types::pointer, + atom!("ptr") => &mut types::pointer, + atom!("f32") => &mut types::float, + atom!("f64") => &mut types::double, + struct_name => { + match self.structs.get_mut(struct_name.as_str()) { + Some(ref mut struct_type) => { + &mut struct_type.ffi_type + }, + None => unreachable!() + } } } } - } } pub(crate) fn load_library(&mut self, library_name: &str, functions: &Vec) -> Result<(), Box> { @@ -122,7 +123,7 @@ impl ForeignFunctionTable { fn build_pointer_args(args: &mut Vec, type_args: &Vec<*mut ffi_type>, structs_table: &mut HashMap) -> Result { let mut pointers = Vec::with_capacity(args.len()); - let mut memory = Vec::new(); + let mut _memory = Vec::new(); for i in 0..args.len() { let field_type = type_args[i]; unsafe { @@ -132,7 +133,7 @@ impl ForeignFunctionTable { let n: $type = <$type>::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; let mut box_value = Box::new(n) as Box; pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); + _memory.push(box_value); } } } @@ -150,73 +151,22 @@ impl ForeignFunctionTable { let n: f32 = args[i].as_float()? as f32; let mut box_value = Box::new(n) as Box; pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); + _memory.push(box_value); }, libffi::raw::FFI_TYPE_DOUBLE => { let n: f64 = args[i].as_float()?; let mut box_value = Box::new(n) as Box; pointers.push(&mut *box_value as *mut _ as *mut c_void); - memory.push(box_value); + _memory.push(box_value); }, libffi::raw::FFI_TYPE_POINTER => { let ptr: *mut c_void = args[i].as_ptr()?; pointers.push(ptr); }, libffi::raw::FFI_TYPE_STRUCT => { - match args[i] { - Value::Struct(ref name, ref mut struct_args) => { - if let Some(ref mut struct_type) = structs_table.get_mut(name) { - let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); - let ptr = alloc(layout) as *mut c_void; - let mut field_ptr = ptr; - - for i in 0..(struct_type.fields.len()-1) { - macro_rules! try_write_int { - ($type:ty) => { - { - let n: $type = <$type>::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; - std::ptr::write(field_ptr as *mut $type, n); - field_ptr = field_ptr.add(std::mem::size_of::<$type>()); - } - } - } - - macro_rules! write { - ($type:ty, $value:expr) => { - { - let data: $type = $value; - std::ptr::write(field_ptr as *mut $type, data); - field_ptr = field_ptr.add(std::mem::size_of::<$type>()); - } - } - } - - let field = struct_type.fields[i]; - match (*field).type_ as u32 { - libffi::raw::FFI_TYPE_UINT8 => try_write_int!(u8), - libffi::raw::FFI_TYPE_SINT8 => try_write_int!(i8), - libffi::raw::FFI_TYPE_UINT16 => try_write_int!(u16), - libffi::raw::FFI_TYPE_SINT16 => try_write_int!(i16), - libffi::raw::FFI_TYPE_UINT32 => try_write_int!(u32), - libffi::raw::FFI_TYPE_SINT32 => try_write_int!(i32), - libffi::raw::FFI_TYPE_UINT64 => try_write_int!(u64), - libffi::raw::FFI_TYPE_SINT64 => try_write_int!(i64), - libffi::raw::FFI_TYPE_POINTER => write!(*mut c_void, struct_args[i].as_ptr()?), - libffi::raw::FFI_TYPE_FLOAT => write!(f32, struct_args[i].as_float()? as f32), - libffi::raw::FFI_TYPE_DOUBLE => write!(f64, struct_args[i].as_float()?), - _ => { - unreachable!() - } - } - } - pointers.push(ptr); - memory.push(Box::from_raw(ptr)); - } else { - return Err(FFIError::InvalidStructName); - } - } - _ => return Err(FFIError::ValueCast) - } + let (mut ptr, _size, _align) = Self::build_struct(&mut args[i], structs_table)?; + pointers.push(&mut *ptr as *mut _ as *mut c_void); + _memory.push(ptr); }, _ => return Err(FFIError::InvalidFFIType) } @@ -224,10 +174,78 @@ impl ForeignFunctionTable { } Ok(PointerArgs { pointers, - memory + _memory }) } + fn build_struct(arg: &mut Value, structs_table: &mut HashMap) -> Result<(Box, usize, usize), FFIError> { + unsafe { + match arg { + Value::Struct(ref name, ref mut struct_args) => { + if let Some(ref mut struct_type) = structs_table.clone().get_mut(name) { + let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); + let align = struct_type.ffi_type.alignment as usize; + let size = struct_type.ffi_type.size; + let ptr = alloc(layout) as *mut c_void; + let mut field_ptr = ptr; + + for i in 0..(struct_type.fields.len()-1) { + macro_rules! try_write_int { + ($type:ty) => { + { + field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>())); + let n: $type = <$type>::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?; + std::ptr::write(field_ptr as *mut $type, n); + field_ptr = field_ptr.add(std::mem::size_of::<$type>()); + } + } + } + + macro_rules! write { + ($type:ty, $value:expr) => { + { + let data: $type = $value; + std::ptr::write(field_ptr as *mut $type, data); + field_ptr = field_ptr.add(align); + } + } + } + + let field = struct_type.fields[i]; + match (*field).type_ as u32 { + libffi::raw::FFI_TYPE_UINT8 => try_write_int!(u8), + libffi::raw::FFI_TYPE_SINT8 => try_write_int!(i8), + libffi::raw::FFI_TYPE_UINT16 => try_write_int!(u16), + libffi::raw::FFI_TYPE_SINT16 => try_write_int!(i16), + libffi::raw::FFI_TYPE_UINT32 => try_write_int!(u32), + libffi::raw::FFI_TYPE_SINT32 => try_write_int!(i32), + libffi::raw::FFI_TYPE_UINT64 => try_write_int!(u64), + libffi::raw::FFI_TYPE_SINT64 => try_write_int!(i64), + libffi::raw::FFI_TYPE_POINTER => write!(*mut c_void, struct_args[i].as_ptr()?), + libffi::raw::FFI_TYPE_FLOAT => write!(f32, struct_args[i].as_float()? as f32), + libffi::raw::FFI_TYPE_DOUBLE => write!(f64, struct_args[i].as_float()?), + libffi::raw::FFI_TYPE_STRUCT => { + let (struct_ptr, struct_size, struct_align) = Self::build_struct(&mut struct_args[i], structs_table)?; + field_ptr = field_ptr.add(field_ptr.align_offset(struct_align)); + + std::ptr::copy(& *struct_ptr as *const _ as *const c_void, field_ptr as *mut c_void, struct_size); + field_ptr = field_ptr.add(struct_size); + }, + _ => { + unreachable!() + } + } + } + return Ok((Box::from_raw(ptr), size, align)); + } else { + return Err(FFIError::InvalidStructName); + } + } + _ => return Err(FFIError::ValueCast) + } + } + } + pub fn exec(&mut self, name: &str, mut args: Vec) -> Result { let function_impl = self.table.get_mut(name).ok_or(FFIError::FunctionNotFound)?; let mut pointer_args = Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs)?; @@ -288,56 +306,75 @@ impl ForeignFunctionTable { Ok(Value::Float(*n)) }, libffi::raw::FFI_TYPE_STRUCT => { - let mut returns = Vec::new(); - let struct_type = self.structs.get_mut(&function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?).ok_or(FFIError::StructNotFound)?; + let name = &function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?; + let struct_type = self.structs.get(name).ok_or(FFIError::StructNotFound)?; let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap(); let ptr = alloc(layout) as *mut c_void; + libffi::raw::ffi_call( &mut function_impl.cif, Some(*function_impl.code_ptr.as_safe_fun()), &mut *ptr as *mut _ as *mut c_void, pointer_args.pointers.as_mut_ptr() as *mut *mut c_void ); - - let mut field_ptr = ptr; - - macro_rules! read_and_push_int { - ($type:ty) => { - { - let n = std::ptr::read(field_ptr as *mut $type); - returns.push(Value::Int(i64::from(n))); - field_ptr = field_ptr.add(std::mem::size_of::<$type>()); - } - } - } - - for i in 0..(struct_type.fields.len()-1) { - let field = struct_type.fields[i]; - match (*field).type_ as u32 { - libffi::raw::FFI_TYPE_UINT8 => read_and_push_int!(u8), - libffi::raw::FFI_TYPE_SINT8 => read_and_push_int!(i8), - libffi::raw::FFI_TYPE_UINT16 => read_and_push_int!(u16), - libffi::raw::FFI_TYPE_SINT16 => read_and_push_int!(i16), - libffi::raw::FFI_TYPE_UINT32 => read_and_push_int!(u32), - libffi::raw::FFI_TYPE_SINT32 => read_and_push_int!(i32), - libffi::raw::FFI_TYPE_UINT64 => { - let n = std::ptr::read(field_ptr as *mut u64); - returns.push(Value::Int(i64::try_from(n).map_err(|_| FFIError::ValueDontFit)?)); - field_ptr = field_ptr.add(std::mem::size_of::()); - }, - libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64), - _ => { - unreachable!() - } - } - } + let struct_val = self.read_struct(ptr, name, struct_type); drop(Box::from_raw(ptr)); - Ok(Value::Struct("texture".into(), returns)) - }, + struct_val + } _ => unreachable!() } }; } + + fn read_struct(&self, ptr: *mut c_void, name: &str, struct_type: &StructImpl) -> Result { + unsafe { + let mut returns = Vec::new(); + let mut field_ptr = ptr; + + for i in 0..(struct_type.fields.len()-1) { + let field = struct_type.fields[i]; + + macro_rules! read_and_push_int { + ($type:ty) => { + { + field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>())); + let n = std::ptr::read(field_ptr as *mut $type); + returns.push(Value::Int(i64::from(n))); + field_ptr = field_ptr.add(std::mem::size_of::<$type>()); + } + } + } + + match (*field).type_ as u32 { + libffi::raw::FFI_TYPE_UINT8 => read_and_push_int!(u8), + libffi::raw::FFI_TYPE_SINT8 => read_and_push_int!(i8), + libffi::raw::FFI_TYPE_UINT16 => read_and_push_int!(u16), + libffi::raw::FFI_TYPE_SINT16 => read_and_push_int!(i16), + libffi::raw::FFI_TYPE_UINT32 => read_and_push_int!(u32), + libffi::raw::FFI_TYPE_SINT32 => read_and_push_int!(i32), + libffi::raw::FFI_TYPE_UINT64 => { + field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::())); + let n = std::ptr::read(field_ptr as *mut u64); + returns.push(Value::Int(i64::try_from(n).map_err(|_| FFIError::ValueDontFit)?)); + field_ptr = field_ptr.add(std::mem::size_of::()); + }, + libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64), + libffi::raw::FFI_TYPE_STRUCT => { + let substruct = struct_type.atom_fields[i].as_str(); + let struct_type = self.structs.get(substruct).ok_or(FFIError::StructNotFound)?; + field_ptr = field_ptr.add(field_ptr.align_offset(struct_type.ffi_type.alignment as usize)); + let struct_val = self.read_struct(field_ptr, substruct, struct_type); + returns.push(struct_val?); + field_ptr = field_ptr.add(struct_type.ffi_type.size); + }, + _ => { + unreachable!() + } + } + } + Ok(Value::Struct(name.into(), returns)) + } + } } #[derive(Clone, Debug)] diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index e8536035..288b457b 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4257,6 +4257,7 @@ impl Machine { } } } + match self.machine_st.try_from_list(args_reg, stub_gen) { Ok(args) => { let args: Vec<_> = args.into_iter().map(|x| map_arg(&mut self.machine_st, x)).collect(); @@ -4264,22 +4265,9 @@ impl Machine { Ok(result) => { match result { Value::Int(n) => self.machine_st.unify_fixnum(Fixnum::build_with(n), return_value), - Value::Struct(name, mut args) => { - args.insert(0, Value::CString(CString::new(name).unwrap())); - let struct_list = heap_loc_as_cell!( - iter_to_heap_list( - &mut self.machine_st.heap, - args.into_iter() - .map(|val| { - match val { - Value::Int(n) => fixnum_as_cell!(Fixnum::build_with(n)), - Value::CString(cstr) => atom_as_cell!(self.machine_st.atom_tbl.build_with(&cstr.into_string().unwrap())), - _ => unreachable!() - } - }), - ) - ); - unify!(self.machine_st, return_value, struct_list); + Value::Struct(name, args) => { + let struct_value = self.build_struct(&name, args); + unify!(self.machine_st, return_value, struct_value); } _ => { unreachable!(); @@ -4302,6 +4290,26 @@ impl Machine { Ok(()) } + fn build_struct(&mut self, name: &str, mut args: Vec) -> HeapCellValue { + args.insert(0, Value::CString(CString::new(name).unwrap())); + let cells: Vec<_> = args.into_iter() + .map(|val| { + match val { + Value::Int(n) => fixnum_as_cell!(Fixnum::build_with(n)), + Value::CString(cstr) => atom_as_cell!(self.machine_st.atom_tbl.build_with(&cstr.into_string().unwrap())), + Value::Struct(name, struct_args) => self.build_struct(&name, struct_args), + _ => unreachable!() + } + }).collect(); + + heap_loc_as_cell!( + iter_to_heap_list( + &mut self.machine_st.heap, + cells.into_iter() + ) + ) + } + #[inline(always)] pub(crate) fn define_foreign_struct(&mut self) -> CallResult { let struct_name = self.deref_register(1); From 0ac93751d0054c1ba1e86d71031b41e94e073a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arroyo=20Calle?= Date: Mon, 27 Feb 2023 20:02:21 +0100 Subject: [PATCH 4/5] FFI: Documentation --- src/ffi.rs | 21 ++++++++++ src/lib/ffi.pl | 101 +++++++++++++++++++++++++++++-------------------- 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 2910a20d..afc52339 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -1,3 +1,24 @@ +/* How does FFI work? + +Each WAM machine has a ForeignFunctionTable instance that contains a table of functions and structs. + +Structs are defined via foreign_struct/2. Basic types are defined by libffi, but struct types need to +be manually defined to get an ffi_type. Additionally, to recover structs from return arguments, we store +fields and atom_fields, as a way to lookup the content of the struct (fields) and the nested structs (atom_fields). + +Functions are defined via use_foreign_module/2. It opens a library and leaks the memory of the library, +to prevent Rust freeing the memory. There's no way to recover that memory at the moment. We get a pointer for +each function and we build a CIF for each one, with the input arguments and the return argument. + +Exec happens via '$foreign_call', we find the function, we try to cast the values that we have to the definition +of the function, we reserve memory for them and we build an array of pointers. To get the return argument, we +reserve enough memory for the return and we build the Scryer values from them. + +Structs are a bit tricky as they need to be aligned. For that, we reserve enough memory (libffi calculates that) +and for each field: we add to the pointer until we're aligned to the next data type we're going to write, we write it, +and finally we add the pointer the size of what we've written. +*/ + use crate::atom_table::Atom; use std::alloc::{alloc, Layout}; diff --git a/src/lib/ffi.pl b/src/lib/ffi.pl index c34cb4ad..cce60ff6 100644 --- a/src/lib/ffi.pl +++ b/src/lib/ffi.pl @@ -1,8 +1,65 @@ :- module(ffi, [use_foreign_module/2, foreign_struct/2]). +/** Foreign Function Interface + +This module contains predicates used to call native code (exposed by the C ABI). +It uses [libffi](https://sourceware.org/libffi/) under the hood. The bridge is very simple +and is very unsafe and should be used with care. FFI isn't the only way to communicate with +the outside world in Prolog: sockets, pipes and HTTP may be good enough for your use case. + +The main predicate is `use_foreign_module/2`. It takes a library name (which depending on the +operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each +function is defined by its name, a list of the type of the arguments, and the return argument. + +Types available are: `sint8`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`, +`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined +with `foreign_struct/2`. + +After that, each function on the lists maps to a predicate created in the ffi module which +are used to call the native code. +The predicate takes the functor name after the function name. Then, the arguments are the input +arguments followed by a return argument. However, functions with return type `void` or `bool` +don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend +on the return value on the native side. + +``` +ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool +ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool +``` + +## Example + +For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library. + +``` +?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]). +``` + +This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it: + +``` +?- ffi:'InitWindow'(800, 600, "Scryer Prolog + Raylib"). +``` + +And a new window should pop up! +*/ + :- use_module(library(lists)). :- use_module(library(error)). - + +%% foreign_struct(+Name, +Elements). +% +% Defines a new struct type with name Name, composed of the elements Elements, which is a list +% of other types. +% +% The name of the types doesn't matter, but the order of Elements must match the ones in the +% native code. +% +% Example: +% +% ``` +% ?- foreign_struct(color, [uint8, uint8, uint8, uint8]). +% ``` foreign_struct(Name, Elements) :- '$define_foreign_struct'(Name, Elements). @@ -16,10 +73,9 @@ assert_predicate(PredicateDefinition) :- functor(Head, Name, NumInputs), term_variables(Head, TermList), Body = ( - lists:maplist(ffi:check_input, Inputs, TermList), '$foreign_call'(Name, TermList, _),! ), - Predicate =.. [:-, Head, Body], + Predicate = (Head:-Body), assertz(ffi:Predicate). assert_predicate(PredicateDefinition) :- @@ -28,10 +84,9 @@ assert_predicate(PredicateDefinition) :- functor(Head, Name, NumInputs), term_variables(Head, TermList), Body = ( - lists:maplist(ffi:check_input, Inputs, TermList), '$foreign_call'(Name, TermList, 1),! ), - Predicate =.. [:-, Head, Body], + Predicate = (Head:-Body), assertz(ffi:Predicate). assert_predicate(PredicateDefinition) :- @@ -43,41 +98,7 @@ assert_predicate(PredicateDefinition) :- term_variables(Head, TermList), Body = ( lists:append(TermListInputs, [TermListReturn], TermList), - lists:maplist(ffi:check_input, Inputs, TermListInputs), '$foreign_call'(Name, TermListInputs, TermListReturn),! ), - Predicate =.. [:-, Head, Body], + Predicate = (Head:-Body), assertz(ffi:Predicate). - -check_input(sint8, Var) :- - must_be(integer, Var), - ( - (Var > -129, Var < 128) -> - true - ; domain_error(integer_does_not_fit, Var, foreign_call/3) - ). -check_input(sint16, Var) :- - must_be(integer, Var), - ( - (Var > -32769, Var < 32768) -> - true - ; domain_error(integer_does_not_fit, Var, foreign_call/3) - ). -check_input(sint32, Var) :- - must_be(integer, Var), - ( - (Var > -2147483649, Var < 2147483648) -> - true - ; domain_error(integer_does_not_fit, Var, foreign_call/3) - ). -check_input(sint64, Var) :- - must_be(integer, Var). -check_input(f32, _Var). -check_input(f64, _Var). -check_input(cstr, Var) :- - must_be(chars, Var). -check_input(_, Var). -% must_be(list, Var). - -% TODO: assert native predicates. -% They MUST validate types From 73df96244dca8b12fb9d02f5047ee55a40ed20ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arroyo=20Calle?= Date: Wed, 1 Mar 2023 22:10:26 +0100 Subject: [PATCH 5/5] Fill more cases --- src/ffi.rs | 2 ++ src/machine/system_calls.rs | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index afc52339..a48ed62e 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -306,6 +306,7 @@ impl ForeignFunctionTable { Ok(Value::Int(i64::try_from(*n).map_err(|_| FFIError::ValueDontFit)?)) }, libffi::raw::FFI_TYPE_SINT64 => call_and_return!(i64), + libffi::raw::FFI_TYPE_POINTER => call_and_return!(*mut c_void), libffi::raw::FFI_TYPE_FLOAT => { let mut n: Box = Box::new(0.0); libffi::raw::ffi_call( @@ -380,6 +381,7 @@ impl ForeignFunctionTable { field_ptr = field_ptr.add(std::mem::size_of::()); }, libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64), + libffi::raw::FFI_TYPE_POINTER => read_and_push_int!(i64), libffi::raw::FFI_TYPE_STRUCT => { let substruct = struct_type.atom_fields[i].as_str(); let struct_type = self.structs.get(substruct).ok_or(FFIError::StructNotFound)?; diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 1c367a80..39311276 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4310,12 +4310,17 @@ impl Machine { Ok(result) => { match result { Value::Int(n) => self.machine_st.unify_fixnum(Fixnum::build_with(n), return_value), + Value::Float(n) => { + let n = float_alloc!(n, self.machine_st.arena); + self.machine_st.unify_f64(n, return_value) + }, Value::Struct(name, args) => { let struct_value = self.build_struct(&name, args); unify!(self.machine_st, return_value, struct_value); } - _ => { - unreachable!(); + Value::CString(cstr) => { + let cstr = self.machine_st.atom_tbl.build_with(cstr.to_str().unwrap()); + self.machine_st.unify_complete_string(cstr, return_value); } } return Ok(()); @@ -4340,10 +4345,10 @@ impl Machine { let cells: Vec<_> = args.into_iter() .map(|val| { match val { - Value::Int(n) => fixnum_as_cell!(Fixnum::build_with(n)), - Value::CString(cstr) => atom_as_cell!(self.machine_st.atom_tbl.build_with(&cstr.into_string().unwrap())), - Value::Struct(name, struct_args) => self.build_struct(&name, struct_args), - _ => unreachable!() + Value::Int(n) => fixnum_as_cell!(Fixnum::build_with(n)), + Value::Float(n) => HeapCellValue::from(float_alloc!(n, self.machine_st.arena)), + Value::CString(cstr) => atom_as_cell!(self.machine_st.atom_tbl.build_with(&cstr.into_string().unwrap())), + Value::Struct(name, struct_args) => self.build_struct(&name, struct_args), } }).collect();