Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Memory Management

rex-engine stores evaluator data in a private moving heap. The builder creates Heap, then ownership moves through the single-use pipeline:

Builder -> Compiler -> Evaluator

There is no public heap capability and no heap mutex. At any moment one task owns the complete runtime and has exclusive access to allocation and collection. The owning evaluator future may migrate between executor threads, but two threads cannot access the same heap concurrently.

Representation boundaries

The runtime deliberately separates three concerns:

RepresentationPurposeVisibility
ValueOwned semantic data passed to and from hostsPublic; no heap references
RootedPtrStable token for evaluator/compiler stateCrate-private
InternalPtrMoving edge stored inside heap cellsMemory implementation only

Value is a host interchange model, not the evaluator’s internal value or a future JIT ABI. Its collection variants recursively own other Values. Evaluator-only states such as closures, native and overloaded functions, and uninitialized cells cannot be exported.

Value::List represents ordinary Rex lists. Value::Bytes is the canonical host representation of Rex List U8. Type-directed conversion returns Bytes for every physical list layout, including empty lists, cons chains, data-backed slices, binary-data slices, and mixed cons/slice values.

Runtime roots

An InternalPtr contains a heap identity, slot, and collection generation. It is valid only until an allocation that may collect unless it is already a traced cell edge or represented by a runtime root.

A RootedPtr is a stable generational token into the runtime root table. It does not expose a cell address. Collection rewrites the table entry when an object moves, allowing evaluator frames, environments, scheduler work, module values, and compiler state to retain the token. RootScope provides exclusive synchronous access for inspecting and allocating values and explicitly roots temporary results.

Machine-owned roots remain registered while they are present in evaluator or compiler state. At a collection safepoint the runtime traverses the current frames, environments, and scheduler state, removes stale root tokens, and then runs the collector. This traversal occurs only when collection is required; the evaluator does not persist and reconstruct its complete state around each ready work item.

Copying collection

Collection begins from the live runtime-root table. It traces private InternalPtr edges through reachable cells, copies reachable cells, and rewrites cell edges and runtime-root entries to the new generation. Debug builds verify that copied slots have the current generation, contain valid children, and remain reachable.

The object allocation path also protects child edges already placed in a pending cell. It temporarily registers those edges as runtime roots, collects, rewrites the pending cell from the relocated roots, releases the temporary entries on both success and failure, and only then installs the cell.

Extreme GC stress is available through a builder test setting. It collects at every evaluator safepoint and randomizes destinations so tests exercise root relocation rather than relying on stable slot numbers.

Host calls

Public host functions never receive Heap, RootScope, RootedPtr, or a capability that can obtain them. Public Context retains only host state and type-system metadata, not runtime registries or their root tokens. The call boundary is:

private heap --type-directed copy--> owned Value
owned Value --host work/future-----> owned Value
owned Value --validate and import--> private heap

Arguments are converted before a host callback is invoked. A synchronous dynamic callback owns Vec<Value>; an async callback future owns the same heap-independent data plus host state. When the result completes, the evaluator validates it against the instantiated Rex result type and imports it while it again has exclusive runtime access. Cancellation drops the owning runtime and host futures without acquiring a lock or unregistering roots from another thread.

The conversion kernel validates scalar and composite shapes, tuple and ADT arity, record fields, constructor identity, and concrete element types. It uses an explicit work stack for recursive data, so deeply nested ADTs do not consume the Rust call stack. List traversal is specialized and iterative.

Host-provided constants are imported once during module installation and retained as internal rooted constants. Looking one up does not schedule a host call or reconvert an owned Value.

Internal intrinsics

Prelude operations and generated constructors are not host functions. They use a crate-private intrinsic ABI that receives an active root scope and may inspect or allocate internal values directly. Higher-order intrinsics may return evaluator-managed work that applies Rex closures. This distinction is important: only genuinely external host work pays for conversion to and from Value.

Future runtimes

The current heap uses Rust cells, but no public API exposes their layout. Future LLVM-generated sequential regions can use private tagged values and a custom binary heap while preserving the same host boundary. Compiled code that may allocate will need to report live references at safepoints through stack maps, statepoints, or a compatible shadow-root mechanism. Value will remain the host interchange tree rather than becoming the generated-code calling convention.

No unsafe code is used by the current memory model.