A personal playground of hand-authored WAT (WebAssembly Text Format) utilities and demos. A quicksort example is here, alongside small experiments like real-time clock reporting. Everything stays in text-format WebAssembly so artifacts are easily inspectable and runnable with the provided helpers.
wat/– Legacy build caches (wat/build/) kept for compatibility.scripts/wasmbuild– Compiles.watto.wasm, with caching and AOT helpers.scripts/– Helper utilities (wasmbuild,wasmrun) and directly-runnable "wat executables" likequicksortandns.tests/–run.shorchestrates all automated checks;hello_nerds.watis the fixture module for helper tests.data/– Sample input (sample_unsorted.txt) and benchmark payloads.demos/– WASI/WAT demos; e.g.nsprints realtime clock,progressbarrenders a single-shot bar, andfactoris an emscripten-converted utility.bench,build_all,test– Top-level convenience wrappers around the scripts inscripts/.flake.nix/.envrc– Development shell provisioning via Nix and direnv.
- Nix 2.18 or newer.
- Optional but highly recommended:
direnv. After cloning, rundirenv allowonce so entering the directory loads the dev shell. - Without direnv, prefix commands with
nix develop -c(e.g.nix develop -c ./test); also, sinceWASM_BUILD_CACHEis not set, you will get "wasm detritus" next to each wat file; you will also be missing the convenience of the project root and scripts/ contents being on PATH (but only for this project directory!)
The dev shell provides all tooling: wasmtime, wasmedge, wasmer, wazero,
deno, hyperfine, wasm-tools, wabt, jq, and python3 (used for the
benchmark report). The flake pins a prebuilt Wasmer release because the
nixpkgs-provided build currently fails to link on x86_64-linux; see the comment
inside flake.nix for details.
- Compile manually:
wasmbuild scripts/quicksort(without direnv,scripts/wasmbuild scripts/quicksort) - Run with default runner (
wazero):quicksort < data/sample_unsorted.txt(without direnv,scripts/quicksort < data/sample_unsorted.txt) - Choose a runner:
scripts/wasmrun wasmtime scripts/quicksort - Cache control: set
WASM_BUILD_CACHE=/tmp/wasm-cacheor pass--cache build/-o path/to/out.wasm. - AOT runners (
wasmer-aot,wasmedge-aot) emit artifacts alongside the.wasmfile (extensions.wasmer/.wasmedge) and reuse them when timestamps match. - Demo modules under
demos/can be executed directly (./demos/ns) thanks to the wasmrun shebang, and they share the same caching/timestamp logic.
The wasmbuild/wasmrun helpers compare timestamps and skip rebuilds when the source and cached artifact share the same mtime.
If wasm-tools is unavailable, they fall back to wat2wasm, emitting a warning
because that route requires temporary files (I dislike touching the disk unless absolutely necessary).
./test # (when on nix and direnv)
# or
nix develop -c ./test
tests/run.sh performs three groups of checks:
- Behavioral parity – For
LC_ALL=CandLC_ALL=en_US.UTF-8, quicksort's output must match the nativesortcommand ondata/sample_unsorted.txt. - Demo smoke tests – Drives the WASI snippets (
demos/ns,demos/progressbar, anddemos/factor) to ensure CLI expectations, locale/env plumbing, and error handling stay intact. - Helper coverage – Exercises
wasmbuild/wasmruncache paths,-oand--cacheoverrides, and the AOT flows for Wasmer and WasmEdge using thetests/hello_nerds.watfixture.
./bench # defaults to RUNS=20, LC_ALL inherited from your env
RUNS=50 ./bench # override the repetition count
The benchmark script:
- Builds
scripts/quicksortonce and then runs each runtime viascripts/wasmrun. - Verifies each runner’s output matches native
sortfor the selected locale. - Feeds the same in-memory dataset (
data/benchmark/strings.txt) to every command, including GNUsortas a baseline. - Invokes
hyperfinewith-Nsemantics using per-runner shim scripts so shell startup costs are consistent. - Stores raw numbers in
build/bench-latest.jsonand prints a simple ASCII bar chart of mean runtimes (requiresjq). The summary excludessortwhen ranking the fastest WASM runner.
Tip: adjust LC_ALL before running to compare collation behaviors, e.g.
LC_ALL=en_US.UTF-8 ./bench.
./build_all # caches outputs under ./build (or WASM_BUILD_CACHE)
WASM_BUILD_CACHE=out ./build_all
This is handy before benchmarking or packaging demos; it ensures cached artifacts are ready without incurring build work on first run.
Each demo is executable thanks to the wasmrun shebang:
chmod +x demos/ns # only needed once if the repo was cloned without exec bits
./demos/ns
# other examples
printf '50\n' | ./demos/progressbar
printf '42\n' | ./demos/factor
ns shows how to call WASI’s clock_time_get for nanosecond
resolution and can be driven (as in a test context) by piping an 8-byte little-endian override on stdin.
progressbar renders a single-shot textual bar using COLUMNS. Set
COLUMNS_OVERRIDE to force a specific width (even when COLUMNS mirrors the
interactive terminal); use scripts/progressbar_ensure_terminates if you want a 5s guard
around the bar demo.
factor is the classic BSD utility converted back to WAT: feed newline-delimited
integers on stdin and it prints n: p1 p2 … prime factorizations. Invalid
lines trigger factor: invalid input: … warnings on stderr while the module
keeps processing the rest of the stream.
New demo ideas worth exploring:
- Ratatui-based terminal UI compiled to WASI (interactive dashboards in wasm)- raw keyboard input still missing in the WASM standard so you have to get a little hacky with shell and stdin.
- Stopwatch/latency probes comparing realtime vs monotonic clocks.
- Embedding the WasmEdge QuickJS shell for a wasm-hosted JavaScript REPL.
Place future experiments under demos/ and they’ll automatically benefit from
./build_all and scripts/wasmrun.
- Command not found: Ensure you are inside the Nix dev shell (
direnv allowornix develop). - Locales missing: The tests and benchmarks rely on
LC_ALL=CandLC_ALL=en_US.UTF-8. Install the locale data on your host if those names fail. - wat2wasm/wat conversion warning: Install
wasm-toolsto enable streaming compilation and avoid temporary files. - Benchmark skips a runner: Check that runtime’s CLI is on
PATH. AOT modes also needwasmedgec(for WasmEdge) or the Wasmer CLI to be present.
Happy wasm hacking!