The Code Generator in Mere: A Compiler That Emits Itself

Interpreting a language is not the same as compiling it. The last self-hosted piece is the code generator — the part that emits WebAssembly instead of walking the tree — and finishing it closes the pipeline: Mere source to a Wasm module, entirely in Mere. Its sharpest moment is when the code generator, run on its own source, emits valid Wasm of itself — the definition of a bootstrappable compiler. Plus a parable about a bug that wasn't: an eerie boundary that turned out to be a memory ceiling, not a flaw.

mereself-hostingcodegenbootstrapwebassemblylanguage-design

The self-hosted pipeline can now read Mere, check its types, and interpret it. But interpreting a program is not the same as compiling it — an interpreter runs the tree directly, while a compiler emits code that runs without the interpreter present. The last piece of a self-hosted compiler is the one that makes that difference: the code generator, written in Mere, turning a checked program into WebAssembly. Finishing it closes the loop the whole part has been circling.

Why WebAssembly, of the three

The OCaml compiler has three code generators — to C, to LLVM, and to Wasm — and self-hosting picked one: the Wasm generator, a WAT emitter. The choice is not arbitrary. Wasm is the browser target, and the browser is where the self-hosted pipeline already lives — the formatter, the REPL, and the type checker were all running as web pages. A self-hosted Wasm generator closes the demo that the whole self-host effort had been building toward: paste Mere source into a page, and get a Wasm module back, with no server and no OCaml in the loop. It is also the smallest of the three generators in the original, which makes it the natural first — and, as it turned out, only necessary — target to self-host. The C and LLVM generators stay in OCaml; the self-hosted compiler emits Wasm.

Emitting Wasm from Mere is the same work the Wasm backend episode described, now written in the language it targets: laying values out in linear memory, representing closures as a function-table index paired with an environment offset, lowering match to loads and branches, threading everything through the sandbox’s import mechanism. All of it, this time, in Mere consuming the shared syntax tree the front end produces and the type checker blesses.

The compiler that emits the compiler

Here the self-reference reaches its deepest point. The code generator is itself a Mere program — a substantial one. So you can do the thing that names a compiler as truly self-hosting: run the code generator on its own source. Feed the Wasm generator, written in Mere, its own source file, and it emits valid WebAssembly of itself — a WAT text well over a million characters, which the standard Wasm assembler accepts and turns into a Wasm module. The compiler compiled the compiler.

That is the definition of a bootstrappable compiler, and it is a genuine milestone rather than a flourish. A compiler that can emit its own source is, in principle, free of the language it was first written in: OCaml was the scaffolding that got Mere off the ground, but once the Mere-written generator can produce the Mere-written generator, the OCaml original is no longer logically required to keep the language alive. Practically it stays — as the reference the self-hosted components are validated against, and as the host of the extern primitives — but the dependency is broken in principle, which is the whole point of bootstrapping.

Honest about where the loop stops — and a red herring

It is worth being precise about what was achieved, in the spirit of the honest ledger the project keeps. Compile-time self-compilation works: the Mere code generator, run under the existing toolchain, emits valid Wasm of its own source, and the assembler accepts it. That milestone is pinned down in the continuous-integration suite — a standing test that self-emission produces a WAT output above a size threshold and that the assembler validates it.

The full runtime bootstrap loop — taking that self-emitted Wasm, running it, and having it compile Mere in turn — was harder to close, and the reason it was hard is a small parable about chasing the wrong cause. Running the compiled compiler inside Wasm trapped, and the trap had an eerie signature: a program that referred to a function whose name was five characters long compiled fine, and six characters trapped — an exact, content-independent boundary that looked like a deep bug in how names or memory were laid out. It was a red herring. The real cause was mundane: self-compilation needs vastly more working memory than compiling a small program — tens of megabytes of scratch arena — and the generated Wasm module’s linear memory was capped far below that, at a few megabytes. The “five-or-six-character boundary” was just the input at which the arena crossed the ceiling; the name length happened to correlate with total allocation and fooled the bisection. Raising the memory ceiling closed the loop, and a runtime-bootstrap check now stands in CI: the compiled compiler runs and compiles a program end to end. The one piece still deferred is a growable allocator for workloads larger than that raised ceiling.

The lesson is that the honest ledger cuts both ways. It stopped an over-claim — for a while the loop genuinely wasn’t closed — and it also meant not mistaking a memory ceiling for a correctness bug, and following the eerie boundary down to the boring truth instead of theorizing about the compiler. “Close enough” is refused in both directions: don’t claim a bootstrap you don’t have, and don’t diagnose a deep flaw you don’t have either.

The pipeline, whole

With code generation self-hosted, every stage of the compiler exists in Mere — lexer, parser, formatter, evaluator, type checker, and code generator, some thousands of lines of the language describing itself, all sharing one syntax tree, each cross-validated against its OCaml twin. The OCaml compiler’s role has quietly inverted over the course of this part: it began as the entire implementation and ends as scaffolding — a reference to check against and a host for primitives — while the working compiler now exists in the language it compiles. That inversion didn’t happen in a leap; it happened one cross-validated component at a time, each held to the one that came before.

The compiler exists in Mere and can emit itself. What remains of the story is not another component but a place: everything built here was meant to run somewhere a person could reach it. The formatter, the REPL, the type checker, and the compiler were each wired into a web page as they landed. Next, the close of Part V: Mere running in the browser — four live pages where the self-hosted pipeline, compiled to the sandbox it targets, does its work in front of you.

← Back to Mere: Building a Language