Expand description
Classes plan (docs/superpowers/plans/2026-10-03-classes.md) Task 3: the
object heap runtime (LRM 1800-2023 §8.29 memory management, §8.4 object
identity). Pure-Rust, zero sv-lang/codegen dependency (this task’s own
acceptance text) — see crate::netlist::ClassTemplateId’s doc comment
for why that id, despite being produced by the frontend-gated
netlist::classes module, is itself defined unconditionally and usable
from here.
Why a second table, not collections.rs’s. [collections] stores
SV values: a = b copies (copy-on-write via Table::modify), and an
overwritten handle is unconditionally freed. A class object is a
reference (§8.2: “the argument copied is the object handle, not the
contents of the object”) — h2 = h1 makes both variables alias the SAME
object, nothing is cloned, and an object is freed only when nothing
references it any more. So this module is a structurally parallel
generational handle table (same version<<32 | index shape, handle 0
reserved for null, a Mutex-guarded table with a free list so a reused
slot never repeats a handle — directly modeled on collections.rs’s
Table/index/alloc) but with real reference counting instead of
copy-on-write.
Weak references fall out for free. A stale (version, index) handle
can never alias a different, later object at the same index: alloc
always bumps that slot’s version counter (never reused, only the index
is recycled), and every read (field_get, is_a,
dynamic_template) rejects a handle whose version does not match the
table’s current entry at that index. The LRM’s “the clearing operation
shall be performed atomically” (8.30.1) is satisfied by construction:
the staleness check and the table’s own Mutex are the same operation,
so there is nothing to race.
Per-template metadata is a registry, not a call-site parameter.
release’s recursive free walk (which fields of a freed object are
themselves class handles that must themselves be released) and is_a
(which ClassTemplateIds a given dynamic template satisfies — self,
every base, every flattened interface) both need information that is
fixed per ClassTemplateId, computed once at elaboration time, and
consumed by scattered call sites with no spare room to thread a &[_]/
&HashMap argument through (most of those call sites are
#[no_mangle] extern "C" ABI entry points reached from LLVM-emitted
code, which can only pass scalar/pointer arguments it already has in
hand — never a slice or map reference). So, exactly like
[collections]’s own table() global, this module keeps one more
process-wide Mutex-guarded table, [registry], populated once per
template by register_handle_fields/register_ancestors (Task 5
wires in the real ObjectLayout-derived field list; Task 10 wires in the
real netlist::classes::base_chain/flattened_interfaces-derived
ancestor set — this task’s own tests call both directly with a hand-built
list/set, exactly as the plan’s Task 3 entry specifies). An
unregistered template is simply treated as having no handle fields and
no ancestors beyond itself — never a panic, since a template legitimately
has nothing to register when, e.g., it declares no class-typed
properties at all.
Structs§
- Field
Slot - One non-static field’s location in an object’s flat
fieldsbuffer: a word offset and a word count.wordsis always1for a field that holds a class handle (a handle is a singleu64); wider counts are for ordinary (non-handle) SV data fields, whichfield_get/field_sethandle identically — this module does not interpret field contents except whererelease’s recursive walk is told a slot is itself a handle (seeregister_handle_fields). - Nested
Handle - A class-handle or collection-handle held INSIDE a larger property: a field
or element of an unpacked struct, or an element of a fixed-size unpacked
array, stored as part of the property’s one flat value (LRM 1800-2023 7.2,
7.4:
state::storage). The handle is a 64-bit value at bitbitof the property’s value bits (the low half of the property’s 4-state packed words, which is wherefield_gethands them out). - Rand
Meta - Per-object
rand/randcmetadata (LRM §18.4, §18.8): therand_mode()on/off state of eachrand/randcproperty, in the orderregister_rand_propslisted them (base-class properties first). Every object starts with all of themtrue– LRM 18.8: “Initially, allrandvariables are active”. What israndand what israndcis the template’s (rand_props); this vector is the only mutable part, so the constraints plan’srandomize()andobj.x.rand_mode(0)read/write it here without a second per-object side table. - Rand
Prop - One
rand/randcproperty of a class template: where its value lives in an object’sfieldsbuffer and which kind it is. Fixed by the class declaration, so it lives on the template (register_rand_props), not on each object. - Template
Snapshot - One class template’s registered runtime metadata as plain data: what a design
image carries so a runner that never saw the class declarations can
register the same templates (
template_snapshot,preload_templates). - Window
- A bit window of one property: a leaf (a member of an unpacked struct, an
element of a fixed-size unpacked array) addressed inside the property’s one
flat 4-state value (
state::storage). The property holdsfield_widthvalue bits thenfield_widthunknown bits; the leaf iswidthbits at value bitbit, with its unknown bits atfield_width + bit.
Enums§
- Rand
Kind - Whether a
randproperty is plainrandor cyclicrandc(LRM 18.4).
Functions§
- alloc
- Allocates a new object of
templatewithfield_wordswords of zero-initialized field storage, refcount 1. §1.1’s target signature takeslayout: &ObjectLayoutinstead of a bare word count —ObjectLayoutdoes not exist until Task 5, so this task takes the one piece of itallocitself actually needs; Task 5 adapts call sites to passlayout.total_words()(or equivalent) once that type exists, with no further change to this function’s behavior. - collect_
cycles - Frees every object that only other objects reference – the reference cycles reference counting alone never frees (LRM 1800-2023 §8.29: an object no handle outside the heap can reach is reclaimed). Returns how many objects were freed.
- collected_
count - How many objects
collect_cycleshas freed in this process. - constraint_
mode_ of - Whether constraint block
ordinalheld byh(an instance’s own block, or a static-property object’sstaticblock) is active (LRM 18.9: all start active).Nonefor a null/stale handle. - copy
- LRM 1800-2023 §8.12 shallow copy (
new obj): a new object (refcount 1, the caller’s reference) ofh’s DYNAMIC template – not the handle’s static type – holding a word-for-word copy of its fields, each field that is itself a handleretained (the copy and the original now both name the nested object: it is shared, not duplicated). The constructor is not run. The copy’srandcvariables start a fresh cycle (a permutation in progress is the original’s history, not part of its value).Nonefor a null or staleh(§8.12 leaves a null source undefined; the caller decides). - dynamic_
template h’s dynamic (runtime) class —Nonefor a null or stale handle.- field_
get - Reads field
slotof the live objecthintoout(as many words asslot.words/outboth have room for);false(leavingoutalone) for a null, stale, or out-of-range handle/slot. - field_
get_ window - Reads the leaf
winof propertyslotof the live objecth: its value intovaland its unknown bits intounk(eachceil(width/64)words);false(leaving both alone) for a null, stale or out-of-range handle/slot. - field_
merge - Replaces the
maskbits of the words atoffsetof the live objecthwith those ofval(a staged nonblocking write of a class property, LRM 1800-2023 10.4.2);falsefor a dead handle or out-of-range span. Wakeswaiters when the property changed, likefield_set. - field_
set - Writes field
slotof the live objecthfromval; a null, stale, or out-of-range handle/slot is a no-op (no live storage exists to write) that returnsfalse, so the caller can report the access (LRM 1800-2023 8.4: a member of anullhandle is a run-time error).truewhen it wrote. - field_
set_ window - Writes the leaf
winof propertyslotof the live objecthas the known valueval, leaving the rest of the property alone; likefield_setit is a no-op returningfalsefor a dead handle or bad slot, and it wakeswaiters when the property changed. - is_a
$cast’s runtime check (6.24.2/8.16): whetherh’s dynamic class isdestor one ofdest’s descendants/implementors in the sense that matters for a cast — concretely, whetherdestis inh’s dynamic template’s own registered ancestor-or-self closure (register_ancestors). A null/stale handle, or a dynamic template with nothing registered for it yet, is neveris_aanything (including itself) — an unregistered template legitimately means “no class/interface hierarchy information is available yet”, not “matches everything”.- is_live
- Whether
hnames a live object: not null, not freed, and not a stale handle to a slot a later object now occupies (the version check of this module’s doc comment). This is the whole ofweak_reference#(T)::get()(LRM 1800-2023 8.30.3): a weak reference stores the referent’s bare handle bits without retaining it, and is “cleared” exactly when this turns false. - leaf_
rand_ mode_ of obj.v[i].rand_mode()(LRM 18.8): whether the element at value bitbitof therandarray property at wordoffsetofhis active, i.e. its property is and the element was not switched off alone.Noneasrand_mode_of.- live_
count - The number of objects currently alive (allocated and not yet freed by
their last
release) – the observable of reference counting: a design that drops every handle it took must bring this back to where it started, and one that over-releases would already have printed a stale read, so a leak and a use-after-free are both visible from outside. - maybe_
collect_ cycles - Runs
collect_cyclesonce enough objects have been allocated since the last collection to make it worthwhile (at least 4096, and at least as many as are alive), so its cost stays proportional to allocation. - preload_
templates - Registers every template of
snapshot(template_snapshot’s inverse), each replacing what was registered for its id. - prng_
seed h’s object-stability PRNG seed (§18.14.3) —Nonefor a null/stale handle.allocleaves it0; the runtime’sAllocop draws the real value from the creating process’s RNG cell atnew-time (LRM 18.14.2) and stores it here.- rand_
meta h’srand/randcmetadata (a clone —Nonefor a null/stale handle).- rand_
mode_ of - Whether the
rand/randcproperty at wordoffsetofhis active (rand_mode(), LRM 18.8).Nonefor a null/stale handle or anoffsetthat is norandproperty ofh’s class. - rand_
props template’s registeredrand/randcproperties (empty for a template with none, or none registered).- randc_
state - The
randccycle state of the field at wordoffsetofh(a clone; the default, not-yet-drawn state if it has none).Nonefor a null/stale handle. - refcount
h’s current reference count (Nonefor a null or stale handle) – test-only observation of the retain/release discipline.- register_
ancestors - Registers (idempotently, same discipline as
register_handle_fields) the ancestor-closure setis_aconsults:templateitself plus every base class and every flattened interface. Task 3 stub (this task’s own tests call it directly with a hand-built set); Task 10 wires in the realnetlist::classes::base_chain/flattened_interfaces-derived computation. - register_
collection_ fields - Registers (same discipline as
register_handle_fields) the ordered list oftemplate’s own non-static fields that hold a collection handle (a queue, dynamic array or associative array; ledger XC-059):releasefrees each such collection when its object dies andcopygives the new object its own copy-on-write clone (LRM 8.12: a shallow copy of a collection property is a value copy, never an alias). - register_
handle_ fields - Registers (idempotently — a later call replaces, never merges, an
earlier one for the same template) the ordered list of
template’s own non-static fields that are themselves class handles, consumed byrelease’s recursive free walk. Task 3 stub (this task’s own tests call it directly with a hand-built list); Task 5 wires in the realObjectLayout-derived list once that type exists. - register_
nested_ handles - Registers (same discipline as
register_handle_fields) the handlestemplate’s struct and fixed-array properties hold at a bit offset of their value (ledger XC-059): a class handle or collection nested in an unpacked struct field or array element.releasefrees each when its object dies andcopyretains each object / clones each collection, exactly as for a whole-property handle. - register_
rand_ props - Registers (idempotently, same discipline as
register_handle_fields)template’srand/randcproperties, base-class properties first. Every object allocated afterwards starts with one activerand_modebit per entry (RandMeta). - register_
static_ singleton_ key - Records that class specialization
key(class_specialization_key) keeps itsstaticproperties and blocks in singleton objects oftemplate. - release
-1reference; at0, recursivelyreleases every fieldregister_handle_fieldsnamed as itself a class handle, then frees the slot (its index returns to the free list; itsversioncounter is left alone, so the NEXTallocat that index bumps it again and no future handle ever collides with this one — see this module’s doc comment on weak-reference staleness). A null or already-stale handle is a no-op, the same asretain.- retain
+1reference. A null (0) or already-stale handle is silently a no-op:retain/releaseare only ever called by codegen on a handle a variable/field/collection element already holds, and a null handle holds nothing to retain.- set_
all_ rand_ modes obj.rand_mode(on)with no variable (LRM 18.8): everyrand/randcproperty ofh(its own, not those of the objects it holds).falsefor a null/stale handle.- set_
constraint_ mode_ of - Sets the mode of constraint block
ordinalheld byh;falsefor a null/stale handle. - set_
leaf_ rand_ mode_ of obj.v[i].rand_mode(on): switches the one element at value bitbitof the array property at wordoffsetofh.falseasset_rand_mode_of.- set_
lifecycle_ hooks - Installs the lifetime observers (see [
CREATE_HOOK]); a second call is a no-op. - set_
prng_ seed - Sets
h’s object-stability PRNG seed; a no-op on a null/stale handle. - set_
rand_ meta - Replaces
h’srand/randcmetadata; a no-op on a null/stale handle. - set_
rand_ mode_ of - Sets the
rand_mode()of therand/randcproperty at wordoffsetofh.false(changing nothing) for a null/stale handle or anoffsetthat is norandproperty ofh’s class. - set_
randc_ states - Stores
h’srandccycle states,(word offset, state)each;false(storing nothing) for a null/stale handle. - set_
static_ rand_ mode - Sets the mode of the
staticrandproperty at wordoffsetof the static-property objecth;falsefor a null/stale handle. - slot_
index - The slot index of
h–weak_reference#(T)::get_id’s value (LRM 8.30.5): nonzero for every real object (index 0 is the null handle’s), shared by every handle to one object whatever its static type, unique among the objects alive at once (one slot each), and reusable after the object is freed (which 8.30.5 explicitly allows).0for the null handle. - static_
rand_ mode - Whether the
staticrandproperty at wordoffsetof the static-property objecthis active (LRM 18.8: its mode is shared by every instance).Nonefor a null/stale handle. - static_
singleton_ key_ pairs - Every registered
(class key, singleton template id)pair, sorted by key: what an AOT image carries so a precompiled run can answerstatic_singleton_ofwithout the compiler having run. - static_
singleton_ of - The live static-property singleton of the class with specialization
key:Nonewhen the compiler never registered the key,Some(None)when no singleton has been allocated yet (nothing has touched the class’s statics, so every static mode still has its default). LRM 18.5.10, 18.9. - template_
is_ a is_afor a template rather than an object: whetherdestis intemplate’s registered ancestor-or-self closure. A compile-time query (a call site asks which registered classes could be the dynamic class behind a static one).- template_
snapshot - The registered metadata of each template in
ids, ascending by id and without repeats. A template nothing was registered for is skipped: it has no handle fields, no ancestors and norandproperties, which is what an absent entry already means.