rontolisp:wasm-import
(rontolisp:wasm-import 'name :from "module" :as "field" :params '(type...) :returns type [:async t])
Declares a function the WASM host provides (JavaScript in a browser, or another
module preloaded into wasmtime) and makes it callable from Lisp under name
exactly like a top-level defun — including #'name, funcall, mapcar and
eval. It is a compile-time directive, not an ordinary function: on the
interpreter and JVM backends it defines a stub that signals an error
when called (there is no host to call), so the same source still loads on every
backend. See the WASM host boundary guide
for the full guide and the WebGL galaxy example
for a complete browser program.
Arguments
- A quoted symbol naming the Lisp-visible function. It resolves in the
current package like a
defunname, so a directive after(in-package mylib)definesmylib:name. :from— the import module name (the import-object key on the JavaScript side, or the--preloadname in wasmtime). Defaults to"env".:as— the import field name (the property inside that module object). Defaults to the bare Lisp name (without any package qualifier).:params— a list of boundary type designators, one per parameter. Omitted,nilor'()means no arguments.:returns— the result boundary type designator. Omitted,nil,'()or:voiddeclares a void result (Lisp receivesnil).
The type designators are shared with
rontolisp:wasm-export:
| Designator | WASM boundary | Notes |
|---|---|---|
:int | i32 | 31-bit signed range (the internal i31ref) |
:float | f64 | an int or ratio argument is converted like the arithmetic built-ins |
:bool | i32 | nil crosses as 0, anything else as 1; a non-zero result reads back as t |
:string | (ptr, len) | UTF-8 bytes in linear memory |
:s-expr | (ptr, len) | the argument is printed to readable text; a result is parsed by the embedded reader |
:bytes | (ptr, len) argument / (ptr, cap) -> len result | an (unsigned-byte 8) vector as raw bytes — no UTF-8 in either direction |
A :string result must be written into linear memory by the host (reserve the
buffer with the exported __ronto_alloc) and returned as a (ptr, len) pair
(a two-element array from JavaScript).
A :bytes result is caller-buffered (the read(2) shape): the Lisp
signature gains one trailing parameter — the (unsigned-byte 8) vector to
receive into — and the host function is called with a trailing (ptr, cap)
pair: write up to cap bytes at ptr and return the value's full length.
The Lisp call answers that full length, so a result longer than the buffer is
a retry with a bigger buffer, never a silent truncation; the wrapper's staging
is popped on return, so a pull loop over one reused buffer keeps linear memory
flat.
:async t — a host function that may suspend
:async t declares that the host may implement the function
asynchronously — on a JavaScript host, a WebAssembly.Suspending-wrapped
function (JSPI). The call then returns a future that
rontolisp:await resolves, so the source says at the
call site that the boundary is asynchronous — the same reading as an
async func member of a rontolisp:wit-import,
which lowers to exactly this option on this backend. (The word deliberately
matches rontolisp:wasm-export's :async: WIT
spells both directions async func, and the directive carries the direction.)
- On this backend the future is settled at creation: the host call blocks
the wasm stack — synchronously, or suspended through JSPI — so the value is
ready when the call returns and
awaitnever actually suspends. The option buys one source that reads the same everywhere, not concurrency. - The build prints what the host now owes: wrap the import in
WebAssembly.Suspending, enter every export that can reach it throughWebAssembly.promising(the build lists them), and serialise calls — a suspended module can be re-entered, and a re-entered export refuses with a trap instead of silently corrupting both calls (every export wrapper of a module that can suspend carries a re-entry guard, unless it was compiled--reentrant, which lets a JSPI host overlap calls instead). A host that answers synchronously is equally valid; the call returns an already-settled future either way.--emit-js-glueWRITES that half rather than describing it (the host boundary guide): the host is then left with what its functions do, and says which of them suspend. - Under
--no-wasi, a call reachable from a top-level form is a compile error:_initializeruns on a stack nopromisingentered, so a suspension there traps naming nobody. Move the call behind an export, or drop:async tif the host answers synchronously.
Limitations
- Applies to the default (wasm-GC) Preview 1 core module only;
--componentand--no-gcreject the directive with an error. On the interpreter and JVM the declared name signals an error when called. - The directive must appear at top level, before use like a
defun. - Instantiating the compiled module requires the host to provide every declared
import;
wasmtime runneeds a--preload <module>=<file>.wasmfor each import module name, and a JavaScript host passes an import object. - At most 10 parameters (the general WASM-backend arity limit).