Mere compiler — written in Mere, running in the browser
Paste a Mere expression or a full file on the left and press Compile. The Wasm module on this page tokenizes, parses, and emits a fresh Wasm module for your input — all written in Mere itself, no server round-trip. The companion pages self-host fmt, self-host REPL, and self-host type-checker cover the format, evaluate, and type-check sides of the pipeline; this one closes §S3 and completes the Mere-bootstraps-Mere story end-to-end.
Source:
codegen_wasm.mere
(WAT emitter — int / arith / let / if / fn (closure ABI + free-var
capture) / match / variant / tuple / top-level / strings / let-rec
backpatching),
parser.mere,
lexer.mere,
bridged by
selfhost-compile.mere.
The shared AST lives in
contrib/parser/ast.mere.
(press Compile to see the emitted Wasm Text)
Pipeline
Four contrib libraries combine into the Wasm running on this page:
lexer.mere— source → tokensparser.mere— tokens → ASTcodegen_wasm.mere— AST → Wasm text (this stage)selfhost-compile.mere— DOM glue (textarea →parse_and_emit→ output)
codegen_wasm.mere emits the Wasm Text Format
(`.wat`) — what wat2wasm compiles into binary
`.wasm`. It covers int literals + arithmetic / comparison /
logical / negation; `let` with full pattern destructure; `if`;
`fn` + `app` with a 2-word closure ABI (env pointer + function
table index, bump-alloc'd on a per-module heap); free-variable
capture (closures over outer scope); `match` as a nested
`if (result i32)` cascade with pattern checks and bindings;
tuples and variants as heap records; top-level decls with
lazy variant-tag allocation; string literals interned into
(data ...) segments; and let rec
(including mutual recursion) via closure-env backpatching.
Try it out
The output panel shows the raw Wasm Text. To actually run the compiled module:
- Copy the emitted WAT.
- Save it as
out.wat. wat2wasm out.wat -o out.wasm— produces a binary Wasm module.node -e "WebAssembly.instantiate(require('fs').readFileSync('out.wasm'),{env:{}}).then(({instance})=>console.log(instance.exports.main()))"
The printed integer is the result of evaluating the source
expression. For example,
let rec fact = ... in fact 5 prints
120.
Cross-validation
dune runtest exercises this pipeline on 14
source-string fixtures. The harness feeds each through the
self-host parse_and_emit (running on the OCaml
interpreter), shells out to wat2wasm + node,
and asserts the printed integer matches an expected value.
As of the closing Stage 53f commit, all 14 cases pass — the
self-host codegen is end-to-end correct on the slice it covers.
Limitations
-
No stdlib codegen. Functions like
print,show,str_concat,vec_*,map_*,file_*aren't emitted — the OCaml-side codegen ships ~2000 lines of runtime helpers for these. The self-host MVP would call them via(import "env" ...)declarations + extern fn stubs in the host's `env` table; this is the standard pattern but isn't wired into the demos yet. - No polymorphic monomorphization. Mere's let-poly types `let id = fn x -> x in id 1, id "a"` need type-driven monomorphization on the codegen side. The MVP uses i32 uniform representation, so this works at runtime but isn't type-correct.
- No optimisation pass. Tail calls aren't optimised; closure records are never reused; constant folding only happens at the parser level.
-
Errors abort the Wasm instance. Unbound vars
and unsupported patterns surface as
failcalls that trap the host Wasm; reload to recover.
Why this matters
Phase 53 closes §S3 — the self-host code generator. Combined with §S1 (parser + fmt, Phase 49–50), §S2.A (evaluator, Phase 51), and §S2.B (type-checker, Phase 52), Mere can now parse, format, type-check, evaluate, AND compile its own source code to a Wasm binary, in the browser, with no server round-trip. The full toolchain is Mere all the way down — the OCaml runtime remains only as the extern-fn host for I/O primitives.