Mica 5.0.0 Technical Portrait · Chapter 8 of 9 · reading guide

Two things keep a six-figure codebase honest: a test suite that is the truth of what the compiler does, and an architecture clean enough that a newcomer can find their way. Mica has both.


◈ The test harness

Over 4,500 test cases across more than 740 test programs (over 150,000 lines)

The harness is a first-class development tool that has driven every feature from day one. The discipline is absolute: no feature lands without harness coverage, and the cases are not regression tests bolted on afterward — they are the specification.

CategoryWhat it validates
executionFull pipeline: compile → link → run → check stdout and exit code
errorsCompiler error messages and diagnostics
ILSpectra intermediate-language output
debugDWARF / debug-information output
exportExported intermediate-representation artifacts

(Assembly-level expectations ride inside IL and execution cases via CHECK directives rather than a separate category.)

Manifest-driven. Every test is a small JSON manifest — name, category, sources, and expected stdout / exit code. Adding a test is: write the Mica source, write the expected output, create the manifest. Done.

Variants. One source runs under several compiler configurations — debug, release, checked, and optimization and assembly modes — which is how more than 740 programs expand to over 4,500 test cases. The checked-arithmetic case is the classic example: the same program runs under debug, release, and checked, each with its own expected output and exit code, proving that overflow wraps silently in two modes and terminates with a diagnostic in the third. The 5.0.0 heap and nil work is proven the same way — dedicated cases pin each guarantee in turn: allocation and dispose, deferred cleanup, owner- and program-lifetime binding, the memory narration, arena exhaustion, self-referential lists and trees, nil as a value, and nil-dereference trapping.

CHECK directives. For IL and assembly output that contains variable content (register numbers, offsets), the harness uses regex-based CHECK: / CHECK-NOT: matching — CHECK: must match, CHECK-NOT: must not — much like LLVM’s FileCheck, adapted for Mica.

Optimization statistics — every improvement is measurable. The compiler does not just transform code; it reports what it did. Each optimizing pass emits per-pass counters to its diagnostic output — peephole patterns matched (broken out by kind: redundant loads removed, dead stores removed, compare-branch fusions, strength reductions, and more), functions inlined and call sites declined, constant folds, SSA store/load forwarding and dead-code counts, register coalescing, and the memory-obligation site classifications from Chapter 3. Zero-valued counters are suppressed, so the report shows exactly what each pass changed. This is not decoration: test cases assert on these numbers — a manifest can state "inlining": { "functions_inlined": 2 }, and the harness parses the compiler’s output and fails if the optimizer’s behaviour drifts. Every claimed optimization is pinned by a number the suite checks.

Stress tests. A separate, opt-in tier compiles generated programs up to 100,000 lines and passes a 1 MB aggregate through the ABI, pinning the compile-speed numbers from Chapter 7.

The test corpus is more than 740 real Mica programs — over 150,000 lines. It is the richest source of worked examples in the project; many snippets in this portrait are drawn directly from it.


◈ The shape of the codebase

The compiler is ≈ 35 packages and 99,516 lines of Go (107,642 with the test harness), in three layers.

The front end turns source text into a validated, lowered intermediate representation: the scanner and token model; the recursive-descent parser; the abstract syntax tree (the largest front-end component, with the visitor and the cross-unit global registry); the type system; the symbol table; the eight-pass semantic analyzer with its compile-time expression evaluator; the obligation analysis that proves heap safety; and a quality-gate layer that checks the compiler’s own transformations.

The middle and back end — the 5.0.0 build-out — turns that representation into native code: the IL inliner; the control-flow graph; SSA construction and its optimization passes; the dataflow analyses (liveness and reaching definitions); register and frame placement with aggregate ABI classification; and the dual lowering backend for x86-64 (System V) and ARM64 (AAPCS64), with the assembly models, the ELF encoder, and the shared DWARF v5 generator. This is the largest layer in the compiler — the dual backend alone is on the order of 27,000 lines — because supporting two architectures honestly, mirrored shape-for-shape, is most of what 5.0.0’s performance rests on.

The driver and runtime tie it together: the pipeline driver with its lock-free, file-parallel concurrency; the interop layer with its JSON contracts and the C standard-library runtime; the platform and ABI definitions; and the diagnostic, debug, and export support.


◈ Building the compiler

A single build step produces the compiler binary and a standard-library archive for each target architecture. When a program is compiled, the archive matching the target architecture is resolved automatically, with an explicit override available for vendored or alternate runtimes — which is also how a third-party allocator plug-in is selected (Chapter 3). The compiler itself builds with a standard Go toolchain and no dependency graph to assemble first.

Mica cross-builds between Linux/AMD64 and Linux/AArch64 on one host (Chapter 6). The compile step runs on non-Linux hosts as well; linking is performed through the GNU binutils integration on Linux.


Where to next

You’ve seen what Mica is, how it reads, how it manages memory, how it’s built, how fast it runs, and how it’s tested. The last chapter is about where it’s going — and it’s the most ambitious part of the story.

Chapter 9 — Roadmap & History · back to the reading guide