Stone Values

Overview

Stone is an immutability property, not a separate actor arena. Stoned heap objects remain on the actor heap and are still moved by the copying collector.

Constants are not a runtime structure. Text, record shapes, and code constants emitted at build time live in Mach images, which are immutable, mapped, and shared by every actor that runs them; the collector recognizes image-backed stone and leaves its placement alone. There is no runtime constant pool and no intern table — nothing accumulates process-wide state as programs run.

The stone() function in ƿit freezes a value by marking the referenced heap object immutable. Immediate values are already stone.

Stone and antestone

A value is either stone — immutable, permanently — or antestone, the state it is in before it is stoned. Those are the two words; “antestone” is what the rest of this documentation means by mutable.

The distinction is physical as well as semantic, because the two states use the field differently. Text carries one length-sized field: while the text is antestone that field holds the count of letters, and once the text is stoned the same field holds its hash. Stoning text is therefore a real conversion, and it is why a stoned text can be compared and used as a key cheaply.

pretext is the internal name for text that is not yet stone. It appears in compiler and runtime discussion below; in ordinary prose such text is antestone.

Immutable Heap Values

When a heap value is stone:

  • The object is immutable — writes disrupt
  • If the object is on the actor heap, it is still copied by GC
  • If the object is image-backed stone, it lives outside the heap and the collector preserves its placement
  • Text can cache derived data such as hashes because its contents cannot change

What Gets Stoned

When stone(value) is called:

  1. If the value is already stone, return immediately
  2. Mark the referenced heap object immutable
  3. Return the stoned value

Nested values keep their own stone state unless they are explicitly stoned elsewhere.

Usage Patterns

Module Return Values

Every module’s return value is automatically stoned. This is not a runtime courtesy: the compiler emits a stone op immediately before the unit main’s return, with nothing between the two. It is ordinary code in the module’s own body, which is why the linker can see it — a stone dominating the single return is the linker’s proof that the export’s member bindings are frozen, and that proof is what lets it finalize a member load into a direct reference.

// config.cm
return {
  debug: true,
  timeout: 30
}
// The returned object is stone inside the actor: a consumer that assigns to
// `config.debug` disrupts.

Because stone is shallow, only the export record’s own member bindings are frozen. Nested state stays mutable, so a module that memoizes into a nested record keeps working:

var cache = {}
function lookup(k) { … cache[k] = v … }   // fine: `cache` is not frozen
return {lookup}                            // the export record is

What the freeze forbids is changing the export record itself — a module adding a member to its own export after returning it, or a consumer assigning to an import.

The freeze is skipped when the returned value has no mutable members to freeze (a number, a logical, null): stone is identity on those, and emitting it would cost the range analysis a proof for nothing.

Message Passing

Messages between actors are copied on send (encoded as wota), so each actor holds its own copy. Only stone values may be sent; the copy is what isolates.

Mutable Text Concatenation

String concatenation in a loop (s = s + "x") is optimized to O(n) amortized by lowering text + to explicit mutable-text mcode. A non-stoned heap text is tracked as pretext; script-visible text must be stone.

How It Works

The compiler and streamline lowering reduce text concat to:

  1. length both operands

  2. add the lengths for capacity

  3. pretext dest, capacity

  4. append dest, left

  5. append dest, right

If the destination is already the left operand and is known to be pretext, the compiler emits only append dest, right. That is the in-place self-append fast path.

The official concat dest, left, right mcode remains valid as early IR, but it is not a VM primitive. It lowers to the same pretext/append sequence before mach bytecode execution.

Safety Invariant

A pretext is uniquely referenced by exactly one slot. This is enforced by stone slot, slot mcode, which the streamline optimizer inserts before any instruction that would expose a pretext to script-visible boundaries or create a second reference to it (return, call argument, closure capture/read, store key/value, push, put, is_stone, or live move aliasing).

Why Over-Allocation Is GC-Safe

  • The copying collector preserves the allocated capacity of mutable text.
  • pit_alloc_string zero-fills the packed data region, so padding beyond length is always clean.
  • String comparisons and hashing use the logical length. Extra capacity is invisible to string operations.

Relationship to GC

The Cheney copying collector operates on actor heaps. Stoned heap objects are copied like other heap objects, but writes to them disrupt. Image-backed stone lives outside actor heaps, so the collector recognizes it and leaves its placement unchanged.