TypeRef: migrate consumers lane by lane
Follow-up to [[feature-a-typeref-handle]], which landed TTypeRef
(compiler/defs.inc) additively — the record exists, nothing reads it yet.
This ticket is the actual payoff: replacing the ~90+ parallel-array sites
(SymPtrBaseTk/SymPtrBaseRec, UFldPtrElemTk/UFldPtrElemRec,
ProcRetPtrElemTk, LiftCapPtrTk/LiftCapProcSig, ...) with one TTypeRef
field per entity, one lane at a time.
See devdocs/dev/type-identity-as-substrate.md for the full design and why
this matters (the "one of six parallel arrays not written" bug class — four
such bugs landed in one session per that note's evidence table).
Landing rule (unchanged from the parent ticket)
Each lane migrates ONE entity kind's parallel arrays to a single TTypeRef
field, lands under the self-host byte-identical gate, and does not touch any
other lane's arrays in the same change. Candidate lane order (smallest/
lowest-risk first, largest blast radius last):
TSymbol(Syms[]) — the largest single consumer (~26 parallel fields per the design note), but also the most self-contained: read/write sites are concentrated insymtab.inc/ir.inc.AliasPtrBaseTk/AliasPtrBaseRec(type aliases).UFldPtrElemTk/UFldPtrElemRec(class/record fields).ProcRetPtrElemTk/ProcRetProcSig(routine return types).LiftCapPtrTk/LiftCapProcSig(closure capture types).CTypeFnRetPBaseTk/CTypeFnRetPProcSig(C frontend type declarations).
Each lane: add a TTypeRef field alongside the existing parallel arrays,
populate it everywhere the old arrays are populated (do not remove the old
arrays yet — they stay the source of truth until every READ site is migrated
too), then migrate reads, then delete the old arrays once nothing references
them. Land incrementally — this is explicitly NOT a one-sitting change; the
parent ticket's Blocks [[feature-a-abi-oracle]] note means the oracle work
cannot start until at least enough lanes are migrated that a type's identity
can be read from one place instead of per-backend Syms[] probing.
Gate
make test + self-host byte-identical per lane; make test-nilpy too for any
lane NilPy code touches (2 and 3 especially, given the design note's NilPy
divergence evidence).
2026-08-01 — lane 1 (TSymbol) in progress; REVERTED two backend read-site
migrations, real sync gap found
SymTR (additive, 54b5684bc) landed, and several read sites migrated
cleanly: ir.inc's managed-record-copy and char-pointer-check (93ebb9377,
e9d1522c6) — both safe because the fields they read (RecName/PtrElemTk
for those specific symbols) are only ever written at the symbol's creation
chokepoint, so SymSyncTypeRef (called there) keeps SymTR correct.
Then a real gap was caught before more damage landed: SymSyncTypeRef is
only called at the 5 symtab.inc creation chokepoints
(AllocVar/AllocParam/AllocArray/AllocDynArray/AddConst), but
parser.inc/cparser.inc/pyparser.inc/ir.inc also mutate the SAME old
fields after creation — e.g. compiler/parser.inc:26006 (and :20565,
:21058) does idx := AllocArray(...) then immediately
Syms[idx].ElemRecName := LastTypeRecId for any array of <record>
parameter or local — a hot, common pattern, not an edge case. None of
those ~132 post-creation write sites call SymSyncTypeRef, so SymTR goes
stale for any symbol they touch.
Two already-merged backend migrations were exposed to exactly this:
573e1c4e7 (x86-64 ir_codegen.inc, migrated ElemRecName/ElemType/
RecName reads for anon-dynarray registration + managed-array-element
copy) and e3497fc73 (i386/arm32/aarch64/riscv32/xtensa RecName reads for
managed-record dynarray-append growth) — both read SymTR[...].ElemRec/
.RecId for exactly the array-of-record symbols the parser.inc sites above
mutate post-creation. Reverted (9b73ff4d6, e484fde67) rather than
leave a known, plausibly-widely-triggered stale-read gap live on master —
this is the same bug shape as the pconst-shift regression from the night
before (a parallel-array sync miss that a happy-path test corpus doesn't
happen to exercise), and that one only surfaced in a full test-core run,
not fixedpoint or testmgr --tier quick, so "tests passed" was not
sufficient evidence to keep it.
Next: before migrating any more reads (especially anything touching
RecName/ElemRecName/TypeKind/PtrElemTk/PtrElemRec/SymProcSig),
add SymSyncTypeRef calls after every post-creation write site — full
inventory (grep for Syms[<idx>].<field> := outside symtab.inc's
Alloc*/AddConst) is roughly: RecName/ElemRecName ~58 sites (ir.inc 3,
pyparser.inc ~42, parser.inc ~13), TypeKind ~9 (parser.inc),
PtrElemTk/PtrElemRec ~17+17 (cparser.inc, parser.inc),
SymProcSig ~11, ElemType/IsArray/ArrLen ~16. That write-side sync
is real, distinct work — do it before trusting any more SymTR reads, and
re-land the two reverted backend migrations only after it's done and
re-verified.
2026-08-03 — moved working/ -> unfinished/ (board maintenance)
working/ is a live lock: a ticket sits there only while an agent is actively
on it. This one had not been touched in three days, so the lock was stale and
next was reserving a Track A file-lane nobody held.
Not a revert, and nothing is half-applied: lane 1 (the SymTR parallel
array plus the full write-side sync) is landed and green — the self-host
fixedpoint has been rebuilt many times since against it. What remains is the
~68 read sites and the old-field deletion, which is why this is unfinished/
rather than done/. Re-claim it to continue.