Mica 7.7.1 lets a program write its own functions over tracked values. A
function that takes tracked values and answers one records its body when the
tape window calls it, so a network’s forward pass reads as its formula,
h₁ := σ(W₁ × a₀), with σ written once. A model’s blocks go to such functions
as records, and a library’s functions record exactly the same way.
The recorded vocabulary gains the logistic Sigmoid, the elementwise product
a ⊙ b, and matrix forms of Exp, Ln and Sqrt. A GPU program carries only
the kernels it uses, compressed, and with fp_contract both processors fuse
the same products at every tier.
The release at a glance
| Subject | What changed |
|---|---|
| Functions of one’s own on the tape | tracked parameters, results and locals, scalars and tensors; records of a model’s blocks; a library’s functions record too |
| The recorded vocabulary | Sigmoid and SigmoidMap; Exp, Ln, Sqrt and Tanh over a tracked scalar; ExpMap, LnMap, SqrtMap; the elementwise product a ⊙ b |
| Device values with an owner | a routine’s, a library’s, a task’s and a generator’s device values go back to the card when their owner ends |
| Smaller programs | a GPU program carries only the kernels it uses, compressed: the GPU hello world from 37.2 MB to 2.5 MB; a hello world half its 7.7.0 size |
| One fused product everywhere | with fp_contract, both processors and every tier fuse the same products |
| On the processor | constant indices cost nothing, 64-bit constants fold, loop counters keep one register, and two wrong results are fixed |
| Installing | one install builds for both architectures, the packages state the C library they need, and the image has a UTF-8 locale and pkg-config |
| The editor | the outline of a program that uses a Map, exact outline ranges, generic types as the program spells them |
Functions of one’s own on the tape
function σ(z : tracked float64) : tracked float64;
begin
σ := 1.0 / (1.0 + Exp(-z));
end;
tape
y := σ(x);
end;
Backward(y);
g := Gradient(x); { σ(x) · (1 − σ(x)): 0.235004 at 0.5 }A function or procedure takes tracked values and answers one. A call inside the
window hands it the history its arguments carry and records its body as if it
were written in the window; outside every window the same function computes
plainly. Parameters may be const, results and locals may be tracked, and the
values may be scalars or tensors of any shape, fixed or data-driven. A function
may call another that records, recursion included, and the whole chain lands on
the one tape.
A model’s block is a record of tracked parameters, and a forward pass takes it as one:
function Forward(const b : Block, input : tracked vector[2] of float64) : tracked vector[2] of float64;
begin
Forward := b.w * input;
end;
tape
for k := 0 to 1 do
h := Forward(blocks[k], h);
l := Sum(h);
end;The call enrolls the block’s tracked members on the tape before the function
reads them, so Gradient(blocks[k].w) after the walk answers for the model’s
own parameters. A constant record, and a constant tracked tensor, cross by
address where a plain aggregate of their size would.
A library’s functions record the same way. The library keeps a recording copy of every function it exports that works on tracked values, and its contract names the copy beside the function, so a program’s window that calls the function records its body. The course page and Model depth teach the feature, and the contract reference states what a library’s contract carries.
The recorded vocabulary
Sigmoid and SigmoidMap join the math unit and record on the processor and on
the card. Exp, Ln, Sqrt and Tanh record over a tracked scalar, and
ExpMap, LnMap and SqrtMap apply them to every cell of a matrix. The
elementwise product is written a ⊙ b over vectors, matrices and every tensor
the elementwise sum takes, with no import:
tape
gate := SigmoidMap(z) ⊙ h;
loss := Sum(gate);
end;Device values with an owner
A device value goes back to the card when what owns it ends: a routine’s when
the routine returns, a library’s when the library ends, and a task’s or a
generator’s when its body ends, its cancellation and a generator’s abandonment
included. A loop’s next keeps a device value the loop’s next round still
reads.
Smaller programs
A GPU program carries only the device kernels its code reaches, compressed, and unpacks them when their module first loads. The GPU hello world, stripped at O2, shrinks from 37.2 MB to 2.5 MB, and its start does not change.
A program carries only the runtime it can reach. The editor’s runtime agent is
linked only where rtp= may be used, so a stripped hello world at the shipping
tier is 51,968 bytes on x86-64 and 68,400 on ARM, where 7.7.0’s were 113,928 and
134,480.
One fused product everywhere
--optimize O2,fp_contract lets a multiply followed by an add run as one fused
multiply-add, C’s FP_CONTRACT ON. The compiler now decides which products fuse
once, over the code as written, so x86-64 and AArch64 fuse the same products and
a debug build fuses them too: a program’s digits follow its flags, never its
tier or its processor. A product of two constants never fuses, so folding at
compile time cannot change a digit between tiers, and a checked build keeps
every product apart, since it guards each result.
On the processor
An element read through a constant index, entries[0], reads the element at its
constant offset, and its bounds check compares with the constant. A 64-bit
signed computation over constants folds when its answer is exact. A loop’s
counter keeps one register in its hottest loop, and a small function on AArch64
saves no register it does not need.
Two wrong results are fixed. In a shared library on AArch64, a value kept in a register across a string comparison was wrong on the first call, and at O2 a string’s length read right after copying it into a record field could be garbage.
Installing
One install now builds for both architectures: the packages carry the other
architecture’s libraries, and the devkit installs its binutils and C library.
The packages state the C library they need, 2.38 or newer, so apt refuses an
older system by name instead of installing a compiler that cannot link; Debian
13 and Ubuntu 24.04 or newer are the supported systems. The arm64 package’s
compiler is statically linked, as the amd64 one is. The container image sets a
UTF-8 locale, so the debugger prints Mica’s text as text, and carries
pkg-config, so the sqlite tutorial builds in it.
The editor
The outline of a program that uses a Map lists what the file declares, never
a library template’s copied declarations, and every outline range runs from the
symbol’s name to the last character of its declaration. Hover, the outline and
signature help print a type as the program spells it: Map of (int64, int64),
pointer Cell.
Upgrading
Three things a 7.7.0 program meets differently in 7.7.1:
- A tracked variable may be declared in a routine, which records when the window calls it; a task, a generator and a callback still refuse one.
- Inside a window and in a routine’s body, a record holding tracked members is copied or handed to a routine from a place of the tracked place grammar: a variable or a parameter, field steps, and at most one index run whose indices are literals or plain names. An index that computes is refused (error 5631); name it in an ordinary variable first.
- A debug build with
fp_contractfuses the products the optimizing tiers fuse, so its digits are theirs.
The packages and the commands that install them are on the install page.