Merge pull request #2786 from Skgland/ffi-f64-tests
add ffi tests & fix ffi
This commit is contained in:
1030
src/ffi.rs
1030
src/ffi.rs
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@ The main predicate is `use_foreign_module/2`. It takes a library name (which dep
|
||||
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
|
||||
Types available are: `sint8`/`i8`, `uint8`/`u8`, `sint16`/`i16`, `uint16`/`u16`, `sint32`/`i32`, `uint32`/`u32`, `sint64`/`i64`,
|
||||
`uint64`/`u64`, `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
|
||||
@@ -27,6 +27,12 @@ ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return typ
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||
```
|
||||
|
||||
## Notes regarding cstr
|
||||
|
||||
- When using `cstr` as an argument type the string will be deallocated once the function returns.
|
||||
- When using `cstr` as a return type the string will be copied and won't be deallocated.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
||||
|
||||
@@ -78,7 +78,7 @@ impl OutputStreamConfig {
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputStreamConfigInner {
|
||||
String(String),
|
||||
String(Cow<'static, str>),
|
||||
Stdin,
|
||||
Channel(Receiver<Vec<u8>>),
|
||||
}
|
||||
@@ -97,7 +97,7 @@ pub struct InputStreamConfig {
|
||||
|
||||
impl InputStreamConfig {
|
||||
/// Gets input from string.
|
||||
pub fn string(s: impl Into<String>) -> Self {
|
||||
pub fn string(s: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
inner: InputStreamConfigInner::String(s.into()),
|
||||
}
|
||||
@@ -123,7 +123,10 @@ impl InputStreamConfig {
|
||||
|
||||
fn into_stream(self, arena: &mut Arena, add_history: bool) -> Stream {
|
||||
match self.inner {
|
||||
InputStreamConfigInner::String(s) => Stream::from_owned_string(s, arena),
|
||||
InputStreamConfigInner::String(s) => match s {
|
||||
Cow::Owned(s) => Stream::from_owned_string(s, arena),
|
||||
Cow::Borrowed(s) => Stream::from_static_string(s, arena),
|
||||
},
|
||||
InputStreamConfigInner::Stdin => Stream::stdin(arena, add_history),
|
||||
InputStreamConfigInner::Channel(channel) => Stream::input_channel(channel, arena),
|
||||
}
|
||||
@@ -156,7 +159,7 @@ impl StreamConfig {
|
||||
/// Binds the output and error streams to memory buffers and has an empty input.
|
||||
pub fn in_memory() -> Self {
|
||||
StreamConfig {
|
||||
user_input: InputStreamConfig::string(""),
|
||||
user_input: InputStreamConfig::string(String::new()),
|
||||
user_output: OutputStreamConfig::memory(),
|
||||
user_error: OutputStreamConfig::memory(),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::atom_table::*;
|
||||
use crate::parser::ast::*;
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
use crate::ffi::FFIError;
|
||||
use crate::ffi::FfiError;
|
||||
use crate::forms::*;
|
||||
use crate::functor_macro::*;
|
||||
use crate::machine::heap::*;
|
||||
@@ -613,14 +613,20 @@ impl MachineState {
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
pub(super) fn ffi_error(&self, err: FFIError) -> MachineError {
|
||||
pub(super) fn ffi_error(&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"),
|
||||
FfiError::ValueCast => atom!("value_cast"),
|
||||
FfiError::ValueOutOfRange => atom!("value_out_of_range"),
|
||||
FfiError::InvalidFfiType => atom!("invalid_ffi_type"),
|
||||
FfiError::InvalidArgumentType => atom!("invalid_argument_type"),
|
||||
FfiError::InvalidArgument => atom!("invalid_argument"),
|
||||
FfiError::InvalidStruct => atom!("invalid_struct"),
|
||||
FfiError::FunctionNotFound => atom!("function_not_found"),
|
||||
FfiError::StructNotFound => atom!("struct_not_found"),
|
||||
FfiError::ArgCountMismatch => atom!("mismatched_argument_count"),
|
||||
FfiError::AllocationFailed => atom!("allocation_failed"),
|
||||
FfiError::LayoutError => atom!("layout_error"),
|
||||
FfiError::UnsupportedAbi => atom!("unsupported_abi"),
|
||||
};
|
||||
let stub = functor!(atom!("ffi_error"), [atom_as_cell(error_atom)]);
|
||||
|
||||
|
||||
@@ -5004,65 +5004,80 @@ impl Machine {
|
||||
#[cfg(feature = "ffi")]
|
||||
#[inline(always)]
|
||||
pub(crate) fn foreign_call(&mut self) -> CallResult {
|
||||
fn stub_gen() -> Vec<FunctorElement> {
|
||||
functor_stub(atom!("foreign_call"), 3)
|
||||
}
|
||||
|
||||
fn map_arg(
|
||||
machine_st: &mut MachineState,
|
||||
source: HeapCellValue,
|
||||
) -> Result<crate::ffi::Value, FfiError> {
|
||||
if let Ok(number) = Number::try_from((source, &machine_st.arena.f64_tbl)) {
|
||||
Ok(Value::Number(number))
|
||||
} else if let Some(string) = machine_st.value_to_str_like(source) {
|
||||
Ok(Value::CString(CString::new(&*string.as_str()).unwrap()))
|
||||
} else if let Ok(args) = machine_st.try_from_list(source, stub_gen) {
|
||||
// structs are lists represented as lists
|
||||
// the head is a string with the struct type name
|
||||
// the tail are the struct field values
|
||||
|
||||
let mut iter = args.into_iter();
|
||||
if let Some(struct_name) = machine_st.value_to_str_like(iter.next().unwrap()) {
|
||||
Ok(Value::Struct(
|
||||
struct_name.as_str().to_string(),
|
||||
iter.map(|x| map_arg(machine_st, x))
|
||||
.collect::<Result<_, _>>()?,
|
||||
))
|
||||
} else {
|
||||
// empty list is an invalid struct repr
|
||||
Err(FfiError::InvalidStruct)
|
||||
}
|
||||
} else {
|
||||
Err(FfiError::InvalidArgument)
|
||||
}
|
||||
}
|
||||
|
||||
let function_name = self.deref_register(1);
|
||||
let args_reg = self.deref_register(2);
|
||||
let return_value = self.deref_register(3);
|
||||
if let Some(function_name) = self.machine_st.value_to_str_like(function_name) {
|
||||
let stub_gen = || functor_stub(atom!("foreign_call"), 3);
|
||||
fn map_arg(machine_st: &mut MachineState, source: HeapCellValue) -> crate::ffi::Value {
|
||||
match Number::try_from((source, &machine_st.arena.f64_tbl)) {
|
||||
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(machine_st, x)).collect(),
|
||||
)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.machine_st.try_from_list(args_reg, stub_gen) {
|
||||
Ok(args) => {
|
||||
let args: Vec<_> = args
|
||||
let args = match args
|
||||
.into_iter()
|
||||
.map(|x| map_arg(&mut self.machine_st, x))
|
||||
.collect();
|
||||
match self
|
||||
.foreign_function_table
|
||||
.exec(&function_name.as_str(), args)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
{
|
||||
Ok(args) => args,
|
||||
Err(err) => {
|
||||
let err = self.machine_st.ffi_error(err);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
};
|
||||
|
||||
match self.foreign_function_table.exec(
|
||||
&function_name.as_str(),
|
||||
args,
|
||||
&mut self.machine_st.arena,
|
||||
) {
|
||||
Ok(result) => {
|
||||
match result {
|
||||
Value::Int(n) => self.machine_st.unify_fixnum(
|
||||
Fixnum::build_with_checked(n).unwrap_or_else(|_| {
|
||||
todo!("handle integer values that don't fit in fixnum")
|
||||
}),
|
||||
return_value,
|
||||
),
|
||||
Value::Float(n) => {
|
||||
let n = float_alloc!(n, self.machine_st.arena);
|
||||
self.machine_st.unify_f64(n, return_value)
|
||||
}
|
||||
Value::Number(n) => match n {
|
||||
Number::Float(OrderedFloat(n)) => {
|
||||
let n = float_alloc!(n, self.machine_st.arena);
|
||||
self.machine_st.unify_f64(n, return_value)
|
||||
}
|
||||
Number::Integer(typed_arena_ptr) => {
|
||||
self.machine_st.unify_big_int(typed_arena_ptr, return_value)
|
||||
}
|
||||
Number::Rational(typed_arena_ptr) => {
|
||||
self.machine_st
|
||||
.unify_rational(typed_arena_ptr, return_value);
|
||||
}
|
||||
Number::Fixnum(fixnum) => {
|
||||
self.machine_st.unify_fixnum(fixnum, return_value)
|
||||
}
|
||||
},
|
||||
Value::Struct(name, args) => {
|
||||
let struct_value = resource_error_call_result!(
|
||||
self.machine_st,
|
||||
@@ -5083,10 +5098,8 @@ impl Machine {
|
||||
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));
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5102,34 +5115,26 @@ impl Machine {
|
||||
fn build_struct(&mut self, name: &str, mut args: Vec<Value>) -> Result<HeapCellValue, usize> {
|
||||
args.insert(0, Value::CString(CString::new(name).unwrap()));
|
||||
|
||||
let mut expanded_args = Vec::with_capacity(args.len());
|
||||
let cells: Vec<_> = args
|
||||
.into_iter()
|
||||
.map(|val| {
|
||||
Ok(match val {
|
||||
Value::Number(n) => match n {
|
||||
Number::Float(OrderedFloat(f)) => {
|
||||
HeapCellValue::from(float_alloc!(f, self.machine_st.arena))
|
||||
}
|
||||
_ => integer_as_cell!(n),
|
||||
},
|
||||
Value::CString(cstr) => atom_as_cell!(AtomTable::build_with(
|
||||
&self.machine_st.atom_tbl,
|
||||
&cstr.into_string().unwrap()
|
||||
)),
|
||||
Value::Struct(name, struct_args) => self.build_struct(&name, struct_args)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, usize>>()?;
|
||||
|
||||
for val in args {
|
||||
expanded_args.push(match val {
|
||||
Value::Int(n) => {
|
||||
if let Ok(fixnum) = Fixnum::build_with_checked(n) {
|
||||
fixnum_as_cell!(fixnum)
|
||||
} else {
|
||||
integer_as_cell!(Number::Integer(arena_alloc!(
|
||||
Integer::from(n),
|
||||
&mut self.machine_st.arena
|
||||
)))
|
||||
}
|
||||
}
|
||||
Value::Float(n) => HeapCellValue::from(float_alloc!(n, self.machine_st.arena)),
|
||||
Value::CString(cstr) => atom_as_cell!(AtomTable::build_with(
|
||||
&self.machine_st.atom_tbl,
|
||||
&cstr.into_string().unwrap()
|
||||
)),
|
||||
Value::Struct(name, struct_args) => self.build_struct(&name, struct_args)?,
|
||||
});
|
||||
}
|
||||
|
||||
sized_iter_to_heap_list(
|
||||
&mut self.machine_st.heap,
|
||||
expanded_args.len(),
|
||||
expanded_args.into_iter(),
|
||||
)
|
||||
sized_iter_to_heap_list(&mut self.machine_st.heap, cells.len(), cells.into_iter())
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
@@ -5150,7 +5155,11 @@ impl Machine {
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
self.foreign_function_table
|
||||
.define_struct(&struct_name.as_str(), fields);
|
||||
.define_struct(&struct_name.as_str(), fields)
|
||||
.map_err(|err| {
|
||||
let ffi_error = self.machine_st.ffi_error(err);
|
||||
self.machine_st.error_form(ffi_error, stub_gen())
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
self.machine_st.fail = true;
|
||||
|
||||
@@ -583,10 +583,13 @@ mod private {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FitsInFixnumSeal> MightNotFitInFixnumSeal for T {}
|
||||
impl MightNotFitInFixnumSeal for i64 {}
|
||||
impl MightNotFitInFixnumSeal for u64 {}
|
||||
impl MightNotFitInFixnumSeal for &Integer {}
|
||||
impl MightNotFitInFixnumSeal for Integer {}
|
||||
impl MightNotFitInFixnumSeal for usize {}
|
||||
impl MightNotFitInFixnumSeal for isize {}
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
|
||||
Reference in New Issue
Block a user