[Feature] Support WASI via --platform option (#36)
Summary
Add wasi as a fourth value for --platform, producing a self-contained wasm that runs
directly under a WASI runtime (wasmtime run index.wasm, Node's --experimental-wasi, edge
runtimes, …) with no JavaScript loader. Together with -E/--executable, the emitted module
runs the entry module's main on start, so a PureScript program becomes a standalone wasm
executable.
Today --platform accepts node | browser | standalone (and -E is wired for node/browser
only — it errors on standalone). wasi is the intended home for "make a runnable wasm that
calls main itself, without JS".
Motivation
- Run PureScript-compiled wasm without a JS host — CLI tools, serverless/edge (WASI),
containers (wasmtime/wasmer), and embedding in non-JS hosts.
standalone is a library artifact (exports only, no run mechanism); wasi is the
executable counterpart. This is why standalone + -E is an error and wasi + -E is the
way to get a self-running command.
- The
-E machinery (main :: Effect Unit, the run-on-load semantics) and the
main(cliRoot)(binaryenBinDir) build wiring already exist; this extends them to a wasm-native
entry point.
Design sketch
--platform=wasi — a single self-contained wasm targeting the WASI ABI
(wasi_snapshot_preview1 to start). Like standalone, every foreign import must be provided
in wasm (no JS loader to fall back to) — see the foreign/IO question below.
-E under wasi — instead of a JS loader calling main, emit a _start export (the
WASI command convention wasmtime invokes) whose body performs main and proc_exit(0).
main stays restricted to main :: Effect Unit for now (same validation as node/browser -E).
wasi without -E — a reactor-style module that exports its values but does not auto-run
(or: wasi implies -E; to be decided).
Open questions
- Foreign / IO story (the crux). With no JS loader, host effects have no provider. Two
options, not mutually exclusive:
- Strict: require every foreign to be wasm-provided (like
standalone). Pure effects
(Effect.Ref, arithmetic) run; console.log does not (no stdout) unless wasm-provided.
- WASI provider layer: map common effects to WASI syscalls — most importantly
Effect.Console.log → fd_write on stdout (fd 1) — so a real "hello world" prints under
wasmtime. Likely needed for wasi to be useful.
- WASI version — preview1 (broadly supported: wasmtime, Node, browser polyfills) first;
preview2 / the component model later?
_start export vs the wasm start section — _start matches the WASI command
convention runtimes invoke; the start section runs at instantiation (reactor-ish). Pick per
the executable vs reactor distinction.
- CLI args / env / exit code —
main :: Array String -> Effect Unit via args_get /
args_sizes_get, environ_*, and a non-zero proc_exit on failure are natural follow-ups
(depends on closing closure-direction-2 / argv marshalling).
Tasks
Related
-E/--executable (node/browser) and the --platform design — ADR 0025, ADR 0011.
- Deferred from v0.1 (the build-options work landed node/browser/standalone +
-E).
[Feature] Support WASI via --platform option (#36)
Summary
Add
wasias a fourth value for--platform, producing a self-contained wasm that runsdirectly under a WASI runtime (
wasmtime run index.wasm, Node's--experimental-wasi, edgeruntimes, …) with no JavaScript loader. Together with
-E/--executable, the emitted moduleruns the entry module's
mainon start, so a PureScript program becomes a standalone wasmexecutable.
Today
--platformacceptsnode | browser | standalone(and-Eis wired for node/browseronly — it errors on
standalone).wasiis the intended home for "make a runnable wasm thatcalls
mainitself, without JS".Motivation
containers (
wasmtime/wasmer), and embedding in non-JS hosts.standaloneis a library artifact (exports only, no run mechanism);wasiis theexecutable counterpart. This is why
standalone + -Eis an error andwasi + -Eis theway to get a self-running command.
-Emachinery (main :: Effect Unit, the run-on-load semantics) and themain(cliRoot)(binaryenBinDir)build wiring already exist; this extends them to a wasm-nativeentry point.
Design sketch
--platform=wasi— a single self-contained wasm targeting the WASI ABI(
wasi_snapshot_preview1to start). Likestandalone, everyforeign importmust be providedin wasm (no JS loader to fall back to) — see the foreign/IO question below.
-Eunder wasi — instead of a JS loader callingmain, emit a_startexport (theWASI command convention
wasmtimeinvokes) whose body performsmainandproc_exit(0).mainstays restricted tomain :: Effect Unitfor now (same validation as node/browser-E).wasiwithout-E— a reactor-style module that exports its values but does not auto-run(or:
wasiimplies-E; to be decided).Open questions
options, not mutually exclusive:
standalone). Pure effects(
Effect.Ref, arithmetic) run;console.logdoes not (no stdout) unless wasm-provided.Effect.Console.log→fd_writeon stdout (fd 1) — so a real "hello world" prints underwasmtime. Likely needed forwasito be useful.preview2 / the component model later?
_startexport vs the wasmstartsection —_startmatches the WASI commandconvention runtimes invoke; the
startsection runs at instantiation (reactor-ish). Pick perthe executable vs reactor distinction.
main :: Array String -> Effect Unitviaargs_get/args_sizes_get,environ_*, and a non-zeroproc_exiton failure are natural follow-ups(depends on closing closure-direction-2 / argv marshalling).
Tasks
Wasito thePlatformADT +--platformparser (PursWasm.CLI.Options*)._startperformingmain) for--platform=wasi -E;keep the
main :: Effect Unitvalidation.fd_writefor stdout soconsole.logworks).--platform=wasi -E, run it underwasmtime, assert stdout.--platformtable in Overview, and the wasi notes in Performance and Limitations.Related
-E/--executable(node/browser) and the--platformdesign — ADR 0025, ADR 0011.-E).