rontolisp:wasm-export
(rontolisp:wasm-export 'name :as "alias" :params '(type...) :param-names '(name...) :returns type :async t)
Marks a top-level defun as host-callable when compiling to a WebAssembly core
module, declaring the WASM-boundary types of its parameters and result. It is a
compile-time directive, not an ordinary function: on the interpreter and
JVM backends it is a no-op that simply returns the named symbol, so the same
source runs on every backend. See
Compiling to WebAssembly for the full guide. Its JVM
twin is rontolisp:jvm-export — the same
declaration, emitting a typed Java-callable method on a compiled class.
Arguments
- A quoted symbol naming the top-level
defunto export. It resolves in the current package like adefunname. :as— the WASM export name, as a string (e.g."factorial", or a camelCase name for a JavaScript-facing API). Defaults to the bare Lisp name (fact, without any package qualifier).:params— a list of boundary type designators, one per parameter. Omitted,nilor'()means no arguments.:param-names— the parameter names of the component-model signature, one per:paramsentry, as symbols or strings. Each must be a component-model label (lower-kebab-case words). Defaults top0,p1, ... — the names a host or a binding generator sees in the component's type, and therefore the names--emit-witprints. It is ignored outside--component(a core WASM parameter has no name), and a program that implements a WIT world withrontolisp:wit-exportgets these from the world instead of declaring them.:returns— the result boundary type designator. Omitted,nil,'()or:voiddeclares a void result (the Lisp return value is discarded).:async—tlifts the export as an async component-model function under--component, so I/O inside it (print,rontolisp:fetch, ...) works instead of trapping. Defaults tonil(a synchronous, pure-compute lift). Meaningful only under--component: Preview 1 /--no-wasicore exports ignore it, and--no-gc --componentrejects it.
The type designators and their boundary representations are:
| Designator | WIT type | WASM boundary | Notes |
|---|---|---|---|
:s8 :s16 :s32 | s8 s16 s32 | i32 | :int is a permanent alias of :s32 |
:u8 :u16 :u32 | u8 u16 u32 | i32 | |
:s64 :u64 | s64 u64 | i64 | :long is a permanent alias of :s64; a :u64 value of 2^63 or more traps (it has no exact representation in the signed 64-bit integers every backend computes with) |
:float | f64 | f64 | rontolisp has no single-precision float, so f32 is not a boundary type |
:bool | bool | i32 | 0 is nil, any non-zero value is t |
:string | string | (ptr, len) | UTF-8 bytes in linear memory |
:s-expr | string | (ptr, len) | s-expression text in linear memory (any value except a function); no WIT type of its own |
:bytes | — | (ptr, len) argument / (ptr, cap) -> len result | an (unsigned-byte 8) vector as raw bytes, no UTF-8 in either direction; GC core-module shapes only |
A :bytes result is caller-buffered (the read(2) shape): the export's
core signature gains a trailing (ptr, cap) pair the host passes — reserve
cap bytes with the exported __ronto_alloc — the wrapper copies at most
cap bytes there, and the single i32 result is the vector's full length,
so an undersized buffer is a retry, not a truncation.
The boundary carries the value exactly, or the call traps. A value the
declared type cannot state — a negative returned through :u32, 300 through
:u8, anything past the 32-bit range through :s32 — stops the call instead of
arriving silently wrapped. Nothing is masked, which is also what keeps a
component behaving the same under wasmtime and under stricter binding
generators such as jco.
The representable range is the declared type's own, on every backend. With the
default (GC) backend an incoming integer arrives as an exact integer (a fixnum
when it fits, a boxed 64-bit integer past that), so a :u32 argument of
3000000000 reaches the Lisp code as the exact integer 3000000000; integer
arithmetic inside the Lisp code is exact at any magnitude
((+ x 1) on a :u32 argument of 1073741823 returns 1073741824 exactly),
and only a result the declared type cannot state traps at the boundary. On the
non-GC backend (--no-gc) integers are computed as i64, crossing the same
way.
Limitations
- Under
--component, an export becomes a typed component-model export callable with WAVE syntax (wasmtime run --invoke 'name(args)'): the whole fixed-width integer family (:longincluded),:float/:bool/void,:stringand:s-expras component-modelstring(--no-gchas no:s-expr). A sync (default) export must be pure-compute — I/O inside it traps; declare:async twhen the export prints or fetches. Under--no-gc --component,:asyncis rejected but printing still works, through a built-in WASI 0.3 stdout micro-adapter wired in only when the program prints — every export of a printing program is then liftedasyncautomatically. The export name must be lower-kebab-case (rename with:asotherwise), and adding--emit-witwrites the component's WIT world (with every export's typed signature) next to the.wasm. See Component-model function exports and Compact component output. On the interpreter and JVM the directive just returns the named symbol. - Only a top-level
defuncan be exported; the declared parameter count must match its arity, and functions that take or return function values are out of scope. - Outside
--component, the exported function is pure-compute: reading, time and file access (from the function or from a top-level form) are unsupported. Under--no-wasieach of those has its own defined answer rather than a bare trap — output is discarded,getenvand file lookups answer nothing, the clock reports what a host wrote through__ronto_set_time(and signals until one does),rontolisp:random-bytessignals a catchable error,randomruns on a built-in generator, and only standard input traps; see No-WASI (reactor) mode. One more exception: under--no-gc,print/princ/terpriwork through a singlefd_writeimport that is added only when the program prints (see Printing). - The non-GC backend (
--no-gc) supports:int/:long/:float/:bool/:stringbut not:s-expr, which needs the cons/reader/printer runtime, and not:bytes, which needs arrays. :bytesis a GC core-module (Preview 1 /--no-wasi) boundary type:--componentrejects it (there is nolist<u8>lift yet), so it has no WIT spelling.