Skip to main content

Module objects

Module objects 

Source
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§

FieldSlot
One non-static field’s location in an object’s flat fields buffer: a word offset and a word count. words is always 1 for a field that holds a class handle (a handle is a single u64); wider counts are for ordinary (non-handle) SV data fields, which field_get/field_set handle identically — this module does not interpret field contents except where release’s recursive walk is told a slot is itself a handle (see register_handle_fields).
NestedHandle
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 bit bit of the property’s value bits (the low half of the property’s 4-state packed words, which is where field_get hands them out).
RandMeta
Per-object rand/randc metadata (LRM §18.4, §18.8): the rand_mode() on/off state of each rand/randc property, in the order register_rand_props listed them (base-class properties first). Every object starts with all of them true – LRM 18.8: “Initially, all rand variables are active”. What is rand and what is randc is the template’s (rand_props); this vector is the only mutable part, so the constraints plan’s randomize() and obj.x.rand_mode(0) read/write it here without a second per-object side table.
RandProp
One rand/randc property of a class template: where its value lives in an object’s fields buffer and which kind it is. Fixed by the class declaration, so it lives on the template (register_rand_props), not on each object.
TemplateSnapshot
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 holds field_width value bits then field_width unknown bits; the leaf is width bits at value bit bit, with its unknown bits at field_width + bit.

Enums§

RandKind
Whether a rand property is plain rand or cyclic randc (LRM 18.4).

Functions§

alloc
Allocates a new object of template with field_words words of zero-initialized field storage, refcount 1. §1.1’s target signature takes layout: &ObjectLayout instead of a bare word count — ObjectLayout does not exist until Task 5, so this task takes the one piece of it alloc itself actually needs; Task 5 adapts call sites to pass layout.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_cycles has freed in this process.
constraint_mode_of
Whether constraint block ordinal held by h (an instance’s own block, or a static-property object’s static block) is active (LRM 18.9: all start active). None for a null/stale handle.
copy
LRM 1800-2023 §8.12 shallow copy (new obj): a new object (refcount 1, the caller’s reference) of h’s DYNAMIC template – not the handle’s static type – holding a word-for-word copy of its fields, each field that is itself a handle retained (the copy and the original now both name the nested object: it is shared, not duplicated). The constructor is not run. The copy’s randc variables start a fresh cycle (a permutation in progress is the original’s history, not part of its value). None for a null or stale h (§8.12 leaves a null source undefined; the caller decides).
dynamic_template
h’s dynamic (runtime) class — None for a null or stale handle.
field_get
Reads field slot of the live object h into out (as many words as slot.words/out both have room for); false (leaving out alone) for a null, stale, or out-of-range handle/slot.
field_get_window
Reads the leaf win of property slot of the live object h: its value into val and its unknown bits into unk (each ceil(width/64) words); false (leaving both alone) for a null, stale or out-of-range handle/slot.
field_merge
Replaces the mask bits of the words at offset of the live object h with those of val (a staged nonblocking write of a class property, LRM 1800-2023 10.4.2); false for a dead handle or out-of-range span. Wakes waiters when the property changed, like field_set.
field_set
Writes field slot of the live object h from val; a null, stale, or out-of-range handle/slot is a no-op (no live storage exists to write) that returns false, so the caller can report the access (LRM 1800-2023 8.4: a member of a null handle is a run-time error). true when it wrote.
field_set_window
Writes the leaf win of property slot of the live object h as the known value val, leaving the rest of the property alone; like field_set it is a no-op returning false for a dead handle or bad slot, and it wakes waiters when the property changed.
is_a
$cast’s runtime check (6.24.2/8.16): whether h’s dynamic class is dest or one of dest’s descendants/implementors in the sense that matters for a cast — concretely, whether dest is in h’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 never is_a anything (including itself) — an unregistered template legitimately means “no class/interface hierarchy information is available yet”, not “matches everything”.
is_live
Whether h names 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 of weak_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 bit bit of the rand array property at word offset of h is active, i.e. its property is and the element was not switched off alone. None as rand_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_cycles once 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) — None for a null/stale handle. alloc leaves it 0; the runtime’s Alloc op draws the real value from the creating process’s RNG cell at new-time (LRM 18.14.2) and stores it here.
rand_meta
h’s rand/randc metadata (a clone — None for a null/stale handle).
rand_mode_of
Whether the rand/randc property at word offset of h is active (rand_mode(), LRM 18.8). None for a null/stale handle or an offset that is no rand property of h’s class.
rand_props
template’s registered rand/randc properties (empty for a template with none, or none registered).
randc_state
The randc cycle state of the field at word offset of h (a clone; the default, not-yet-drawn state if it has none). None for a null/stale handle.
refcount
h’s current reference count (None for 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 set is_a consults: template itself 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 real netlist::classes::base_chain/ flattened_interfaces-derived computation.
register_collection_fields
Registers (same discipline as register_handle_fields) the ordered list of template’s own non-static fields that hold a collection handle (a queue, dynamic array or associative array; ledger XC-059): release frees each such collection when its object dies and copy gives 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 by release’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 real ObjectLayout-derived list once that type exists.
register_nested_handles
Registers (same discipline as register_handle_fields) the handles template’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. release frees each when its object dies and copy retains each object / clones each collection, exactly as for a whole-property handle.
register_rand_props
Registers (idempotently, same discipline as register_handle_fields) template’s rand/randc properties, base-class properties first. Every object allocated afterwards starts with one active rand_mode bit per entry (RandMeta).
register_static_singleton_key
Records that class specialization key (class_specialization_key) keeps its static properties and blocks in singleton objects of template.
release
-1 reference; at 0, recursively releases every field register_handle_fields named as itself a class handle, then frees the slot (its index returns to the free list; its version counter is left alone, so the NEXT alloc at 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 as retain.
retain
+1 reference. A null (0) or already-stale handle is silently a no-op: retain/release are 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): every rand/randc property of h (its own, not those of the objects it holds). false for a null/stale handle.
set_constraint_mode_of
Sets the mode of constraint block ordinal held by h; false for a null/stale handle.
set_leaf_rand_mode_of
obj.v[i].rand_mode(on): switches the one element at value bit bit of the array property at word offset of h. false as set_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’s rand/randc metadata; a no-op on a null/stale handle.
set_rand_mode_of
Sets the rand_mode() of the rand/randc property at word offset of h. false (changing nothing) for a null/stale handle or an offset that is no rand property of h’s class.
set_randc_states
Stores h’s randc cycle states, (word offset, state) each; false (storing nothing) for a null/stale handle.
set_static_rand_mode
Sets the mode of the static rand property at word offset of the static-property object h; false for 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). 0 for the null handle.
static_rand_mode
Whether the static rand property at word offset of the static-property object h is active (LRM 18.8: its mode is shared by every instance). None for 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 answer static_singleton_of without the compiler having run.
static_singleton_of
The live static-property singleton of the class with specialization key: None when 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_a for a template rather than an object: whether dest is in template’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 no rand properties, which is what an absent entry already means.