Merge pull request #2786 from Skgland/ffi-f64-tests
add ffi tests & fix ffi
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -591,6 +591,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "current_platform"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a74858bcfe44b22016cb49337d7b6f04618c58e5dbfdef61b06b8c434324a0bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dashu"
|
name = "dashu"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -2689,6 +2695,7 @@ dependencies = [
|
|||||||
"crossterm",
|
"crossterm",
|
||||||
"crrl",
|
"crrl",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
|
"current_platform",
|
||||||
"dashu",
|
"dashu",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ js-sys = "0.3"
|
|||||||
ouroboros = "0.18"
|
ouroboros = "0.18"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
current_platform = "0.2.0"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
serial_test = "3.1.1"
|
serial_test = "3.1.1"
|
||||||
|
|
||||||
|
|||||||
1008
src/ffi.rs
1008
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
|
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.
|
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`,
|
Types available are: `sint8`/`i8`, `uint8`/`u8`, `sint16`/`i16`, `uint16`/`u16`, `sint32`/`i32`, `uint32`/`u32`, `sint64`/`i64`,
|
||||||
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
`uint64`/`u64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||||
with `foreign_struct/2`.
|
with `foreign_struct/2`.
|
||||||
|
|
||||||
After that, each function on the lists maps to a predicate created in the ffi module which
|
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
|
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
|
## Example
|
||||||
|
|
||||||
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
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)]
|
#[derive(Debug)]
|
||||||
enum InputStreamConfigInner {
|
enum InputStreamConfigInner {
|
||||||
String(String),
|
String(Cow<'static, str>),
|
||||||
Stdin,
|
Stdin,
|
||||||
Channel(Receiver<Vec<u8>>),
|
Channel(Receiver<Vec<u8>>),
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ pub struct InputStreamConfig {
|
|||||||
|
|
||||||
impl InputStreamConfig {
|
impl InputStreamConfig {
|
||||||
/// Gets input from string.
|
/// Gets input from string.
|
||||||
pub fn string(s: impl Into<String>) -> Self {
|
pub fn string(s: impl Into<Cow<'static, str>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: InputStreamConfigInner::String(s.into()),
|
inner: InputStreamConfigInner::String(s.into()),
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,10 @@ impl InputStreamConfig {
|
|||||||
|
|
||||||
fn into_stream(self, arena: &mut Arena, add_history: bool) -> Stream {
|
fn into_stream(self, arena: &mut Arena, add_history: bool) -> Stream {
|
||||||
match self.inner {
|
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::Stdin => Stream::stdin(arena, add_history),
|
||||||
InputStreamConfigInner::Channel(channel) => Stream::input_channel(channel, arena),
|
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.
|
/// Binds the output and error streams to memory buffers and has an empty input.
|
||||||
pub fn in_memory() -> Self {
|
pub fn in_memory() -> Self {
|
||||||
StreamConfig {
|
StreamConfig {
|
||||||
user_input: InputStreamConfig::string(""),
|
user_input: InputStreamConfig::string(String::new()),
|
||||||
user_output: OutputStreamConfig::memory(),
|
user_output: OutputStreamConfig::memory(),
|
||||||
user_error: OutputStreamConfig::memory(),
|
user_error: OutputStreamConfig::memory(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::atom_table::*;
|
|||||||
use crate::parser::ast::*;
|
use crate::parser::ast::*;
|
||||||
|
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
use crate::ffi::FFIError;
|
use crate::ffi::FfiError;
|
||||||
use crate::forms::*;
|
use crate::forms::*;
|
||||||
use crate::functor_macro::*;
|
use crate::functor_macro::*;
|
||||||
use crate::machine::heap::*;
|
use crate::machine::heap::*;
|
||||||
@@ -613,14 +613,20 @@ impl MachineState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ffi")]
|
#[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 {
|
let error_atom = match err {
|
||||||
FFIError::ValueCast => atom!("value_cast"),
|
FfiError::ValueCast => atom!("value_cast"),
|
||||||
FFIError::ValueDontFit => atom!("value_dont_fit"),
|
FfiError::ValueOutOfRange => atom!("value_out_of_range"),
|
||||||
FFIError::InvalidFFIType => atom!("invalid_ffi_type"),
|
FfiError::InvalidFfiType => atom!("invalid_ffi_type"),
|
||||||
FFIError::InvalidStructName => atom!("invalid_struct_name"),
|
FfiError::InvalidArgumentType => atom!("invalid_argument_type"),
|
||||||
FFIError::FunctionNotFound => atom!("function_not_found"),
|
FfiError::InvalidArgument => atom!("invalid_argument"),
|
||||||
FFIError::StructNotFound => atom!("struct_not_found"),
|
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)]);
|
let stub = functor!(atom!("ffi_error"), [atom_as_cell(error_atom)]);
|
||||||
|
|
||||||
|
|||||||
@@ -5004,65 +5004,80 @@ impl Machine {
|
|||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn foreign_call(&mut self) -> CallResult {
|
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 function_name = self.deref_register(1);
|
||||||
let args_reg = self.deref_register(2);
|
let args_reg = self.deref_register(2);
|
||||||
let return_value = self.deref_register(3);
|
let return_value = self.deref_register(3);
|
||||||
if let Some(function_name) = self.machine_st.value_to_str_like(function_name) {
|
if let Some(function_name) = self.machine_st.value_to_str_like(function_name) {
|
||||||
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) {
|
match self.machine_st.try_from_list(args_reg, stub_gen) {
|
||||||
Ok(args) => {
|
Ok(args) => {
|
||||||
let args: Vec<_> = args
|
let args = match args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| map_arg(&mut self.machine_st, x))
|
.map(|x| map_arg(&mut self.machine_st, x))
|
||||||
.collect();
|
.collect::<Result<Vec<_>, _>>()
|
||||||
match self
|
|
||||||
.foreign_function_table
|
|
||||||
.exec(&function_name.as_str(), args)
|
|
||||||
{
|
{
|
||||||
|
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) => {
|
Ok(result) => {
|
||||||
match result {
|
match result {
|
||||||
Value::Int(n) => self.machine_st.unify_fixnum(
|
Value::Number(n) => match n {
|
||||||
Fixnum::build_with_checked(n).unwrap_or_else(|_| {
|
Number::Float(OrderedFloat(n)) => {
|
||||||
todo!("handle integer values that don't fit in fixnum")
|
|
||||||
}),
|
|
||||||
return_value,
|
|
||||||
),
|
|
||||||
Value::Float(n) => {
|
|
||||||
let n = float_alloc!(n, self.machine_st.arena);
|
let n = float_alloc!(n, self.machine_st.arena);
|
||||||
self.machine_st.unify_f64(n, return_value)
|
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) => {
|
Value::Struct(name, args) => {
|
||||||
let struct_value = resource_error_call_result!(
|
let struct_value = resource_error_call_result!(
|
||||||
self.machine_st,
|
self.machine_st,
|
||||||
@@ -5083,10 +5098,8 @@ impl Machine {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let stub = functor_stub(atom!("current_input"), 1);
|
|
||||||
let err = self.machine_st.ffi_error(e);
|
let err = self.machine_st.ffi_error(e);
|
||||||
|
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||||
return Err(self.machine_st.error_form(err, stub));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5102,34 +5115,26 @@ impl Machine {
|
|||||||
fn build_struct(&mut self, name: &str, mut args: Vec<Value>) -> Result<HeapCellValue, usize> {
|
fn build_struct(&mut self, name: &str, mut args: Vec<Value>) -> Result<HeapCellValue, usize> {
|
||||||
args.insert(0, Value::CString(CString::new(name).unwrap()));
|
args.insert(0, Value::CString(CString::new(name).unwrap()));
|
||||||
|
|
||||||
let mut expanded_args = Vec::with_capacity(args.len());
|
let cells: Vec<_> = args
|
||||||
|
.into_iter()
|
||||||
for val in args {
|
.map(|val| {
|
||||||
expanded_args.push(match val {
|
Ok(match val {
|
||||||
Value::Int(n) => {
|
Value::Number(n) => match n {
|
||||||
if let Ok(fixnum) = Fixnum::build_with_checked(n) {
|
Number::Float(OrderedFloat(f)) => {
|
||||||
fixnum_as_cell!(fixnum)
|
HeapCellValue::from(float_alloc!(f, self.machine_st.arena))
|
||||||
} else {
|
|
||||||
integer_as_cell!(Number::Integer(arena_alloc!(
|
|
||||||
Integer::from(n),
|
|
||||||
&mut self.machine_st.arena
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
_ => integer_as_cell!(n),
|
||||||
Value::Float(n) => HeapCellValue::from(float_alloc!(n, self.machine_st.arena)),
|
},
|
||||||
Value::CString(cstr) => atom_as_cell!(AtomTable::build_with(
|
Value::CString(cstr) => atom_as_cell!(AtomTable::build_with(
|
||||||
&self.machine_st.atom_tbl,
|
&self.machine_st.atom_tbl,
|
||||||
&cstr.into_string().unwrap()
|
&cstr.into_string().unwrap()
|
||||||
)),
|
)),
|
||||||
Value::Struct(name, struct_args) => self.build_struct(&name, struct_args)?,
|
Value::Struct(name, struct_args) => self.build_struct(&name, struct_args)?,
|
||||||
});
|
})
|
||||||
}
|
})
|
||||||
|
.collect::<Result<_, usize>>()?;
|
||||||
|
|
||||||
sized_iter_to_heap_list(
|
sized_iter_to_heap_list(&mut self.machine_st.heap, cells.len(), cells.into_iter())
|
||||||
&mut self.machine_st.heap,
|
|
||||||
expanded_args.len(),
|
|
||||||
expanded_args.into_iter(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
@@ -5150,7 +5155,11 @@ impl Machine {
|
|||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
self.foreign_function_table
|
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.machine_st.fail = true;
|
self.machine_st.fail = true;
|
||||||
|
|||||||
@@ -583,10 +583,13 @@ mod private {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: FitsInFixnumSeal> MightNotFitInFixnumSeal for T {}
|
||||||
impl MightNotFitInFixnumSeal for i64 {}
|
impl MightNotFitInFixnumSeal for i64 {}
|
||||||
|
impl MightNotFitInFixnumSeal for u64 {}
|
||||||
impl MightNotFitInFixnumSeal for &Integer {}
|
impl MightNotFitInFixnumSeal for &Integer {}
|
||||||
impl MightNotFitInFixnumSeal for Integer {}
|
impl MightNotFitInFixnumSeal for Integer {}
|
||||||
impl MightNotFitInFixnumSeal for usize {}
|
impl MightNotFitInFixnumSeal for usize {}
|
||||||
|
impl MightNotFitInFixnumSeal for isize {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(private_bounds)]
|
#[allow(private_bounds)]
|
||||||
|
|||||||
21
tests-pl/ffi_cstr.pl
Normal file
21
tests-pl/ffi_cstr.pl
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
:- use_module(library(os)).
|
||||||
|
:- use_module(library(ffi)).
|
||||||
|
|
||||||
|
init :-
|
||||||
|
read(Body),
|
||||||
|
term_variables(Body, [LIB]),
|
||||||
|
Body,
|
||||||
|
use_foreign_module(LIB, [
|
||||||
|
'ffi_cstr_len'([cstr], u64),
|
||||||
|
'ffi_example_cstr'([], cstr),
|
||||||
|
'ffi_null_cstr'([], cstr)
|
||||||
|
]).
|
||||||
|
|
||||||
|
test :-
|
||||||
|
ffi:'ffi_cstr_len'("Scryer Prolog", Len),
|
||||||
|
ffi:'ffi_example_cstr'(Str),
|
||||||
|
ffi:'ffi_null_cstr'(Null),
|
||||||
|
ffi:'ffi_cstr_len'(0, MaxU64),
|
||||||
|
write((Len-Str-Null-MaxU64)).
|
||||||
|
|
||||||
|
:- initialization((init,test)).
|
||||||
18
tests-pl/ffi_f64_minus_zero.pl
Normal file
18
tests-pl/ffi_f64_minus_zero.pl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
:- use_module(library(os)).
|
||||||
|
:- use_module(library(ffi)).
|
||||||
|
|
||||||
|
test :-
|
||||||
|
read(Body),
|
||||||
|
term_variables(Body, [LIB]),
|
||||||
|
Body,
|
||||||
|
use_foreign_module(LIB, ['ffi_f64_minus_zero'([], f64), 'signum'([f64], f64)]),
|
||||||
|
ffi:'ffi_f64_minus_zero'(N),
|
||||||
|
A is max(0.0, N),
|
||||||
|
B is max(N, 0.0),
|
||||||
|
ffi:'signum'(A, SA),
|
||||||
|
ffi:'signum'(B, SB),
|
||||||
|
write((SA, SB)),
|
||||||
|
-1.0 is SA, % incorrect, based on https://www.swi-prolog.org/pldoc/man?function=max/2 -0.0 is less than 0.0 so A and B should be 0.0 for which signum should be 1
|
||||||
|
1.0 is SB.
|
||||||
|
|
||||||
|
:- initialization(test).
|
||||||
12
tests-pl/ffi_f64_nan.pl
Normal file
12
tests-pl/ffi_f64_nan.pl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
:- use_module(library(os)).
|
||||||
|
:- use_module(library(ffi)).
|
||||||
|
|
||||||
|
test :-
|
||||||
|
read(Body),
|
||||||
|
term_variables(Body, [LIB]),
|
||||||
|
Body,
|
||||||
|
use_foreign_module(LIB, ['ffi_f64_nan'([], f64)]),
|
||||||
|
ffi:'ffi_f64_nan'(N),
|
||||||
|
_ is round(N).
|
||||||
|
|
||||||
|
:- initialization(test).
|
||||||
13
tests-pl/ffi_invalid_type.pl
Normal file
13
tests-pl/ffi_invalid_type.pl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
:- use_module(library(os)).
|
||||||
|
:- use_module(library(ffi)).
|
||||||
|
|
||||||
|
test :-
|
||||||
|
read(Body),
|
||||||
|
term_variables(Body, [LIB]),
|
||||||
|
Body,
|
||||||
|
use_foreign_module(LIB, [
|
||||||
|
%% should be void instead of c_void
|
||||||
|
'ffi_invalid_type'([], c_void)
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- initialization(test).
|
||||||
36
tests-pl/ffi_return_values.pl
Normal file
36
tests-pl/ffi_return_values.pl
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
:- use_module(library(os)).
|
||||||
|
:- use_module(library(ffi)).
|
||||||
|
|
||||||
|
test :-
|
||||||
|
read(Body),
|
||||||
|
term_variables(Body, [LIB]),
|
||||||
|
Body,
|
||||||
|
use_foreign_module(LIB, [
|
||||||
|
'ffi_return_values_true'([], bool),
|
||||||
|
'ffi_return_values_false'([], bool),
|
||||||
|
'ffi_return_values_i8'([], sint8),
|
||||||
|
'ffi_return_values_u8'([], uint8),
|
||||||
|
'ffi_return_values_i16'([], sint16),
|
||||||
|
'ffi_return_values_u16'([], uint16),
|
||||||
|
'ffi_return_values_i32'([], sint32),
|
||||||
|
'ffi_return_values_u32'([], uint32),
|
||||||
|
'ffi_return_values_i64'([], sint64),
|
||||||
|
'ffi_return_values_u64'([], uint64),
|
||||||
|
'ffi_return_values_f32'([], f32),
|
||||||
|
'ffi_return_values_f64'([], f64)
|
||||||
|
]),
|
||||||
|
ffi:'ffi_return_values_true',
|
||||||
|
(\+ ffi:'ffi_return_values_false'),
|
||||||
|
ffi:'ffi_return_values_i8'(I8),
|
||||||
|
ffi:'ffi_return_values_u8'(U8),
|
||||||
|
ffi:'ffi_return_values_i16'(I16),
|
||||||
|
ffi:'ffi_return_values_u16'(U16),
|
||||||
|
ffi:'ffi_return_values_i32'(I32),
|
||||||
|
ffi:'ffi_return_values_u32'(U32),
|
||||||
|
ffi:'ffi_return_values_i64'(I64),
|
||||||
|
ffi:'ffi_return_values_u64'(U64),
|
||||||
|
ffi:'ffi_return_values_f32'(F32),
|
||||||
|
ffi:'ffi_return_values_f64'(F64),
|
||||||
|
write((i8-I8, u8-U8, i16-I16, u16-U16, i32-I32, u32-U32, i64-I64, u64-U64, f32-F32, f64-F64)).
|
||||||
|
|
||||||
|
:- initialization(test).
|
||||||
15
tests-pl/ffi_struct.pl
Normal file
15
tests-pl/ffi_struct.pl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
:- use_module(library(os)).
|
||||||
|
:- use_module(library(ffi)).
|
||||||
|
|
||||||
|
test :-
|
||||||
|
read(Body),
|
||||||
|
term_variables(Body, [LIB]),
|
||||||
|
Body,
|
||||||
|
foreign_struct(pg, [uint8, uint16, uint32, uint64, uint8, f32, f64]),
|
||||||
|
use_foreign_module(LIB, ['construct'([uint8, uint16, uint32, uint64, uint8, f32, f64], pg), 'modify'([pg], pg)]),
|
||||||
|
ffi:'construct'(8, 12, 46, 40, 127, 1.368, -4.587, PG),
|
||||||
|
write(("PG"-PG)), nl,
|
||||||
|
ffi:'modify'(PG, [pg, A, B, C, D, A2, E, F]),
|
||||||
|
write(("PG2"-[pg, A, B, C, D, A2, E, F])), nl.
|
||||||
|
|
||||||
|
:- initialization(test).
|
||||||
285
tests/scryer/ffi.rs
Normal file
285
tests/scryer/ffi.rs
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
use std::{
|
||||||
|
env::consts::{DLL_PREFIX, DLL_SUFFIX},
|
||||||
|
io::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Stdio,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::helper::load_module_test_with_input;
|
||||||
|
|
||||||
|
use current_platform::CURRENT_PLATFORM;
|
||||||
|
|
||||||
|
const TMP_DIR: &str = env!("CARGO_TARGET_TMPDIR");
|
||||||
|
|
||||||
|
// each test is building its own library so that they can easier run in parallel,
|
||||||
|
// i.e. don't need to wait for a large dynamic library to compile,
|
||||||
|
// also rusts test infra currently has no functionallity for a setup/befor step
|
||||||
|
fn build_dynamic_library(name: &str, src: &str) -> PathBuf {
|
||||||
|
let tmp_dir: &Path = TMP_DIR.as_ref();
|
||||||
|
|
||||||
|
let mut child = std::process::Command::new("rustc")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.args(["--edition", "2024"])
|
||||||
|
.arg(format!("--target={CURRENT_PLATFORM}"))
|
||||||
|
.arg("--crate-type=dylib")
|
||||||
|
.arg(format!("--crate-name={name}"))
|
||||||
|
.arg("--out-dir")
|
||||||
|
.arg(tmp_dir)
|
||||||
|
.arg("-")
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
child
|
||||||
|
.stdin
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.write_all(src.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(child.wait().unwrap().success());
|
||||||
|
|
||||||
|
tmp_dir.join(format!("{DLL_PREFIX}{name}{DLL_SUFFIX}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(miri, ignore = "ffi")]
|
||||||
|
fn ffi_f64_nan() {
|
||||||
|
let dynlib_path = build_dynamic_library(
|
||||||
|
"ffi_f64_nan",
|
||||||
|
r##"
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_f64_nan() -> f64 {
|
||||||
|
f64::NAN
|
||||||
|
}
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
|
||||||
|
load_module_test_with_input(
|
||||||
|
"tests-pl/ffi_f64_nan.pl",
|
||||||
|
format!("LIB={dynlib_path:?}."),
|
||||||
|
" error(evaluation_error(undefined),round/1).\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(miri, ignore = "ffi")]
|
||||||
|
fn ffi_f64_minus_zero() {
|
||||||
|
let dynlib_path = build_dynamic_library(
|
||||||
|
"ffi_f64_minus_zero",
|
||||||
|
r##"
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_f64_minus_zero() -> f64 {
|
||||||
|
-0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn signum(f: f64) -> f64 {
|
||||||
|
f.signum()
|
||||||
|
}
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
|
||||||
|
// note: ouput is currently wrong correct would be 1.0,1.0
|
||||||
|
load_module_test_with_input(
|
||||||
|
"tests-pl/ffi_f64_minus_zero.pl",
|
||||||
|
format!("LIB={dynlib_path:?}."),
|
||||||
|
"-1.0,1.0",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(miri, ignore = "ffi")]
|
||||||
|
fn ffi_return_values() {
|
||||||
|
let dynlib_path = build_dynamic_library(
|
||||||
|
"ffi_return_values",
|
||||||
|
r##"
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_false() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_i8() -> i8 {
|
||||||
|
-42
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_u8() -> u8 {
|
||||||
|
73
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_i16() -> i16 {
|
||||||
|
-0xBEE
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_u16() -> u16 {
|
||||||
|
0xC0DE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_i32() -> i32 {
|
||||||
|
-0xBEEFBEE
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_u32() -> u32 {
|
||||||
|
0xC0DEB000
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_i64() -> i64 {
|
||||||
|
-0xBEEFBEE5C0DEB00
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_u64() -> u64 {
|
||||||
|
0xFEDCBA9876543210
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_f32() -> f32 {
|
||||||
|
std::f32::consts::PI
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_return_values_f64() -> f64 {
|
||||||
|
std::f64::consts::TAU
|
||||||
|
}
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
|
||||||
|
let expected = format!(
|
||||||
|
"i8- {},u8-{},i16- {},u16-{},i32- {},u32-{},i64- {},u64-{},f32-{},f64-{}",
|
||||||
|
-42,
|
||||||
|
73,
|
||||||
|
-0xBEE,
|
||||||
|
0xC0DE,
|
||||||
|
-0xBEEFBEE,
|
||||||
|
0xC0DEB000u32,
|
||||||
|
-0xBEEFBEE5C0DEB00i64,
|
||||||
|
0xFEDCBA9876543210u64,
|
||||||
|
std::f32::consts::PI as f64,
|
||||||
|
std::f64::consts::TAU
|
||||||
|
);
|
||||||
|
|
||||||
|
load_module_test_with_input(
|
||||||
|
"tests-pl/ffi_return_values.pl",
|
||||||
|
format!("LIB={dynlib_path:?}."),
|
||||||
|
expected.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(miri, ignore = "ffi")]
|
||||||
|
fn ffi_invalid_type() {
|
||||||
|
let dynlib_path = build_dynamic_library(
|
||||||
|
"ffi_invalid_type",
|
||||||
|
r##"
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_invalid_type() -> () {
|
||||||
|
}
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
|
||||||
|
load_module_test_with_input(
|
||||||
|
"tests-pl/ffi_invalid_type.pl",
|
||||||
|
format!("LIB={dynlib_path:?}."),
|
||||||
|
"% Warning: initialization/1 failed for: user:test\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(miri, ignore = "ffi")]
|
||||||
|
fn ffi_struct() {
|
||||||
|
let dynlib_path = build_dynamic_library(
|
||||||
|
"ffi_struct",
|
||||||
|
r##"
|
||||||
|
#[repr(C)]
|
||||||
|
struct PaddingGalore {
|
||||||
|
a: u8,
|
||||||
|
b: u16,
|
||||||
|
c: u32,
|
||||||
|
d: u64,
|
||||||
|
a2: u8,
|
||||||
|
e: f32,
|
||||||
|
f: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn construct(a: u8, b: u16, c: u32, d: u64, a2: u8, e: f32, f: f64) -> PaddingGalore {
|
||||||
|
PaddingGalore {
|
||||||
|
a,
|
||||||
|
a2,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
e,
|
||||||
|
f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn modify(data: PaddingGalore) -> PaddingGalore {
|
||||||
|
PaddingGalore {
|
||||||
|
a: data.a2,
|
||||||
|
a2: data.a,
|
||||||
|
b: !data.b,
|
||||||
|
c: !data.c,
|
||||||
|
d: !data.d,
|
||||||
|
e: -data.e,
|
||||||
|
f: -data.f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
|
||||||
|
load_module_test_with_input(
|
||||||
|
"tests-pl/ffi_struct.pl",
|
||||||
|
format!("LIB={dynlib_path:?}."),
|
||||||
|
"[P,G]-[pg,8,12,46,40,127,1.3680000305175781,-4.587]\n[P,G,2]-[pg,127,65523,4294967249,18446744073709551575,8,-1.3680000305175781,4.587]\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(miri, ignore = "ffi")]
|
||||||
|
fn ffi_cstr() {
|
||||||
|
let dynlib_path = build_dynamic_library(
|
||||||
|
"ffi_cstr",
|
||||||
|
r##"
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_cstr_len(c_str: Option<std::ptr::NonNull<core::ffi::c_char>>) -> u64 {
|
||||||
|
if let Some(c_str) = c_str {
|
||||||
|
unsafe { CStr::from_ptr(c_str.as_ptr()) }.count_bytes() as u64
|
||||||
|
} else {
|
||||||
|
u64::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_example_cstr() -> *const core::ffi::c_char {
|
||||||
|
c"Rust Lang".as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ffi_null_cstr() -> *const core::ffi::c_char {
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
|
||||||
|
load_module_test_with_input(
|
||||||
|
"tests-pl/ffi_cstr.pl",
|
||||||
|
format!("LIB={dynlib_path:?}."),
|
||||||
|
format!(r#"13-[R,u,s,t, ,L,a,n,g]-0-{}"#, u64::MAX).as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
use scryer_prolog::MachineBuilder;
|
use scryer_prolog::MachineBuilder;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use scryer_prolog::{InputStreamConfig, StreamConfig};
|
||||||
|
|
||||||
pub(crate) trait Expectable {
|
pub(crate) trait Expectable {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_eq(self, other: &[u8]);
|
fn assert_eq(self, other: &[u8]);
|
||||||
@@ -47,3 +51,16 @@ pub(crate) fn load_module_test_with_tokio_runtime<T: Expectable>(file: &str, exp
|
|||||||
expected.assert_eq(wam.test_load_file(file).as_slice())
|
expected.assert_eq(wam.test_load_file(file).as_slice())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_module_test_with_input<T: Expectable>(
|
||||||
|
file: &str,
|
||||||
|
input: impl Into<Cow<'static, str>>,
|
||||||
|
expected: T,
|
||||||
|
) {
|
||||||
|
use scryer_prolog::MachineBuilder;
|
||||||
|
|
||||||
|
let mut wam = MachineBuilder::default()
|
||||||
|
.with_streams(StreamConfig::in_memory().with_user_input(InputStreamConfig::string(input)))
|
||||||
|
.build();
|
||||||
|
expected.assert_eq(wam.test_load_file(file).as_slice());
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ fn issue2725_dcg_without_module() {
|
|||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
||||||
|
#[cfg_attr(not(miri), ignore = "flaky due to network requests")]
|
||||||
fn http_open_hanging() {
|
fn http_open_hanging() {
|
||||||
load_module_test_with_tokio_runtime(
|
load_module_test_with_tokio_runtime(
|
||||||
"tests-pl/issue-http_open-hanging.pl",
|
"tests-pl/issue-http_open-hanging.pl",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ mod helper;
|
|||||||
mod issues;
|
mod issues;
|
||||||
mod src_tests;
|
mod src_tests;
|
||||||
|
|
||||||
|
mod ffi;
|
||||||
|
|
||||||
/// To add new cli test copy an existing .toml file in `tests/scryer/cli/issues/` or `tests/scryer/cli/issues/src_tests/`,
|
/// To add new cli test copy an existing .toml file in `tests/scryer/cli/issues/` or `tests/scryer/cli/issues/src_tests/`,
|
||||||
/// adjust as necessary the `-f` and `--no-add-history` args should be kept but additional args may be added.
|
/// adjust as necessary the `-f` and `--no-add-history` args should be kept but additional args may be added.
|
||||||
/// For input on stdin add a .stdin file with the same filename.
|
/// For input on stdin add a .stdin file with the same filename.
|
||||||
|
|||||||
Reference in New Issue
Block a user