Next: Evaluation, Previous: Pattern matching, Up: Scheme reference [Contents][Index]
WebAssembly follows the capability security model, which means that
modules cannot do much on their own. Wasm modules are guests within a
host. They must be given capabilities by the host in order to
interact with the outside world. Modules request capabilities by
declaring imports, which the host then fills out with concrete
implementations at instantiation time. Hoot provides a foreign
function interface (FFI) in the (hoot ffi)
module to embed
these import declarations within Scheme code.
The define-foreign
form declares an import with a given type
signature (Wasm is statically typed) and defines a procedure for
calling it. The FFI takes care of converting Scheme values to Wasm
values and vice versa. For example, declaring an import for creating
text nodes in a web browser could look like this:
(define-foreign make-text-node "document" "createTextNode" (ref string) -> (ref extern))
In the above example, the procedure is bound to the variable
make-text-node
. In the Wasm binary, this import is named
“createTextNode” and resides in the “document” namespace of the
import table. A Wasm host is expected to satisfy this import by
providing a function that accepts one argument, a string, and returns
an arbitary host value which may be null.
Note that declaring an import does not do anything to bind that import to an implementation on the host. The Wasm guest cannot grant capabilities unto itself. Furthermore, the host could be any Wasm runtime, so the actual implementation will vary. In the context of a web browser, the JavaScript code that instantiates a module with this import could look like this:
Scheme.load_main("hello.wasm", {}, { document: { createTextNode: (text) => document.createTextNode(text) } });
And here’s what it might look like when using the Hoot interpreter:
(use-modules (hoot reflect)) (hoot-instantiate (call-with-input-file "hello.wasm" parse-wasm) `(("document" . (("createTextNode" . ,(lambda (str) `(text ,str)))))))
Once defined, make-text-node
can be called like any other
procedure:
(define hello (make-text-node "Hello, world!"))
Since the return type of make-text-node
is (ref extern
),
the value of hello
is an external reference. To check
if a value is an external reference, use the external?
predicate:
(external? hello) ; => #t
External references may be null, which could indicate failure, a cache
miss, etc. To check if an external value is null, use the
external-null?
predicate:
(external-null? hello) ; => #f
Note that we defined the return type of make-text-node
to be
(ref extern)
, not (ref null extern)
, so hello
would never be null in this example.
A large application will likely need to manipulate many different
kinds of foreign values. This introduces an opportunity for errors
because external?
cannot distinguish between them. The
solution is to wrap external values using disjoint types. To define
such wrapper types, use define-external-type
:
(define-external-type <text-node> text-node? wrap-text-node unwrap-text-node)
make-text-node
could then be reimplemented like this:
(define-foreign %make-text-node "document" "createTextNode" (ref string) -> (ref extern)) (define (make-text-node str) (wrap-text-node (%make-text-node str))) (define hello (make-text-node "Hello, world!")) (external? hello) ; => #f (text-node? hello) ; => #t (external? (unwrap-text-node hello)) ; => #t
We’ve now explained the basics of using the FFI. Read below for detailed API documentation.
Define scheme-name, a procedure wrapping the Wasm import import-name in the namespace namespace.
The signature of the function is specified by param-types and result-type, which are all Wasm types expressed in WAT form.
Valid parameter types are:
Valid result types are:
Return #t
if obj is an external reference.
Return #t
if extern is null.
Return #t
if extern is not null.
Define a new record type named name for the purposes of wrapping
external values. predicate is the name of the record type
predicate. wrap is the name of the record type constructor.
unwrap is the name of the record field accessor that returns the
wrapped value. Optionally, print is a procedure that accepts
two arguments (obj
and port
) and prints a textual
representation of the wrapped value.
Next: Evaluation, Previous: Pattern matching, Up: Scheme reference [Contents][Index]