Atoll combines compiler-directed ownership with a compact per-block reference count. Ordinary acyclic values are reclaimed deterministically. Types that can participate in cycles are additionally registered for precise tracing at scheduler safepoints.
This design is single-mutator within one Unit instance. Reference counts are
ordinary u32 words, not atomics, because the Store reactor is the sole arena
mutator. Moving a value to another instance requires an explicit deep transfer;
sharing a bare arena handle between reactors is invalid.
Block ownership
Every managed block reserves the word at header offset +4 for its reference
count while the block is live. A new block starts at 1. The value
u32::MAX marks immortal storage, such as a compiler-emitted literal, which
clone and drop operations leave unchanged.
When a decrement reaches zero, generated type information selects the correct drop path:
- run user-visible
Dropbehavior where the language requires it; - walk nested reference-containing fields;
- decrement or destroy those children;
- return the physical block to the arena allocator.
The allocator does not infer a value’s shape from bytes. Compiler-generated drop walkers provide the type-directed traversal.
Ownership modes
MIR classifies references so code generation can omit unnecessary operations without changing semantics:
| Mode | Runtime behavior |
|---|---|
Static |
emit ordinary clone and drop operations |
Confined |
retain deterministic ownership inside a proven confinement |
Linear |
move exactly once; omit reference-count traffic |
PinElided |
omit traffic under the compiler’s pin/lifetime proof |
RawRef |
non-owning reference; never changes the count |
These names describe the current compiler model. Older GcOnly terminology is
retired even if a stale ABI comment still contains it. Internal modes such as
specialized inherited extracts refine lowering decisions but do not define new
source-level ownership kinds.
Incorrect classification can cause a leak or premature reuse, so the proof is a compiler correctness obligation rather than a runtime hint.
Clone
Cloning a managed handle increments its block count unless it is null or immortal. Cloning an aggregate follows generated clone logic for the fields that own references. Plain scalar and raw-reference fields require no count change.
Because counts are non-atomic, clone must execute on the instance’s Store reactor. Host operations retain values through explicit runtime roots or pinned buffer protocols rather than modifying arena reference counts concurrently.
Drop
Dropping decrements the count. A nonzero result ends the operation. A zero result enters generated destruction and eventually performs a definite free.
Destructor execution is a semantic event, not merely allocator bookkeeping. A normal scope exit and successful Unit-instance cleanup follow generated drop rules. Forced termination is different: if execution was interrupted while ownership state may be inconsistent, the runtime poisons and evicts the arena without promising to invoke every destructor.
Reuse
Perceus-style reuse fuses destruction with a following allocation of compatible storage.
- If the source count is
1, generated code can reuse the same block and initialize its new value in place. - If the source is shared, code decrements the old reference and obtains a new block.
Reuse is observable only through performance and resource behavior; Atoll does not expose stable object addresses. The compiler must still run any required field drops before overwriting an owned payload.
Cycles
Reference counting alone cannot reclaim a strongly connected component with no external references. The compiler identifies types whose layouts can form cycles. Only blocks of those types enter the arena’s traced set; provably acyclic types pay no tracing-table overhead.
Collection is precise:
- roots are known runtime structures, not conservative machine words;
- generated mark walkers understand each traced type;
- allocator mark bits identify reached blocks;
- typed cycle-drop dispatch handles unreachable values before hard deallocation.
Roots
At a scheduler safepoint the collector marks from every live runtime root, including:
- continuation frame chains for every task control block;
- pending I/O resume records;
- ready-resume and ready-loop queues;
- awaiter chains and select groups;
- stream consumer and producer waiters;
- yield resumes and other scheduler-owned continuation handles.
Mark walkers recursively follow only fields described as owning references by the compiled layout.
The collector is deliberately fail-safe. If a root chain is truncated, an offset is out of bounds, or required generated walker tables are absent, it skips sweeping that cycle. Leaking until a later safe collection is preferable to freeing reachable memory.
Collection
A collection cycle:
- stops arena mutation at a trampoline safepoint;
- clears or advances mark state for cycle-capable blocks;
- marks from all scheduler and generated roots;
- validates each traced-set entry;
- dispatches typed cycle cleanup for unmarked entries;
- returns their blocks directly to the allocator;
- repairs traced-set and allocator accounting.
Triggers include explicit runtime collection, allocation thresholds, rate or adaptive policies, and memory pressure before growth. Collection is not a background thread and cannot race the Unit instance’s mutator.
Destruction
There are three distinct cleanup paths:
| Event | Behavior |
|---|---|
| ordinary drop | generated decrement, typed destruction, definite free |
| cycle collection | precise mark, typed cycle cleanup, hard deallocation |
| poisoned eviction | release entire arena backing allocation |
The third path is fault containment. It must not be described as equivalent to running all user destructors.
Invariants
- a live managed block has a positive or immortal count;
- free-list metadata is interpreted only after the block is marked free;
- only the owning reactor mutates counts or allocator state;
- non-owning
RawRefvalues cannot outlive their compiler-proven referent; - every cycle-capable layout has a matching mark and cycle-drop walker;
- collection runs only while generated code is at a safe suspension boundary;
- failed root validation suppresses sweeping.