WebAssembly
WebAssembly🔗
Nybl runs on wasm32 in production embeddings (browser clients and edge runtimes). This page collects the supported configuration in one place: which crates build for wasm, which features to pick, and the handful of host-side caveats.
What builds for wasm🔗
nybl-lang (the tree-walker) and nybl-vm (the bytecode VM) both build clean for wasm32-unknown-unknown, in both feature configurations:
- Default features (
std+nybl-std) — works because Rust’sstdcompiles for wasm32. Simplest option when you are using a bundler-style toolchain (wasm-bindgen, wasm-pack) that expectsstd. no_std— disable default features and opt in explicitly. This is the configuration for bare-metal-style wasm modules and for embeddings that want a deterministic pure-Rust math backend (see below):
[dependencies]
nybl-lang = { version = "0.4", default-features = false, features = ["no_std", "nybl-std"] }
nybl-vm = { version = "0.4", default-features = false, features = ["no_std", "nybl-std"] }Keep nybl-std if you want the bundled Nybl stdlib (use std.math, std.json, …) to resolve. If Cargo ever unifies std and no_std in one build graph, std wins — a genuine no_std build must disable default features.
nybl-compile’s generated sandbox runtime follows the same feature split. nybl-cli is a native application. nybl-sys compiles for wasm, but remains an OS-oriented host rather than the recommended browser/freestanding host; its unsupported clock behavior is described below.
Allocator🔗
On wasm32-unknown-unknown without std’s default machinery you provide the global allocator yourself. The pattern used by the shipped embeddings is lol_alloc, a tiny single-threaded wasm allocator:
#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOCATOR: lol_alloc::AssumeSingleThreaded<lol_alloc::FreeListAllocator> =
unsafe { lol_alloc::AssumeSingleThreaded::new(lol_alloc::FreeListAllocator::new()) };Walker + VM + libm + lol_alloc ships at roughly 355 KB stripped. With default features (std), Rust’s ordinary wasm allocator is used and no setup is needed.
Math backend and float determinism🔗
Nybl’s f64 builtins (sqrt, sin, cos, tan, exp, log, pow, …) go through a single math facade:
- With
std(default), they call the platform’s nativef64methods. - With
no_std, they call the pure-Rustlibmcrate.
Arithmetic (+ - * / %), sqrt, and the rounding builtins are exact IEEE 754 operations and bit-identical everywhere. The transcendentals (sin, cos, tan, exp, log, pow) are not guaranteed bit-identical across platform math libraries — e.g. macOS’s system libm returns 1.tan() one ULP away from wasi-libc’s result. If your embedding needs bit-identical script output across native and wasm builds (lockstep simulation, replay verification), build both sides with no_std so every platform uses the same pure-Rust libm code.
CI executes the same curated parity corpus twice: once with the default configuration and once with both engines linked using default-features = false, features = ["no_std", "nybl-std"]. The second comparison is the lockstep configuration described above — see CI enforcement below.
Timing: Instant, SystemTime, and web-time🔗
std::time::Instant::now() and SystemTime::now() compile on wasm32-unknown-unknown but panic at runtime — the target has no clock. Two places this bites:
Host timeout patterns. The
Instant-based timeout host from Embedding Nybl needs a wasm-aware clock. The standard fix is theweb-timecrate — a drop-inInstant/SystemTimereplacement that usesperformance.now()on wasm32 with browser bindings and re-exportsstd::timeeverywhere else. Swap the import and the rest of the host code is unchanged:use web_time::Instant; // instead of std::time::InstantOn WASI targets (
wasm32-wasip1),std::timeworks natively and no replacement is needed.nybl-sys.StdHostusesSystemTime::now()on native and WASI targets. Onwasm32-unknown-unknown,unix_time()andunix_time_ms()instead return a Nybl runtime error explaining that the system clock is unavailable and recommending a customNyblHost; they do not panic or trap. Browser and other freestanding wasm embeddings should provide time through a host function backed byweb-time, JavaScript, or a caller-supplied deterministic tick.nybl-vmdoes not depend onnybl-sys, so this does not constrain the VM.
CI enforcement🔗
Two CI jobs keep the wasm surface from regressing:
- Compile:
cargo checkruns fornybl-langandnybl-vmonwasm32-unknown-unknownin both the default andno_stdconfigurations.nybl-sysis also compile-checked there with its default features. - Execution: a small
wasm32-unknown-unknownmodule is run under wasmtime to provenybl-sysreports its unsupported clock as a normal Nybl error. Thewasm_parityrunner (tests/wasm-parity) then runs a curated corpus of Nybl programs — float arithmetic and formatting, transcendental builtins, rounding at the i64 boundary, negative division/modulo, string interpolation, dict/array ordering, the deterministicrandsequence, error messages, and composite programs — through both engines, natively and under wasmtime onwasm32-wasip1, and byte-compares the transcripts in both feature configurations: defaultstd(platform math) and defaults-offno_std(pure-Rustlibm), withnybl-stdenabled in both so the corpus can use bundled modules. Any native/wasm difference fails CI. An opt-in negative control perturbs one transcendental Nybl input on wasm: CI also verifies that the same comparison rejects the resulting math-output divergence. Parity executes onwasm32-wasip1because the std runner needs stdout; the engine libraries themselves are separately compile-checked forwasm32-unknown-unknown.