Build1 publisher3 min readPublished
Wago's single-pass compiler aims to cut wasm compile memory to kilobytes, author says
Wago's author says wazero peaked at hundreds of megabytes compiling large modules at two employers, so he wrote a single-pass compiler called Railshot. The post sets targets for it and publishes no benchmark.
The Engineer · Build desk
What happened
- A developer released a new WebAssembly runtime called Wago and announced it on dev.to, opening by conceding that Wasmtime, Wasmer, V8 and wazero already cover the ground.
- He says wazero runs much slower than Wasmtime, and attributes the gap to Cranelift, the optimizing compiler Wasmtime uses to emit machine code.
- His two previous employers, Hypermode and Impart Security, both use wazero to run isolated, secure wasm applications.
- The post sets five targets for Wago's compiler: kilobytes of memory to compile, less machine code emitted, compilation several times faster, output substantially faster than wazero, and still single-pass.
- The compiler, Railshot, skips building a large IR and keeps small temporary views called Valent Blocks, which it uses for register pinning and bounds-check elimination while compiling.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Railshot's code quality leans on whatever optimizer produced the binary, so a module shipped without that pass gives it nothing to inherit.
- decision Anyone weighing a swap out of wazero is deciding on stated targets, which puts the benchmarking work on the adopter's own module set.
- contradiction The post says "This isn't really wazero's fault though" and then calls the same middle-ground design laziness, so a wazero user has no defect to file, only a default to disagree with.
- capability Compiling in kilobytes is the precondition for the embedded targets the author lists as an eventual goal, which no wazero-class runtime footprint reaches today.
A single-pass compiler walks the input once and emits machine code. Cranelift, which Wasmtime uses, does the opposite: it looks at the whole function, shuffles things around, runs optimization passes and makes globally better decisions [10][4]. The post is straight about the price of skipping that, saying single-pass compilers are incredibly fast to compile with but generally produce worse code [10].
Railshot's way of buying that quality back is to not redo work the toolchain already did. The argument is that wasm binaries often arrive already optimized, so a module out of LLVM, Rust or TinyGo has been through an optimizer before Wago ever sees it, and Railshot does not need to rediscover those decisions [11]. What is left is structure the format hands over for free: blocks, loops, branches, types and the operand stack [12].
That premise is what a buyer has to check. It holds for a release build out of Rust or TinyGo. It is weaker for hand-written wasm or a debug build with the optimizer off, because there is no upstream pass to inherit and no second pass inside Railshot to clean up after it [11][9].
The post publishes no measurement. The targets are written as goals, and the one production number in it sits on wazero's side of the comparison: on some larger workloads, wazero "could peak at hundreds of megabytes of memory just to compile a module", the author wrote [17][7]. Set that against the footprint the post is defending, which is a few kilobytes of RAM to run some wasm here and a megabyte there, with no Docker containers or micro-VMs booted [16]. Hundreds of megabytes against roughly a megabyte is a factor of a hundred or more, and all of it lands in compilation [20].
The cost case is put as a question. "Hey, if i can reduce memory usage by 10x and improve performance by 50%, surely I can cut our costs in half, right?" the author wrote [14]. Halving a bill on a 50 percent throughput gain needs per-request CPU to be the thing that sizes the fleet, and the post does say that more CPU costs money and more memory costs money [15]. The 10x applies to compile memory, so it pays where compile peaks set instance size, which is the per-request or per-tenant compile case. Compile each module once and cache it, and you meet that peak once per module.
The post names Wasmtime, Wasmer, V8 and wazero, then asks "So why would I be stupid enough to make a new wasm runtime?" [2][18]. It is a fair question. In my context the deciding numbers would be compile-time peak and steady-state throughput on my own module set, measured against wazero on the same hardware. The evidence offered for the gap today is the author's production experience at two companies that run isolated wasm on wazero [3].
What to watch
- A Wago benchmark that separates compile time from steady-state throughput, names the module set and states the hardware.
- Whether Hypermode or Impart Security moves production wasm workloads off wazero onto Wago.
- An embedded build of Wago, the target the post lists as an eventual goal.