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:
- If the value is already stone, return immediately
- Mark the referenced heap object immutable
- 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:
lengthboth operandsaddthe lengths for capacitypretext dest, capacityappend dest, leftappend 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_stringzero-fills the packed data region, so padding beyondlengthis 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.