Files
cell/docs/spec/stone.md
2026-02-20 21:54:19 -06:00

4.7 KiB

title, description
title description
Stone Memory Immutable arena allocation

Overview

Stone memory is a separate allocation arena for immutable values. Objects in stone memory are permanent — they are never moved, never freed, and never touched by the garbage collector.

The stone() function in ƿit petrifies a value, deeply freezing it and all its descendants. Stoned objects have the S bit set in their object header.

The Stone Arena

Stone memory uses bump allocation from a contiguous arena:

stone_base ──────── stone_free ──────── stone_end
[allocated objects] [free space        ]

Allocation advances stone_free forward. When the arena is exhausted, overflow pages are allocated via the system allocator and linked together:

struct StonePage {
  struct StonePage *next;
  size_t size;
  uint8_t data[];
};

The S Bit

Bit 3 of the object header is the stone flag. When set:

  • The object is immutable — writes disrupt
  • The object is excluded from GC — the collector skips it entirely
  • For text objects, the length field caches the hash instead of the character count (since the text cannot change, the hash is computed once and reused)

What Gets Stoned

When stone(value) is called:

  1. If the value is already stone, return immediately
  2. Recursively walk all nested values (array elements, record fields, etc.)
  3. Copy each mutable object into the stone arena
  4. Set the S bit on each copied object
  5. Return the stoned value

The operation is deep — an entire object graph becomes permanently immutable.

Text Interning

The stone arena maintains a hash table for text interning. When a text value is stoned, it is looked up in the intern table. If an identical string already exists in stone memory, the existing one is reused. This deduplicates strings and makes equality comparison O(1) for stoned text.

The hash is computed with fash64 over the packed UTF-32 words.

Usage Patterns

Module Return Values

Every module's return value is automatically stoned:

// config.cm
return {
  debug: true,
  timeout: 30
}
// The returned object is stone — shared safely between actors

Message Passing

Messages between actors are stoned before delivery, ensuring actors never share mutable state.

Constants

Literal objects and arrays that can be determined at compile time may be allocated directly in stone memory.

Mutable Text Concatenation

String concatenation in a loop (s = s + "x") is optimized to O(n) amortized by leaving concat results unstoned with over-allocated capacity. On the next concatenation, if the destination text is mutable (S bit clear) and has enough room, the VM appends in-place with zero allocation.

How It Works

When the VM executes concat dest, dest, src (same destination and left operand — a self-assign pattern):

  1. Inline fast path: If dest holds a heap text, is not stoned, and length + src_length <= capacity — append characters in place, update length, done. No allocation, no GC possible.

  2. Growth path (JS_ConcatStringGrow): Allocate a new text with capacity = max(new_length * 2, 16), copy both operands, and return the result without stoning it. The 2x growth factor means a loop of N concatenations does O(log N) allocations totaling O(N) character copies.

  3. Exact-fit path (JS_ConcatString): When dest != left (not self-assign), the existing exact-fit stoned path is used. This is the normal case for expressions like var c = a + b.

Safety Invariant

An unstoned heap text is uniquely referenced by exactly one slot. This is enforced by the stone_text mcode instruction, which the streamline optimizer inserts before any instruction that would create a second reference to the value (move, store, push, setarg, put). Two VM-level guards cover cases where the compiler cannot prove the type: get (closure reads) and return (inter-frame returns).

Why Over-Allocation Is GC-Safe

  • The copying collector copies based on cap56 (the object header's capacity field), not length. Over-allocated capacity survives GC.
  • js_alloc_string zero-fills the packed data region, so padding beyond length is always clean.
  • String comparisons, hashing, and interning all use length, not cap56. Extra capacity is invisible to string operations.

Relationship to GC

The Cheney copying collector only operates on the mutable heap. During collection, when the collector encounters a pointer to stone memory (S bit set), it skips it — stone objects are roots that never move. This means stone memory acts as a permanent root set with zero GC overhead.