Mica 6.12.5 is released for Linux x86-64 and AArch64. Its headline is one
sentence: Mica gets projects, and becomes a scripting language. A three-line
mica.project beside your sources is read identically by the build, the
editor, and the run mode — and one Mica file now runs as easily as a shell
script. Underneath, the compiler itself was split into four modules with a
backend SDK that frontends other than Mica’s own can drive.
But the part of this release we are proudest of is not a feature. Between the preview and this article lie review weeks in which the compiler’s own claims were attacked adversarially — five review lanes hunting data races, deadline holes, pointer misuse, hostile inputs, and debugger lies. Everything they found was fixed at the root before this page was written, and four of the findings were judged important enough to change the language itself.
The focus points
- Projects — one
mica.project, one truth for compiler, language server, and run mode alike. - Scripting —
mica --run tool.mica, plainmica tool.mica, shebang scripts, extension-less commands on PATH. - The breakup — four modules (foundation, spectra, dragon, mica) and a backend SDK, proven by a second, non-Mica frontend.
- The quality initiative — four ruled language changes, two silent-corruption miscompiles found and root-fixed, a category of optimizer bugs made structurally impossible, and every failure taught to name itself.
Projects: one folder, one truth
The project model is a small file with
a large consequence: the compiler, the language server, and the run mode all
read the same mica.project, so the diagnostics you see while typing are the
diagnostics of your build — same encoding, same contracts, same libraries. One
program can consume several separately built libraries, and a script beside
your project imports them with no flags at all. The
tutorial walks the whole road.
Scripting: one file, one command
mica tool.mica # compile and run, argv and exit codes as a shell expects
./tool.mica # with a #!/usr/bin/env mica line and chmod +xThe run mode compiles one file into a per-run scratch directory and executes it — argv, exit codes, stdin, here-documents, and interrupts behave exactly as a shell expects; Ctrl+C and supervisor stops reach your program, and nothing is left behind. The scripting tutorial has every detail.
The breakup: four modules and a backend SDK
The compiler is now four Go modules — foundation, spectra (the intermediate
language), dragon (the backend), and mica (the frontend) — and the three lower
modules never import the frontend. That is not an internal nicety: the backend
has its own documented entry (dragon.Build), and the proof is a second frontend —
Wirth’s PL/0 compiles to native executables through the same backend, checked
byte-for-byte against its own oracle. The port ships as a fully commented
reading copy in the tutorial repository’s
sdk, with a guided reading
order — and with the honest caveat that the Dragon and Spectra APIs are a
preview and may change. The backend also gained an executable
validity contract: dragon.Validate answers findings with rule ids and repair
sentences, and Build refuses invalid input with the whole repair list.
The breakup tells the full story.
Alongside it, the test harness that gates every Mica release moved into its own repository, Apache-2.0 licensed.
The review weeks: the compiler attacked its own claims
Five adversarial lanes ran against the release candidate: smuggling data races through every door, defeating scope deadlines, misusing pointers toward silent corruption, feeding the toolchain hostile input, and lying to the debugger. The score that matters: no crash and no internal error anywhere — and every finding fixed at the root, in this release, not in a backlog.
Four language rulings
The deepest findings changed the language, each ruled after studying what industry compilers do:
- An element address of a dynamic array is a lend, never a binding. A
dynamic array’s backing moves when it grows; a stored pointer keeps naming
memory the array has left behind, and writes through it stop meeting writes
through the element while both answers still look plausible.
Inc(address a[i])stays exactly as it reads;view := address a[i]is refused, as is lending an element address to a call that grows that same array. - A constant expression that overflows its own operand type is refused on
every tier.
-5 * Secondused to wrap silently — a named constant’s value moved with the optimization tier. Both operands are constants, so the exact value exists at compile time; the message states it, and the width that holds it. - Every integral family may index an array. The narrowing cast at C
boundaries —
buffer[count as int32]— is gone, and its removal is the safety improvement: the cast truncated the index before the always-on bounds guard ever judged it. The whole value is now exactly what the guard judges. - A mark-bound alias may be lent to a callee that provably stores it nowhere. Shared state can finally be handed to a helper by pointer — but only when the compiler proves the pointer outlives nothing; storing it, a suspending callee, or an imported callee each stay refused with their own sentence.
The error report
Every release now publishes its error report — what newly refuses, and why. Refusals are a design surface in Mica, not an afterthought (refusals as design); the new ones follow the house rule that a refusal names the consequence and the remedy, not just the rule. In 6.12.5:
- a stream type cannot be given a name — a stream is one live activation, not a shape; name the element type instead
- a typed file transfer inside a deadline-armed scope earns a warning: the deadline moves on from a parked task, but blocking file stdio is not cancellable — the compiler now says so at compile time
- user-written star forms (
%.*ls,%*lld) in C format strings are refused — they used to print garbage with exit 0 - a source file that is not honest UTF-8 refuses byte-precisely instead of being silently repaired or truncated
--stdlibnaming an archive that contradicts--memory-classis refused with the consequence spelled out — it used to silently link a fixed-arena program with no arena at all- every hostile command line answers one sentence plus a hint, and the error report is bounded — a 100 MB garbage input refuses in about a second instead of timing out
Silent corruption, found and killed
The review found two miscompiles that answered wrong values with exit 0 — the
worst class of bug a compiler can have. A function forwarding into its own
result inside a for x in loop could answer another build’s values, or zeroes
in a fixed arena; and a retry loop in the blocking-IO layer read a stale error
code, discarding successful reads. Both are root-fixed and pinned by
deterministic regression tests. Beyond the instances, one category is now
structurally impossible: the backend computes which stack slots have their
address taken, and every optimization that would drop a store consults that
fact — the two optimizations once deleted for this bug class are back,
soundly.
Failures name themselves
A failing program should read like a diagnosis, not a shrug. In 6.12.5: out-of-memory traps name the failing line on every road, including inside library helpers; stack exhaustion prints a named report instead of dying silently; when several tasks trap at once under multicore, exactly one complete banner is printed; and a scope deadline now fires whatever the task tree is doing — compute-bound children can no longer defeat it.
Small memory, long loops
Fixed-arena programs — the deployment class for
running without a heap —
now reclaim each loop iteration’s transients: a condition like
while Length(Step(r)) > 0 runs in constant region space, where the same loop
used to exhaust a 64 KB arena after a few hundred rounds. Record reassignment
in a loop reuses its backing, and growing reassignment extends in place.
And at the checked tier, tensor arithmetic now traps integer overflow exactly as scalar arithmetic does — at the first offending element, with the statement’s source position. Debug and release wrap, unchanged and unpaid.
A changelog that ships with the package
Starting with this release, the package’s Debian changelog carries the
release’s real story instead of a version stamp — apt changelog mica answers
with the highlights above — and the build refuses to package a release whose
story has not been written.
Getting 6.12.5
6.12.5 passed the full release gate before tagging: the complete corpus through the compiler (11,188/0 runs on the widest gate), the same corpus through the legacy lowering as a differential oracle (11,093/0), and the corpus under Go’s race detector (11,093/0) — on both architectures, plus the multicore gate (287/0).
Not in this release, so you do not go looking: the networking library is 6.13, and the Mica-native numeric stack is release 7.
Install it natively or as a container — and if the review weeks’ philosophy resonates, we would genuinely like to hear from you: info@mica-dev.com.