Every live Unit instance owns one persistent arena in its link group’s WebAssembly linear memory. The arena holds that instance’s values, task tree, continuation frames, and scheduler nodes. It survives ordinary invocation completion and disappears when the instance is evicted.
An arena is therefore an isolation and reclamation boundary:
- values from different instances do not share arena offsets;
- an instance can be migrated by copying its arena at a safepoint;
- eviction releases all of the instance’s in-arena storage together;
- corruption or forced termination can poison one instance without requiring reconstruction of individual allocations.
Physical layout
One raw backing allocation contains an allocator header followed by the arena body:
lower address
┌──────────────────────────────────────┐
│ FlatBuilderHeader, 304 bytes │
├──────────────────────────────────────┤ ← arena body base
│ RuntimeArena header, 16 bytes │
├──────────────────────────────────────┤
│ flex blocks │
│ values, frames, TCBs, queue nodes… │
├──────────────────────────────────────┤
│ unused body capacity │
└──────────────────────────────────────┘
higher addressThe 304-byte FlatBuilderHeader and 16-byte RuntimeArena header are separate
structures. Older prose that calls the allocator header 296 bytes is stale; the
generated layout and compile-time assertion make its current size 304 bytes
with 8-byte alignment.
Builder header
| Offset | Field | Meaning |
|---|---|---|
0 |
max_bytes: u32 |
maximum permitted body size |
4 |
alloc_mode: u8 |
bump or TLSF mode |
8 |
potential_garbage_count: u32 |
deferred block count |
12 |
potential_garbage_bytes: u32 |
deferred byte estimate |
16 |
pin_count: u32 |
pins on the active generation |
20 |
gen_id: u32 |
relocation generation |
24 |
alloc_count: u32 |
allocation counter |
28 |
free_count: u32 |
free counter |
32 |
TLSF state | 272-byte allocator state |
Padding is part of the generated layout even where it is not shown as a named field.
Arena header
The arena body starts with the standard 16-byte flat header:
| Offset | Field | Meaning |
|---|---|---|
0 |
total_size: u32 |
used body bytes |
4 |
base_size: u32 |
fixed layout size |
8 |
type_id: u32 |
body type identifier |
12 |
reserved: u32 |
reserved ABI word |
RuntimeArena has no additional fixed fields. Its initial body size is
therefore 16 bytes, and flex allocations begin after this header.
Builders
The guest-side owner uses ContiguousBuilder<RuntimeArena>. It owns the backing
allocation and may request a larger allocation, copy the arena, update its base,
and retire the old allocation.
Host code uses BorrowedBuilder when operating over the same bytes. It follows
the same allocation and reclamation algorithms but does not own WebAssembly
memory and cannot grow it itself. When capacity is insufficient, control must
return to the owner or runtime slow path for out-of-band growth.
This division avoids maintaining a second allocator model in the host. It does not make a borrowed builder safe to retain across relocation.
Allocation mode
A fresh arena starts in bump mode. Allocation advances total_size, which
makes the common append-only path small and predictable.
A definite free has three possible outcomes:
- freeing the tail rewinds
total_size; - freeing a non-tail block in a body of at least 1 KiB promotes the arena to TLSF and rebuilds free lists;
- in a smaller body, the allocator records potential garbage and postpones the more expensive rebuild.
Once promoted, TLSF supplies reusable blocks and coalesces adjacent free space. Promotion is one-way for the arena’s remaining lifetime.
See TLSF for block metadata and algorithms.
Initial size
The default initial runtime arena body capacity is 256 KiB. It can be set explicitly, or selected adaptively. When an instance is evicted, the Store remembers that Unit’s observed peak. A later instance starts with at least the configured floor and may reserve roughly 125 percent of the remembered peak.
These values are tuning policy, not portable language guarantees. Code must handle allocation failure regardless of the initial capacity.
Growth
Growth can occur proactively or in an allocation slow path.
The trampoline periodically checks pressure. At its current cadence of every 64 ticks, usage above seven eighths of capacity first offers cycle collection a chance to reclaim storage and then attempts to double the body capacity.
An allocation failure requests enough space to satisfy the pending block plus headroom. The current target is approximately:
max(2 × current capacity, current capacity + requested bytes + 4 KiB)All arithmetic is checked against the u32 arena representation and configured
limits. After a successful relocation the runtime:
- allocates a larger raw range;
- copies the complete builder header and body;
- increments
gen_id; - publishes the new body base and capacity to the active link group;
- frees or retires the old generation;
- retries the interrupted allocation.
Because every stored relationship uses relative offsets, the copied object graph does not need pointer rewriting.
Generations
A generation identifies one physical placement of an arena. Relocation changes the generation even though all arena-relative offsets retain their meanings.
Asynchronous host operations use two strategies:
- direct operations retain offsets and derive an absolute address from the current base on each poll;
- operations that must capture a raw address pin the exact arena generation before suspension.
If a pinned arena relocates, the runtime puts the old backing allocation on a retired list instead of freeing it. Completion unpins the exact captured base, after which unpinned retired generations can be reaped. A pin does not prevent the active arena from moving; it prevents the pinned old storage from becoming invalid prematurely.
Runtimes that lack the pin, unpin, and retired-generation ABI gate growth until no I/O operation is active. The current ABI supports mid-I/O growth through generation pins.
Safepoints
Snapshot and migration require a coherent arena. The runtime reaches a safepoint where generated code is not mutating the arena, then requires no unresolved pins on the captured generation. Host resources are handled separately because an arena copy cannot serialize an operating-system socket, file descriptor, or database connection.
Restore copies the bytes into a new backing allocation, publishes a new base, and advances the generation. Supported host resources are detached and reattached through the migration protocol described in Deployment.
Failure rules
Allocation failure is recoverable only while the runtime can still trust the arena and scheduler state. A forcefully terminated invocation can interrupt generated ownership operations, so its Unit instance is poisoned. The runtime then evicts the complete arena; it does not walk possibly inconsistent reference counts or promise to run every user destructor.
Optional arena verification checks the physical block walk, free lists, size
classes, and counters. It is enabled by the arena-verify build feature or
ATOLL_ARENA_VERIFY diagnostics, and is intentionally disabled on normal hot
paths.
Invariants
total_sizenever exceeds body capacity.- All nonzero live offsets resolve within the current body.
- Relocation preserves the complete header and body byte sequence.
gen_idchanges whenever the physical placement changes.- A retired generation is not freed while pinned.
- Only the owning Unit instance mutates its arena on its Store reactor.
- Arena teardown is not a substitute for ordinary
Dropsemantics during successful execution.