Skip to content

Ownership

Reference counting, reuse, generated drop logic, and precise cycle collection.

Updated View as Markdown

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:

  1. run user-visible Drop behavior where the language requires it;
  2. walk nested reference-containing fields;
  3. decrement or destroy those children;
  4. 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:

  1. stops arena mutation at a trampoline safepoint;
  2. clears or advances mark state for cycle-capable blocks;
  3. marks from all scheduler and generated roots;
  4. validates each traced-set entry;
  5. dispatches typed cycle cleanup for unmarked entries;
  6. returns their blocks directly to the allocator;
  7. 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 RawRef values 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.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close