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

SubjectWhat changed
Functions of one’s own on the tapetracked parameters, results and locals, scalars and tensors; records of a model’s blocks; a library’s functions record too
The recorded vocabularySigmoid and SigmoidMap; Exp, Ln, Sqrt and Tanh over a tracked scalar; ExpMap, LnMap, SqrtMap; the elementwise product a ⊙ b
Device values with an ownera routine’s, a library’s, a task’s and a generator’s device values go back to the card when their owner ends
Smaller programsa 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 everywherewith fp_contract, both processors and every tier fuse the same products
On the processorconstant indices cost nothing, 64-bit constants fold, loop counters keep one register, and two wrong results are fixed
Installingone 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 editorthe 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_contract fuses the products the optimizing tiers fuse, so its digits are theirs.

The packages and the commands that install them are on the install page.