mc documentation
A compiler small enough to read, written in itself. mc compiles a schoolbook-C language — seven
types, one opaque pointer, if and loop — into Mach-O, ELF and PE, for macOS, Linux and
Windows on arm64 and x86-64, writes and signs (or directly writes) the executable itself, and
reaches a fixed point where one generation is byte-identical to the next. Everything the core
leaves out — while, for, classes, a second backend, a whole object format — you teach it from
ordinary mc source.
New here? Start at guide/00-getting-started.md. It goes from nothing to a running signed binary in about two minutes.
Curious how it compares? comparison.md measures mc against C, Go, Zig,
Rust and C# on the same workload: a 4.1 ms compile, a 33 KB executable and a 1.2 MB toolchain.
The default road folds constants and nothing else, at clang -O0 level; -O (M49) closes most
of the gap — 1.30x clang -O2 on the same workload, measured and committed in a reproducible
bench cell (M50), not estimated. The sources are in ../bench/.
The guide — task-oriented, read in order #
| page | what it covers |
|---|---|
| Getting started | install, first program, --exe, the five dumps |
| One file, one program | the working tour of the language: types, memory, control flow, extern, function pointers |
-O: what it does and what it does not | the optimizer, off by default: what qualifies for a register, what disqualifies a function, what it costs and what it never changes |
| A project | mc build and every mc.toml section by example |
| Using a package | [deps], mc.lock, #include <pack/file.mc>, vendoring, and publishing a package of your own |
| Publishing a package | registering on minicompiler.dev, what a GitHub Release publishes, the validator's box, the CI action, yank |
| Teaching the compiler | #token/#infix/#prefix, #rule and the prelude, then the syntax hooks with a worked toy language |
| Emitting bytes | #section, #opcode, emit()/reloc(), pass(), backend(), and the arm64-surface proof |
| Cross-compiling | Linux arm64 targets, sysroots, external linkers |
| Two worked examples | examples/api and examples/lang, walked through |
How mc compiles itself | the bootstrap chain, the fixed point, and the determinism rules for contributors |
| Footprint | what the smallest program costs per target, floor by floor, and the ceilings that guard it |
mc on a Linux host | the host layer, the Linux bootstrap chain, cross-building the compiler, what make check skips on Linux and why |
| A new primitive | teaching mc a value type it has never heard of: type_new, syntax_lit, a derived machine, intrinsic |
mc on a Windows host | the same for Windows: no C runtime at all, the kernel32 runtime object, the .exe suffix, CRLF, and the Windows make check subset |
| Recreating the compiler | "I want a compiler for X and nothing else": the five parts of <mc/core>, what each omitted one costs in bytes and in capability, removing a type or an intrinsic, and declaring the width of uptr |
| Run something you do not trust | mc sandbox run|exec|check: the box in one command, the four caps, the five refusals and their exit codes, a project inside it, and the two hosts that have none |
| A new architecture | adding an instruction set and an output format from OUTSIDE the compiler: the three registrations, how to prove the encoder, which families it reaches (including 8-bit parts, since M40) and which one it excludes permanently |
The reference — exhaustive, read by lookup #
| page | what it lists |
|---|---|
| language.md | grammar, types, precedence, semantics, limits |
| directives.md | all ten # directives, with errors and examples |
| cli.md | every command, flag, dump and exit code |
| toml.md | every mc.toml key: type, default, meaning |
| packages.md | [deps] and mc.lock: the resolution order, the tree hash, the closure rule, the refusals |
| tools.md | mc tool: install a program package, run it boxed, the permission-to-sandbox mapping |
| hooks.md | every public function of the parser and hook API |
| sysroot.md | where a cross link finds its files: the resolution chain, the cache, the messages |
| objects.md | the object model (sec_*, sym_*, reloc_add) and the codegen accessors (gen_*) |
| machine.md | the machine task contract: the 31 tasks, and the three instruction sets side by side |
| diagnostics.md | every message the compiler emits, with cause and fix |
| bundle.md | every #include <name> the binary carries |
| sandbox.md | mc sandbox: the box, the shim, check, and what is not isolated |
The design documents #
Most of these predate this tree and record why things are the way they are — the guide and the
reference describe the compiler as it is; these describe the decisions that produced it.
comparison.md is the exception: it reports a measurement taken against the tree as it stands,
not a decision behind it, and lives here because it is exhaustive rather than task-oriented.
| document | subject |
|---|---|
| plan.md | the language, the teaching surface, the architecture, the budget, the milestones |
| core-language.md | the core language as specified milestone by milestone |
| surface.md | the teaching surface, tier by tier, with the acceptance criteria |
| build.md | mc build, the bundle, #embed, Linux and Windows targets, limits |
| bootstrap.md | cutting clang, then ld, then the checkout |
| determinism.md | the rules that make the output reproducible |
| macho-notes.md | every Mach-O field, with its verified value |
| comparison.md | mc measured against C, Go, Zig, Rust and C#: compile time, binary size, run time, and the feature matrix |
| ci.md | the GitHub Actions workflows and the release process |
| specs/ | one spec per milestone, M1.md … M53.md |
There is deliberate overlap between the design documents and this tree — surface.md and
guide/30+guide/40, build.md and guide/20+guide/50, core-language.md and
reference/language.md, bootstrap.md and guide/70. The guide and reference are written
against the compiler as it stands today and are checked mechanically; the design documents keep
the reasoning and the milestone history.
1.0.0 #
The road to 1.0.0 — plan.md § "What 1.0.0 promises, and what it does not" — is what
turns the API's current stability into a written promise: a recorded public surface (421 names
and formats across seven kinds, reference/hooks.md § 8) that does not lose
an entry without a MAJOR, ten committed fixed points on five hosts, and a release canary — a real
out-of-tree consumer's own build gating every publish before the registry hears about it
(ci.md § The canary). It does not promise a src/ internal, a message's wording, or
an emitted byte, which is determinism.md's promise and moves by design. The
surface freeze itself landed in 0.17.0 (specs/M53.md); 1.0.0 is cut when the
real-world consumer's own gap list reaches zero.
How this documentation is checked #
scripts/check-docs.sh, inside make check, does three things:
- Coverage. Every
p_*,syntax*,type_alias,pass,backend*,machine*,sec_*,sym_*,reloc_addandgen_*definition insrc/, every CLI flag, every TOML key the driver looks up, and every directive in the lexer's table must appear inreference/. The lists are extracted from the source, never written down in the script, so a new public function fails the check until it is documented. -
Samples. Every fenced
```mcblock in this tree is compiled by the real compiler. A block that declares// expect-exit:or// expect-stdout:is built with--exeand run, and its output compared; one that declares// expect-error:must fail to compile with that text on stderr. A block may name the compiler that should build it — - Links. Every relative markdown link in this tree resolves to a file that exists.
So every command, every message and every program you see here was actually run.