]> Repositorios git - scryer-prolog.git/commitdiff
FFI: Documentation
authorAdrián Arroyo Calle <[email protected]>
Mon, 27 Feb 2023 19:02:21 +0000 (20:02 +0100)
committerAdrián Arroyo Calle <[email protected]>
Tue, 28 Feb 2023 21:09:35 +0000 (22:09 +0100)
src/ffi.rs
src/lib/ffi.pl

index 2910a20d72670ee72e8cdbc6038ec8d58193ec58..afc52339451c7c4073a2da03e05c5d8220eaa7b5 100644 (file)
@@ -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};
index c34cb4ad13fcd93a5aa55977cefffceb5a4b551c..cce60ff614782c1313043d630da6eff14fb46e0b 100644 (file)
@@ -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