Mica 7.6.0 is the release where Mica’s mathematics comes home to the idea Mica started from. Mica was made to reuse the whole C world with ease: a C library is one import away, its functions are called as a C compiler calls them, and nothing C already does well is built a second time. In 7.6 the mathematics follows the same rule. On the processor, Mica computes with the C library. On the GPU, it computes with NVIDIA’s own compiler, NVIDIA’s own mathematics and cuBLAS. And every number a program writes means what C says it means, at every width from int8 to float64, with float16 and bfloat16 beside them.

This is the one change of direction in this release, and it is a return to the principle that made Mica what it is. The measure is plain: where Mica’s arithmetic and C’s meet, GCC and Clang decide what the right answer is, and the release’s gates compare every operation against them.

7.6 is also the first public release since 7.5.0, so it brings everything built in between: sum types, tasks that run in another process, a program that describes itself and answers a protocol while it runs, routines that promise never to touch the heap, one call stack across Mica and C, and the editor extension 5.0.

The release at a glance

SubjectWhat changed
Mathematics on the C libraryExp, Ln and Tanh are libm’s functions in the operand’s own width; float32 and float64 arithmetic gives GCC’s and Clang’s result to the bit
Numbers mean what C saysinteger and real constants exact until their use, rounded once where they are used, C’s rule for operations over real constants
float16 and bfloat16stored, converted and computed with one rounding per operation, and passed to C as the platform’s calling convention lays them down
The GPU on NVIDIA’s librariesCUDA C compiled on the machine by NVRTC, NVIDIA’s mathematics on the device, matrix products on cuBLAS, the driver found when the program starts
Sum typesenumeration cases with named field sets, built, matched and checked for coverage
Tasks on processesschedule T(args) on process runs a task in a worker process of the same program
A program that describes itselfmica --inspect, +mica info -mica, and the runtime protocol
Heapless routinesheapless promises a routine never touches the heap, checked through every call
Memorystorage reused in loops and through pointers, a fixed arena that reuses what it is given back whatever its size, a checked build that traps on a leak, an analysis that follows a cell into a container and out again
Debugging across Mica and Cone call stack, stepping in both directions, on both architectures
The editorextension 5.0: every stage of a compilation made visible, and a language server that answers from the text on screen

Mathematics on the C library

On the processor, Exp, Ln and Tanh are calls of the C library, as a C compiler emits them: expf, logf and tanhf for float32, and exp, log and tanh for float64, each in the operand’s own width. Sqrt and FusedMultiplyAdd stay one instruction each, which is what a C compiler makes of sqrt and fma. A Mica program and a C program that compute the same formula on the same machine get the same bits.

The same holds for the arithmetic itself. Every float32 and float64 operation, computed at run time or folded by the compiler, gives the result GCC and Clang give at -O2, on both architectures and at every optimization tier, and cases in the release gate hold every one of them to it. A float32 literal is read once at float32’s width, as C’s f suffix reads it.

The language’s mathematics lives in one namespace, the math unit. A math word imported from somewhere else is pointed there by name:

imp Sqrt : std;

The compiler answers: 'Sqrt' is not exported by 'std', but 'math' exports it: write 'imp Sqrt : math;'.

The one numerics promise is C’s own: the same program on the same machine gives the same result, run after run.

Numbers mean what C says

A constant in Mica keeps its exact value until its use decides its type. An integer constant is exact, so u8 := u8 + 300 is judged once, where the addition meets its operand, and WriteLn("%d", i32 + 3000000000) prints the value you wrote. A real constant is exact too, and rounded once where it is used. An operation over real constants rounds per operation as C does, so 0.1 + 0.2 is 0.30000000000000004, the answer every C programmer already knows.

A constant beside a float operand takes that operand’s type, again as in C: f32 * 0.1 multiplies in float32. And a conversion that can lose an integer is written where it happens:

f := i64 as float64;

int64 and uint64 hold integers a float64 cannot represent exactly, so the conversion is spelled out, as Go, Rust, Ada and Zig spell it. The refusal of an unwritten one says what to write.

float16 and bfloat16 join the family. Both are stored, converted and computed with one rounding per operation, and they cross to C as the platform’s calling convention lays them down: in the low sixteen bits of a vector register, and in records as the convention classifies them, which on arm64 is AAPCS64, as Clang passes them. GCC on arm64 passes a record of bfloat16 values in an integer register instead, and Known limitations names the roads that work. More than seven thousand computed rows compare every half-precision operation with what GCC and Clang compute.

The GPU on NVIDIA’s libraries

Code placed on the GPU is rendered as CUDA C and compiled on the machine by NVRTC, NVIDIA’s runtime compiler, for the exact card the program runs on. The mathematics on the device is NVIDIA’s own. Matrix products run on cuBLAS, with a stream-ordered memory pool beneath them. The program opens NVIDIA’s driver, NVRTC and cuBLAS when it first touches a device, so a device program links without the CUDA libraries, and one binary runs on every NVIDIA card from compute capability 7.5 up.

The device runs are part of the compiler’s own test corpus: each one is a case that runs on a GPU host and states its reason where no GPU answers. The GPU, without kernels is a first device program in one page, and Train your own GPT trains a model on the card.

Sum types

An enumeration’s cases carry named field sets. A value is built with its fields, a case arm binds a read-only view of them, is tests one case, and the compiler checks that every case is covered. A field set holds plain values, strings, and dynamic arrays of those, each copied and given back by the case the tag names. The walk is Sum types.

Tasks, processes and the runtime

schedule T(args) on process runs a task in a worker process of the same program, from a block below the root, from a library, and from an importer of a library’s task, with arguments of any size. A concurrent block that places a task on a process answers a lost worker in on lost, and it carries one on fail section for each error domain its tasks can fail.

Every linked program, archive and shared object carries its manifest: mica --inspect prints which compiler, platform and string encoding built it without starting it, and a running program prints the same with +mica info -mica, beside the configuration its start resolved. With +mica rtp=listen -mica, a program opens an owner-only socket and answers the runtime protocol: its manifest, its configuration, its task tree, suspend and continue, cancelling a task’s scope, and its memory readings. The editor’s runtime view draws all of it live.

The process unit starts other programs with posix_spawn and close-on-exec pipes, reads and writes the environment, and supervises what it started; the flags unit declares a command line before it reads it. One timer serves every sleeping task.

Heapless routines

The word heapless promises that a routine never touches the heap, and the compiler checks the promise through its calls, its imports and the callbacks it hands on. A signal handler slot takes only such a routine, and refuses one that waits or locks. It is the language’s road to code that runs where no allocator exists; Mica on bare metal shows it running as the interrupt handler of a small kernel.

Memory

A string or an array stored into a variable, a field or an element reuses the storage it replaces, in place when it fits and from its region’s released cells otherwise, so a loop that stores over the same place runs in constant memory. A value written through a pointer does the same: it lives in the scope that holds the storage, for as long as that storage lives, so a procedure that writes through a pointer parameter and a map whose string values are rewritten under the same keys both run in constant memory.

Under a fixed arena, released storage is reused whatever its size: what comes back is joined to its free neighbours, and what is handed out is split from a larger run. A program whose values change length — a table rebuilt at a new size every round — runs in a budget its live set fits.

A Mica program does not leak unnoticed: what the compiler cannot prove, the checked build traps. At a checked program’s end the runtime walks every cell never released and stops with their sizes and a count. The analysis follows a cell into a container’s owning slot and out again, so releasing it a second time, reading it after its container released it, and placing one cell in two owning slots are refused at compile time, each by its rule.

Debugging across Mica and C

A backtrace, a frame walk, step and finish cross between Mica and C at any depth, on both architectures, with each frame’s variables in its own language. Strings print as their text in gdb, and a debugger’s first stop in a routine is its first statement. The editor turns this into one call stack: set breakpoints in both languages and step between them.

The editor extension 5.0

The Mica extension for VS Code reaches 5.0 with this release. It opens the compiler to you: every stage of a compilation becomes a listing you can read, click through and draw, from the tokens to the placement of every value in registers and frame. The language server answers from the text on your screen, saved or not. Its own post tells the whole story.

A Mica program stopped at a breakpoint: locals and registers on the left, the call stack across Mica and C, and the debug console naming every breakpoint it passed

The picture is one moment of this release standing together: a tensor and a tape in the source, a generic routine compiled at the type it is called with, a task pool of four carriers, locals and registers read live, and a console whose breakpoints were met in Mica and in the C library beneath it.

Changes to note

  • int64 and uint64 become float64 only when written with as float64.
  • A subrange whose bounds lie in 2^31 .. 2^32 - 1 takes an int64 base, so its storage is eight bytes and an unsigned print specifier over it becomes a signed one.
  • f64 := f32 * 0.1 multiplies in float32 and widens the product, as C does.
  • --stack-size is the one flag for the stack; the stack it states is the frame ceiling.
  • On AMD64, the baseline is an x86-64-v3 processor, with AVX2 and FMA3, from 2013 or later.
  • A leading sign belongs to the whole selected path: -b[1] negates the element, as in Pascal, C and Go.

apt changelog mica lists every item, with the diagnostics that are new in this release.

Getting it

The Debian packages for AMD64 and AArch64, the container image and the editor extension 5.0 are published with this release. The install page has every road.