5.0.0 — Technical Summary
Mica is a systems programming language with clean, structured syntax and explicit low-level control, compiled from scratch — in pure Go, with no external dependencies — to native Linux x86-64 and ARM64 ELF binaries with full DWARF v5 debug information, using the GNU assembler and linker as the only external tools. It is the work of a single engineer with AI assistance, built quietly over around three years.
5.0.0 is the largest release in the project’s history and the first prepared for a public audience. It adds the two capabilities a systems language is ultimately measured by — a heap and a real optimizing backend — and it does the first of those in a way that is, as far as we know, distinctive: heap memory safety is proven at compile time by flow analysis, with no garbage collector, no borrow checker, and no lifetime annotations.
Structured clarity. C control. Born for AI.
At a glance
| Implementation | Pure Go, zero external dependencies |
| Compiler source | 107,642 gross lines of Go across 594 files; ≈ 35 packages |
| Whole project | ≈ 268,000 gross lines (Go + Mica + C runtime + contracts) |
| History | 2,799 commits since 2023 |
| Targets | Linux x86-64 (System V AMD64) and ARM64 (AAPCS64) |
| Output | ELF executables, static archives (.a), shared objects (.so) |
| Debug info | DWARF v5, with a registered DW_LANG_Mica language code |
| External toolchain | GNU as + ld only |
| Heap safety | Proven at compile time by flow analysis |
| Allocators | Three plug-ins behind one seam: hosted, region, fixed-arena |
| Standard library | A single 1,149-line C23 runtime |
| Verification | Over 4,500 test cases across more than 740 test programs (over 150,000 lines) |
1 · The language
Mica reads like structured intent. Its design rests on a precise, expressive type system rather than on runtime machinery.
- Structured core —
program/library,const/type/var, nested functions and procedures (to 16 levels), and a small, complete statement set:if,while,for(to/downto),repeat … until,case,leave.and/orshort-circuit. - Fixed-width numeric types —
int8/16/32/64,uint8/16/32/64,float32/64,bool,unicode(a 32-bit code point), andstring(a length-prefixed descriptor, UTF-32 by default). - The ordinal type system — enumerations, subranges, and the integer/char/bool
types are all ordinals with computable domain bounds the compiler uses
everywhere: array index domains (with lower bounds that mean something —
array[1900..2100],array[Jan..Dec]), set cardinalities,for-loop ranges, andcaselabel validation. Domain meaning lives in the type, not in the programmer’s head. - Explicit memory — pointers are spelled in words (
pointer T,address x,value p), with a typed, zero-cost auto-dereference for pointer-to-record field access. - First-class
niland recursive types —nilis an assignable, comparable pointer value, and a record may contain a pointer to its own type, enabling linked lists, trees, and graphs. - Checked arithmetic on demand — under the
checkedprofile the compiler weaves overflow detection into every signed arithmetic site; underdebug/releasethe hardware wrap semantics apply. - Compile-time generics — monomorphized: a
genblock parameterizes a procedure or function over a type constrained by capabilities (ordered,numeric,equality, …); type arguments are inferred at the call, and one specialization is generated per concrete type set. No boxing, no runtime cost. - Strings — a length-prefixed descriptor, UTF-32 or UTF-8 per target from one source; chained concatenation is fused into a single allocation with no intermediate temporaries, and format strings are validated at compile time.
2 · Memory safety by flow
This is the heart of 5.0.0. Every heap allocation creates an obligation that
the compiler must see discharged on every control-flow path. There are exactly
three ways to discharge one: a dominating dispose, registration with an owner
(defer dispose p, or new T until F / until program), or transfer by
returning the pointer. Each allocation site is then classified into one of four
lifetime classes — owner-bound, transferred, manual-verified, or
manual-unverified — and the compiler can narrate that decision, in source
order, naming the owner and the allocator it scheduled.
The proof is a dataflow analysis: a forward may-analysis over the intermediate code and control-flow graph, run in every compilation mode. It is the same kind of reasoning a backend uses to schedule registers, turned onto lifetimes. The result is a guarantee that reads simply: a leak of a fully-tracked allocation does not compile. Where a pointer’s flow exceeds what the analysis can follow precisely, the site is reported as manual-unverified and narrated — never silently miscompiled, and never a rejected correct program.
What makes this concrete is that the contract reasons about every path, not
about whether a dispose appears somewhere in the text.
The simplest case — allocate and never release
A dispose that runs on only one branch still leaves a path that leaks. The
compiler finds it and names the way out:
1 program LeakOnePath;
2 imp
3 WriteLn : std;
4
5 procedure Handle(flag : int64);
6 var
7 p : pointer int64;
8
9 begin
10 p := new int64;
11 value p := 42;
12
13 if flag = 1 then
14 dispose p;
15
16 WriteLn("handled");
17 end;
18
19 begin
20 Handle(1);
21 end.
10: p := new int64;
^ obligation error 13102 [10,10]: heap cell is not released on every path:
last use at line 14, the path leaving at line 17 releases nothing —
dispose it, defer the dispose, return it, or bind it to an owner with 'until'
Across loop iterations
The analysis reasons about loops too. Each turn here overwrites p, abandoning the
cell the previous turn allocated:
1 program LeakInLoop;
2 imp
3 WriteLn : std;
4
5 procedure Fill(n : int64);
6 var
7 i : int64;
8 p : pointer int64;
9
10 begin
11 for i := 1 to n do
12 begin
13 p := new int64;
14 value p := i;
15 end;
16 end;
17
18 begin
19 Fill(3);
20 end.
13: p := new int64;
^ obligation error 13103 [13,14]: heap cell is allocated again while the
previous cell is still owed — dispose the previous cell before the
allocation repeats, or bind it to an owner with 'until'
Every message names the way out — dispose it, defer the dispose, return it, or
bind it to an owner with until — and a program that takes any of them compiles.
Moving the dispose in Handle so it runs on both paths builds and runs cleanly.
The contract is against silent leaks, never against correct code: it tells you
exactly where the obligation escapes, and how to close it.
3 · The heap runtime
Proving a lifetime is half the work; serving it is the other half. Mica’s
intermediate code never names malloc — it emits allocator intrinsics, and the
compiler chooses which allocator serves each site from the lifetime class it
proved. Allocator selection is instruction selection.
| Allocator | Behaviour | Chosen for |
|---|---|---|
| Hosted (default) | calloc/free over the C allocator; transparent to valgrind and AddressSanitizer | manual, transferred, and unverified sites |
| Region | per-owner arena — bump-allocate, reclaim the whole region in bulk at the owner’s exit | owner-bound sites (until F / until program) |
| Fixed-arena | one statically-budgeted byte array with no operating-system heap behind it — for freestanding deployment | every site, when linked as that deployment class |
defer, owners, and regions all ride one small runtime data structure — a
per-owner cleanup list drained last-in-first-out at the owner’s exit. The
deployment class (hosted or fixed-arena) is a per-program link decision, never
a source change, because code generation is byte-identical across classes. The
allocator is a genuine plug-in: a third-party archive that honours the heap symbol
contract links and runs with no change to the compiler.
4 · The optimizing backend
5.0.0 introduces a full SSA-based middle and back end, the largest subsystem in the compiler.
- Inlining — a pure IL→IL pass (body budget 256 instructions) that runs before the graph is built, so later passes tidy the substituted code.
- Control-flow graph — basic blocks via the leader algorithm, with explicit edges.
- SSA — pruned φ-placement at the iterated dominance frontier, renaming over the dominator tree, and a suite of passes over the form: constant folding and constant-branch (dead-code) elimination, store/load forwarding, value-range analysis, loop rotation and invariant motion, interference checking, and a verifier.
- Dataflow — liveness (backward) and reaching definitions (forward), solved by worklist to a fixed point.
- Register allocation — a whole-function linear scan with loop-weighted spill costs by default, plus an optional Chaitin–Briggs graph-colouring allocator; debug builds keep every value on the stack for predictability, register homing is a release-mode optimization.
- Two architectures, mirrored — x86-64 (System V) and ARM64 (AAPCS64) lowering backends behind one shared seam, written shape-for-shape, with every divergence a named ISA constraint. A per-architecture peephole optimizer runs over a shared dispatch engine.
5 · Code generation and debugging
- ELF output — executables, static archives, and shared objects, with correct section layout.
- DWARF v5, architecture-agnostic and shared by both backends. A registered
DW_LANG_Micalanguage code means GDB reports the source language as Mica; arrays carry their real declared bounds (anarray[1900..2100]is not shown zero-based), and enumerations print by name. - ABI fidelity — one ABI classification of every type feeds the call emitter, the register allocator, and the DWARF generator alike, so a value is passed, returned, and described to the debugger the same way the platform ABI specifies.
- An inspectable pipeline — the token stream, AST, Spectra IL, SSA form, assembly for both AMD64 (Intel or AT&T syntax) and ARM64, and the binary can each be exported and read; the compiler will also narrate its memory decisions and report per-pass optimization statistics, so every transformation is measurable.
6 · The platform
Mica’s standard library grows by surfacing what already exists rather than rebuilding it. Every C library that follows a stable ABI is reachable through a JSON contract with full compile-time type and format-string checking, and Mica binaries link with C as equal citizens — in both directions.
- Contracts ship for standard I/O, process arguments, numeric limits, the C
standard library, and
libm(math, linked with-lm). - No separate I/O runtime — console I/O is resolved, per the target’s string encoding, onto the C standard I/O layer; the encoding boundary lives in the contracts, not in programs, so the same source serves UTF-32 and UTF-8 targets.
- Type fingerprinting catches ABI mismatches between compilation units at compile time.
- Cross-compilation between Linux/AMD64 and Linux/AArch64 works on one host.
7 · Performance
Performance is measured with the deterministic retired-instruction count (Ir) as
the primary metric and wall-clock time as an advisory second, on x86-64 and ARM64
development machines, reproducible from committed benchmark scripts. (Ratios are
Mica ÷ gcc -O2; below 1.00× means Mica executes fewer instructions.)
- The heap is additive. A heap runtime call is emitted only in a function that
uses
new,dispose,defer, oruntil. Non-allocating compute kernels are byte-identical to their pre-heap baseline. - Scalar kernels are competitive. On the Computer Language Benchmarks Game
kernels, Mica runs within roughly 0.94×–1.42× of
gcc -O2; Mandelbrot executes fewer instructions thangcc -O2on both architectures. - On an allocation-bound workload, the region allocator wins on instruction
count. Repeated waves of small cells run at 0.80× of
gcc -O2’s retired instructions on x86-64 — the compiler-scheduled region (bump-allocate, reclaim in bulk) executing fewer instructions than glibcmalloc/free. On wall-clock, glibc’s low per-call latency keeps it ahead; on the deterministic instruction count the region leads. Safety analysis and allocation strategy are the same engine here: the programmer wroteuntil F, and the speed followed from the proof. - Compile speed. A 100,000-line program compiles, with full optimization, in about 5.5 seconds, on a lock-free, file-parallel driver.
8 · Engineering discipline
No feature lands without coverage in the manifest-driven harness — over 4,500 test cases running on more than 740 test programs (over 150,000 lines), across execution, error, IL, debug, and export categories. Tests run under multiple compiler configurations from one source, and an internal quality-gate layer checks the compiler’s own transformations. The compiler is pure Go, with no dependencies to resolve and nothing beyond a standard Go toolchain required to build it.
9 · How Mica stands against GCC 15
GCC 15.1 (April 2025) is a roughly 38-year-old, ~15-million-line toolchain with about 48 CPU back ends and ten front ends. Mica is young and small, with one language and two targets. The map below marks where each is distinct.
| Dimension | Mica 5.0.0 | GCC 15 |
|---|---|---|
| Compile-time memory-safety proof | Flow analysis proves heap discharge on every path; a fully-tracked leak does not compile | -fanalyzer is, by its own documentation, “neither sound nor complete … a bug-finding tool, rather than a tool for proving program correctness,” and C-only |
| Allocation-bound performance | Region allocator at 0.80× of gcc -O2 retired instructions on one workload | The baseline (lower wall-clock latency via glibc) |
| Scalar performance | Within ~0.94×–1.42× of gcc -O2; ahead on Mandelbrot | The mature reference, decades of tuning |
| Optimization breadth | A new SSA backend with the core passes | Full IPA, LTO, PGO, polyhedral and vectorization machinery |
| Target architectures | x86-64 and ARM64, Linux | ~48 CPU back ends, many operating systems, GPUs |
| Languages | One structured systems language | C (C23 default), C++, Fortran, Ada, D, Go, Modula-2, COBOL, and more |
| Debug info | DWARF v5 on both targets, custom Mica language code | DWARF 5 by default |
| External dependencies | Pure Go; GNU as + ld only | A large self-contained toolchain |
| Transparency | Every stage exportable; memory decisions narrated | Internal IRs (GIMPLE/RTL) exist but are large and internal |
| Maturity & ecosystem | Months–years; single developer + AI | ~38 years; hundreds of contributors; a vast ecosystem |
Two distinctions are Mica’s own: a different category of guarantee — heap safety proven at compile time, where GCC offers runtime hardening and a best-effort analyzer — and deliberate minimalism and transparency. On nearly everything else, GCC’s maturity and breadth lead, and that is the honest shape of a young compiler beside a foundational one.
10 · Direction
The road ahead is consistent: Mica grows new capability out of the foundation it already has. Through the rest of the 5.x line, a modern text and string runtime, broader standard-library coverage with compiler-emitted contracts, first POSIX and Linux API access, and a supported editor experience. Then the track that gives the language its tagline — built-in fixed-shape vector and matrix types with compile-time shape checking, grown directly out of the ordinal type system; mixed-precision numerics; SIMD and accelerator lowering; and structured concurrency. These are the stated direction, not present capability — but the foundation they build on is the one described above.
Mica is being built in the open because compilers deserve to be understood, not just used. If it is useful to you — as a language, as a study in compiler construction, or as a glimpse of where structured systems programming can go — that is exactly what it is for.
License: MCL-1.0 — free for personal learning, private projects, academic research, and teaching; commercial use by separate agreement. Contact: info@mica-dev.com · Full detail in the Technical Portrait.
GCC 15 facts are drawn from gcc.gnu.org (release notes; optimization, debugging, and static-analyzer documentation). Mica figures are reproducible measurements from the project’s committed benchmark scripts on the author’s x86-64 and ARM64 development machines, gated on retired-instruction count. All figures are a snapshot of 5.0.0 in active development.