There is a class of computer that is not allowed to run out of memory. The engine controller, the brake unit, the medical sensor — firmware standards for these forbid what a desktop program does every millisecond: asking the operating system for memory whose availability, latency and fragmentation nobody can bound. The usual consequence is a dialect: embedded C with allocation banned, static buffers everywhere, and a wall between “code for the device” and “code for everything else”.

Mica’s answer is a build flag:

mica --memory-class hosted          # the default: the OS heap, growing as needed
mica --memory-class fixed-arena=16384   # one 16 KiB block, claimed at start

Same source. Same language, dynamic arrays included. The program does not know which one it got — and this article’s example is built to prove that sentence.

The example is examples/MemoryClasses. Build and run it both ways:

make -C examples/MemoryClasses run
make -C examples/MemoryClasses run MICA_EXTRA_FLAGS="--memory-class fixed-arena=16384"

The output is byte-identical. Everything below was run under both classes, on both architectures.

What the two classes are

Hosted backs allocations with the operating system’s heap and grows as the program needs. It is the right class for servers, tools, and everything that lives where memory is a service.

Fixed-arena claims one block of the stated size at start — 1 MiB if you write no size — and from then on the program allocates from that block and from nothing else. No call reaches the OS allocator again: no allocator jitter in a control loop, no out-of-memory surprise at hour nine, memory budgeted at build time like clock cycles and flash. This is the deployment a car component runs, and Mica’s stated ambition is that the language is fully usable there — not a subset of it.

The flag also selects the matching standard library automatically. You change one build argument, and nothing else.

Why a small arena is livable: lifetimes

A fixed block sounds like a trap: without free, would it not just fill up? The answer is the model this section proves, and it is the heart of the article.

An allocation without a lifetime clause lives exactly as long as the activation that made it:

function Work(seed : int64) : int64;
var
    scratch : Numbers;
    i, sum : int64;
begin
    scratch := new Numbers reserve 64;
    ...
end;

Work allocates 64 fresh elements — about half a kilobyte — every time it is called. It frees nothing. Now call it five thousand times, under a sixteen kilobyte arena:

for round := 1 to 5000 do
    checksum := checksum + Work(round);
  5000 rounds done, checksum 810240000

Five thousand rounds allocated roughly 160 times the arena’s entire size, and fit. When Work returns, its activation’s memory is reclaimed wholesale — not object by object, but as a region, in one step. The arena’s high-water mark is one round’s worth, because every round’s memory is back before the next begins.

This is why the source needs no free and the arena needs no garbage collector: the call structure you already wrote is the lifetime structure. A hosted build reclaims the same way against the OS heap; the arena build merely makes the discipline visible, because there the discipline is the budget.

When a value must outlive its maker

Not everything is scratch. The other end of the model is a lifetime stated at the allocation:

persistent := new Numbers reserve 4 until program;
  persistent: length 4, [0] = 41
  ...
  persistent[0] still 41

until program pins this allocation to the program’s lifetime. It coexisted with five thousand short-lived neighbours in the same small arena and was not disturbed — mixed lifetimes are the normal case, not a special one.

Between “my activation” and “the whole program” there is a richer middle — values handed to callers, owners that carry obligations, dispose — and that is the next article’s whole subject. This one needs only the two ends.

When the arena runs out

A budget can be exceeded, and honesty about the failure mode is what makes a budget usable. Ask a 16 KiB arena for 800 KB:

Mica runtime failure: reason=allocation_failure (8)
Mica runtime context: file=MX.mica, line=9, column=10
Mica runtime source:     a := new Numbers reserve 100000 until program;

A named-line trap, at the allocation that broke the budget — the same shape as an index out of range. What it is not is a NULL return to forget to check, or a corruption three subsystems later. The failure is loud, immediate, and attributed; sizing the arena is engineering against a high-water mark you can measure, not against luck.

What this does not do

The arena does not grow. That is its entire point. fixed-arena=N is a budget between 16 bytes and 1 GiB, and exceeding it is the trap above, every time.

Reclaim is per activation, wholesale. The proof above leaned on returns. A single activation that allocates in a loop without returning accumulates — its region reclaims when it exits, not before. The design consequence: put per-item scratch in the routine that handles the item, which is where it belonged anyway.

The class changes the backing, not the meaning. No conditional compilation, no arena-only dialect: a program that is correct hosted is correct in the arena, or it traps loudly at the allocation the budget refuses.

This article stopped at two lifetimes. Regions between activation and program, ownership that crosses calls, and what the compiler proves about all of it — that is heap, end to end, next.

What the compiler proved

One source ran under two memory deployments and printed the same bytes. Five thousand activations flowed through an arena a fraction of their total allocation, because the reclaim discipline is structural rather than manual. A pinned value survived all of them. And the one program that broke its budget was stopped at the line that broke it, by name.

Next

Heap, end to end — ownership between activation and program: values that outlive their maker on purpose, dispose, and the obligations the compiler tracks so a forgotten one is a compile error rather than a leak. (In preparation; its example is next in the repository.)