moxzi
Docs / Concepts / Self-hosting and fixed points

Self-hosting and fixed pointsalpha

Why does moxzi compile itself, what is a C2==C3 fixed point, and why is it the real correctness gate?

Self-hosting means the compiler's source is written in the language it compiles, and the binary that compiles it is a binary it produced. A fixed point is the point at which generating one more compiler stops changing anything.

The constraint is that self-hosting alone proves very little. A compiler that compiles itself into a broken compiler still "self-hosts". The claim that carries weight is the fixed point plus the requirement that each generation is itself a working compiler, and those two together are the acceptance criterion here.

The generations#

Each generation is the compiler wasm produced by the one before it, compiling the same sources.

NameWhat it is
C0compiler.wasm built by the reference toolchain (make compiler-canister-wasm)
C1the compiler wasm produced by C0 compiling the compiler's own sources
C2produced by C1 compiling the same sources
C3produced by C2 compiling the same sources

C1 == C2 says the compiler has stopped injecting its own idiosyncrasies into its output. C2 == C3 says that equality is stable rather than a coincidence of one generation. Both have been verified byte-identical in this project, and the on-chain fixed point recorded in CHANGELOG.md is C2 == C3, established at real mainnet caps rather than with limits relaxed.

Why the fixed point is stronger than the corpus#

A corpus tests the programs it contains. The compiler's own source is a 169-file, 7.6 MB Motoko program that exercises the language the way real code does and the way a test suite does not: deep pattern matches, mutually recursive modules, generic classes, thousands of methods, functions large enough that a single one exceeds an IC message budget.

More importantly, a self-compile closes a loop the corpus cannot. If the compiler miscompiles a construct it uses itself, the corpus may never notice — but the next generation will be built with a broken version of that construct, and either it fails to build, or it builds and produces different bytes. The self-referential structure is the detector.

That is not hypothetical. The method that found several real miscompiles was diffing C0's codegen function against C1's codegen function: when a compiler miscompiles the code that does the compiling, that function is the one to look at. Three Nat64-representation bugs on the enhanced-orthogonal-persistence path were found exactly this way, in code the typer never sees.

Fixed point is not enough on its own#

Byte-identical output between two generations means the codegen agrees. It does not mean the produced binary works, because the compiler's #done output is unlinked: RTS imports unresolved, duplicate keep_memory_reserve exports still present, and a wasm engine will refuse to parse it. moc -no-link has the same two artifacts of the same design.

So the acceptance test is two-part, and the second part is the one that took the longest:

  1. The generations are byte-identical.
  2. The linked generation is a valid module that is a working compiler — it compiles programs, and it compiles the corpus with the same results as the generation before it.

A run that fails WebAssembly.validate on an unlinked artifact is a healthy run being misread. So is a run that dies on M0020 unresolved import because the source closure manifest was stale — paths existing does not mean the closure is complete.

The mainnet record#

The self-compile has been run on-chain, under the real 40 billion instruction per-message cap with rate limiting enforced, not in a benchmarking mode with limits lifted.

BuildSourceMessagesReported instructionsCycles
hello.mo1 file, 217 B52~0.001 T
the compiler, self-compiling177 files, 8.0 MB2,345~1.6 T
evm_engine661 files, 15.3 MB4,138~1.6 T/attempt

Those figures are recorded in docs/onchain-builds.md, measured 2026-08-21 and 2026-08-25. The artifacts were byte-identical to the local builds of the same sources — which is the whole point: a canister that produces a different compiler than your laptop does gives you nothing to check.

The compiler self-compiling is the pathological case for cost — 200,657 cycles per source byte against 69,677 for evm_engine — because its functions are enormous. Price on-chain compilation from that number, not from the average.

What made it possible#

Three things, none of which changed a byte of output:

Things you cannot do#

Not possibleWhy
Bootstrap without the reference toolchainC0 is built by moc. There is no independent path to a first compiler; the fixed point starts from a borrowed binary, as every self-hosted compiler's does.
Treat a fixed point as a proof of language conformanceIt proves internal consistency. Conformance is the reference-moc corpus comparison, which is a separate gate.
Skip the linker in the loopEach generation must be linked before it can compile the next. The linker is itself a Motoko canister compiled by the same compiler.
Reproduce the mainnet figures locallyLocal wasmtime fuel prices this workload roughly 3× cheaper than mainnet; a local worst message under 15 B landed near 40 B on-chain. Local numbers are a lower bound.

Next#

On this pageThe generationsWhy the fixed point is stronger than the corpusFixed point is not enough on its ownThe mainnet recordWhat made it possibleThings you cannot doNext