WebAssembly
Velt programs compile to WebAssembly for two hosts:
| Target | Runs in | Output |
|---|---|---|
wasm32-wasip1 (alias wasm32-wasi) | wasmtime, wasmer, Node's WASI, … | a WASI command module target/velt/<stem>.wasm |
wasm32-unknown-unknown | the browser (and Node) | target/velt/<stem>.wasm plus the JavaScript glue velt_web.mjs |
velt run --target wasm32-wasip1 hello.vlt # builds, then `wasmtime run --dir=. …`
velt build --target wasm32-unknown-unknown hello.vlt # hello.wasm + velt_web.mjs
velt run --target wasm32-unknown-unknown hello.vlt # runs it with node and the glueSetup
Release toolchains don't include the WebAssembly runtime yet: WebAssembly builds need a
checkout of the Velt repository and the Rust toolchain, and
the commands below run in that checkout. WebAssembly builds go through LLVM's opt and llc (Apple's clang has no WebAssembly backend,
so clang is not used) and a WebAssembly linker. With rustup, everything comes from the Rust
toolchain:
rustup component add llvm-tools # opt, llc
rustup target add wasm32-wasip1 wasm32-unknown-unknown # wasi-libc; rust-lld links wasm
cargo build -p velt_rt_wasm --target wasm32-wasip1 # the runtime, per target
cargo build -p velt_rt_wasm --target wasm32-unknown-unknown
brew install wasmtime # or https://wasmtime.devThe linker is the Rust toolchain's rust-lld, which matches the wasi-libc rustup installs (an
older LLVM wasm-ld can fail on it with undefined symbols such as __wasm_first_page_end);
without Rust, wasm-ld on PATH. velt doctor shows which one is used.
Overrides: VELT_LLVM_BIN (a directory with opt and llc), VELT_LINKER (wasm-ld),
VELT_WASI_SYSROOT (a directory with wasi-libc's crt1-command.o and libc.a), VELT_RT_LIB
(the runtime library), VELT_WASM_RUNNER (runs WASI modules for velt run).
What works
The language and the standard library as on native targets, single-threaded:
async/await,spawn,Promise.all,sleepandyieldNowrun on a current-thread executor (tasks interleave atawaitpoints;sharedandMutexwork, with one thread);velt/fsuses the WASI file system (velt rungrants the current directory);velt/processarguments and environment,performance.now()andDate.now()work on both targets;Math.random()andvelt/randomwork (seeded from WASI's random source, or the clocks in the browser); local time is UTC (Date's local getters,velt/time'slocalParts()), since there is no time zone;- panics print
panic: …and exit with 101, like native programs.
Differences from native targets and Node:
- Tasks that a synchronous program spawns (a
mainthat isn'tasync, or top-level statements withoutawait) don't run: the executor runs only under anasyncprogram. - A
resolvecalled later, or a shared promise settling, resumes the code waiting for it at the executor's next step instead of as a microtask, so the order of output can differ from Node's. - A promise passed where a
Promise<unknown>is expected and kept that way (in aSet<Promise<unknown>>, say) reports its rejection as its own when nothing awaits it there, even if the program handled the rejection through the original promise. Await orcatchthe kept promise too.
Not available: TCP, the http-server package, child
processes and the database packages; the link fails with a note naming the missing runtime
function. Packages with native code can't
be used (package `sqlite` has native code, which cannot be used for WebAssembly (wasm32-wasip1)).
fetch links but rejects with IoError ENOTSUP (there is no network), so code that only
builds Responses runs. Browser modules have no file system.
System functions don't exist on WebAssembly: a program may declare one (so portable code can choose by platform), but a call of it is a compile error:
error: system function `lseek` is not available on wasm32-wasip1
= note: `main` calls it
= note: this target has no system functionsIn the browser
import { runVelt } from "./velt_web.mjs";
const code = await runVelt(fetch("hello.wasm"), {
write: (stream, bytes) => console.log(new TextDecoder().decode(bytes)),
});runVelt instantiates the module, runs main and resolves to the exit code; output arrives as
bytes per stream (1 = stdout, 2 = stderr). Run long programs in a Web Worker: sleep
busy-waits, because a browser's main thread cannot block.
The playground
velt playground # http://127.0.0.1:8090/ (--port, --host)An editor with examples, a Run button and an output pane. The compiler is native (it uses LLVM
and a linker), so the playground compiles on the server (the velt playground process) and the
page runs the resulting module in a Web Worker in your browser. Only standard library
(velt/…) imports are accepted. Anyone who can reach the port can compile programs (which run in their own browser,
not on the server); keep the default 127.0.0.1 unless you mean to share it. The playground
needs the browser runtime: cargo build -p velt_rt_wasm --target wasm32-unknown-unknown.