Wasm Gives It Back

The port of memory reclamation to the Wasm backend had an unusual starting point: the git history showed it had been tried before, years of phases ago, and removed as unsound — escaping values were corrupted, so the backend chose to leak instead. The revival kept the old idea and added the three missing pieces: copy the result out, reject the escaping stores at compile time, and reject escaping closures through the result type. The acceptance test was already written — the same 2048 probe, now expecting a flat line. It got one: the bump pointer froze at 4,544 bytes for thirty thousand moves.

merewasmmemory-modelregionslanguage-design

The forcing number from the previous episode — a 64-megabyte lifespan, ~8.4 kilobytes per move — bought the implementation session it was meant to buy. But the session began with an archaeology lesson. The Wasm backend’s region-block emission carried a comment explaining that it used to do exactly the obvious thing — save the bump pointer at block entry, restore it at exit — and that this had been removed as unsound: a value allocated inside the block that escaped (a container returned from the block, in the recorded example) pointed into memory that the next allocation would silently overwrite. Faced with corruption, the honest choice at the time was to leak instead. The port, then, was not “add reclamation to Wasm” but something more interesting: revive a known- unsound design by supplying the pieces whose absence made it unsound.

Three pieces, and a simpler machine

The pieces were the same three the C backend’s reclamation had needed, translated. First, the result copies out: a block’s value is deep-copied by the per-type copy machinery — twice, in fact: once to a staging copy above the block’s garbage, then, after the bump pointer restores, down into the enclosing allocation range. The two copies cannot overlap, because the staging copy sits above everything the body allocated — a small proof that replaces a subtle bug with an argument. Second, escaping stores are compile errors: Wasm has no per-container storage, so where C copies stored values into the container’s own region, Wasm simply rejects — pushing a heap value into a container created outside the block, sending to a channel, spawning, or handing a callback to an extern all fail to compile, with pointers to the docs. A container created inside the block mutates freely; it dies with the block. Third, escaping closures are rejected via the result type. What made this port lighter than the original C work is that Wasm’s single-threaded world needs none of C’s apparatus — no thread-locals, no heap block chains: the mark rides the Wasm value stack, and one scratch global shuffles the copies. The same design, one moving part instead of four.

The flat line

The 2048’s key handler gained one region R { ... } around its body — snapshots, render strings, and every intermediate now die with each move, while the board itself, a vector of plain integers created at startup, mutates in place untouched by any of this. The probe reran: thirty thousand moves, zero traps, and the bump pointer read 4,544 bytes at move one, at move fifteen thousand, and at move thirty thousand. Not flat-ish — frozen. Zero net allocation per move, on a game that had been dying at 7,700. The suite kept one piece of history straight: the old test that asserted the leak (the Phase-16.4 behavior, pinned deliberately when leaking was the sound choice) was replaced by tests asserting the new shape, including that returning a container from a block is a compile error rather than a corruption.

Where the arc closes

This closed a loop that had been open across three parts. The long-lived-programs arc ended with reclamation on the C backend and an honest note that Wasm still had none; the probe episode turned “none” into a number; this episode turned the number into a flat line. Each backend now has its own honest story: the interpreter is garbage-collected; C reclaims with copy-on-store so stores are unrestricted; Wasm reclaims with escape errors instead of copy-on-store — stricter, simpler, and enough for the browser programs it exists to serve. The remaining gap between the C and Wasm stories is written down where the others were, waiting for a program that needs it. The next episode is about a different kind of discovery: the probe that found its feature already built.

← Back to Mere: Building a Language