diff --git a/Cargo.lock b/Cargo.lock index 3b817784..1b504f1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -963,6 +963,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" @@ -1845,6 +1874,8 @@ dependencies = [ "lazy_static", "lexical", "libc", + "libffi", + "libloading", "modular-bitfield", "native-tls", "ordered-float", diff --git a/Cargo.toml b/Cargo.toml index b21d4921..9e2ca883 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" derive_deref = "1.1.1" [dev-dependencies] diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 0ea3fa59..d2cee3bb 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -546,6 +546,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")))] @@ -1704,6 +1710,9 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::CallHttpListen(_) | &Instruction::CallHttpAccept(_) | &Instruction::CallHttpAnswer(_) | + &Instruction::CallLoadForeignLib(_) | + &Instruction::CallForeignCall(_) | + &Instruction::CallDefineForeignStruct(_) | &Instruction::CallPredicateDefined(_) | &Instruction::CallStripModule(_) | &Instruction::CallCurrentTime(_) | @@ -1920,6 +1929,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..a48ed62e --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,444 @@ +/* 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}; +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, Clone)] +struct StructImpl { + ffi_type: ffi_type, + fields: Vec<*mut ffi_type>, + atom_fields: Vec, +} + +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, 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, 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!() + } + } + } + } + } + + 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(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 => 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; + 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 => { + 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) + } + } + } + Ok(PointerArgs { + pointers, + _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)?; + + 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 => 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, + pointer_args.pointers.as_mut_ptr() as *mut *mut c_void + ); + 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( + &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::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::Float(*n)) + }, + libffi::raw::FFI_TYPE_STRUCT => { + 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 struct_val = self.read_struct(ptr, name, struct_type); + drop(Box::from_raw(ptr)); + 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_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)?; + 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)] +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..cce60ff6 --- /dev/null +++ b/src/lib/ffi.pl @@ -0,0 +1,104 @@ +:- 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). + +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 = ( + '$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 = ( + '$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), + '$foreign_call'(Name, TermListInputs, TermListReturn),! + ), + Predicate = (Head:-Body), + assertz(ffi:Predicate). diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index f0ed0469..e2e144c5 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4197,6 +4197,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/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/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 21fc3e54..5193eb66 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -27,6 +27,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::*; @@ -66,6 +67,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)] @@ -435,6 +437,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 e9ce7731..39311276 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}; @@ -4217,6 +4219,170 @@ 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::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); + } + 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(()); + }, + Err(e) => { + 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)); + } + } + } + Err(e) => return Err(e) + } + } + self.machine_st.fail = true; + 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::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(); + + 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); + 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());