Files
cell/docs/spec/gc.md
2026-02-08 08:25:48 -06:00

3.6 KiB

title, description
title description
Garbage Collection Cheney copying collector

Overview

ƿit uses a Cheney copying collector for automatic memory management. Each actor has its own independent heap — actors never share mutable memory, so garbage collection is per-actor with no global pauses.

Algorithm

The Cheney algorithm is a two-space copying collector:

  1. Allocate new space — a fresh memory block for the new heap
  2. Copy roots — copy all live root objects from old space to new space
  3. Scan — walk the new space, updating all internal references
  4. Free old space — the entire old heap is freed at once

Copying and Forwarding

When an object is copied from old space to new space:

  1. The object's data is copied to the next free position in new space
  2. The old object's header is overwritten with a forwarding pointer (OBJ_FORWARD) containing the new address
  3. Future references to the old address find the forwarding pointer and follow it to the new location
Old space:                 New space:
┌──────────────┐          ┌──────────────┐
│ OBJ_FORWARD ─┼────────> │ copied object│
│ (new addr)   │          │              │
└──────────────┘          └──────────────┘

Scan Phase

After roots are copied, the collector scans new space linearly. For each object, it examines every JSValue field:

  • If the field points to old space, copy the referenced object (or follow its forwarding pointer if already copied)
  • If the field points to stone memory, skip it (stone objects are permanent)
  • If the field is an immediate value (integer, boolean, null, immediate string), skip it

The scan continues until the scan pointer catches up with the allocation pointer — at that point, all live objects have been found and copied.

Roots

The collector traces from these root sources:

  • Global object — all global variables
  • Class prototypes — built-in type prototypes
  • Exception — the current exception value
  • Value stack — all values on the operand stack
  • Frame stack — all stack frames (bytecode and register VM)
  • GC reference stack — manually registered roots (via JS_PUSH_VALUE / JS_POP_VALUE)
  • Parser constant pool — during compilation, constants being built

Per-Actor Heaps

Each actor maintains its own heap with independent collection:

  • No stop-the-world pauses across actors
  • No synchronization between collectors
  • Each actor's GC runs at the end of a turn (between message deliveries)
  • Heap sizes adapt independently based on each actor's allocation patterns

Heap Growth

The collector uses a buddy allocator for heap blocks. After each collection, if less than 20% of the heap was recovered, the next block size is doubled. The new space size is: max(live_estimate + alloc_size, next_block_size).

All allocations within a heap block use bump allocation (advance a pointer), which is extremely fast.

Alignment

All objects are aligned to 8-byte boundaries. Object sizes are rounded up to ensure this alignment, which guarantees that the low 3 bits of any heap pointer are always zero — available for JSValue tag bits.

Interaction with Stone Memory

Stone memory objects (S bit set) are never copied by the collector. When the scanner encounters a pointer to stone memory, it leaves it unchanged. This means:

  • Stone objects are effectively permanent GC roots
  • No overhead for tracing through immutable object graphs
  • Module return values and interned strings impose zero GC cost