Up: Scheme reference   [Contents][Index]

3.1 Foreign function interface

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) 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 null 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: Document.prototype.createTextNode.bind(document)

And here’s what it might look like when using the Hoot interpreter:

(use-modules (hoot reflect) (wasm parse))
(define reflector (call-with-input-file "js-runtime/reflect.wasm" parse-wasm))
(hoot-instantiate reflector
                  (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 text-node (make-text-node "Hello, world!"))

Since the return type of make-text-node is (ref null extern), the value of text-node is an external reference. To check if a value is an external reference, use the external? predicate:

(external? text-node) ; => #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:

(if (external-null? text-node) 'yay 'uh-oh)
Syntax: define-foreign scheme-name namespace import-name param-types ... -> result-type

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:

  • i32: 32-bit integer
  • i64: 64-bit integer
  • f32: 32-bit float
  • f64: 64-bit float
  • (ref string): a string
  • (ref null extern): an external reference
  • (ref eq): any Scheme value

Valid result types are:

  • none: no return value
  • i32: 32-bit integer
  • i64: 64-bit integer
  • f32: 32-bit float
  • f64: 64-bit float
  • (ref string): a string
  • (ref null extern): an external reference
Procedure: external? obj

Return #t if obj is an external reference.

Procedure: external-null? extern

Return #t if extern is null.

Up: Scheme reference   [Contents][Index]