selfhost+test: single-source-of-truth @tagscr scratch reservation (#38)

Closes STATUS latent #1: @tagscr shared 24B reservation across the
four tagged-scratch sites (cgreturn, pushargsrev, cgindex
tagged-elem, pointer-rooted struct-field tagged write). Any fn that
needed >24B (e.g. slice-in-tagged-field 32B) silently overflowed
into the neighbor frame slot. Surfaced concretely as getopttest's
errortable wwstage exit 16 after #37 fixed the upstream gaps.

c.tagscrsz: i32 on the cgen struct is the single source of truth.
tagscrbump(c, need) in scanlocals raises the max across all 4
reservation sites and returns the frame delta. All emit sites
(cgreturn / pushargsrev / cgindex / cgwidentaggedstore pointer-
rooted) read c.tagscrsz instead of hardcoded 24. Mirrors the existing
cgwidentaggedstore precedent; @tagbase keeps its 8B scanseenmark
dedup (always 8B, correct).

Unmasked latent bug (now fixed): scanlocals's pointer-rooted struct-
field tagged-write detection uses localfindnode(c, base.str) to
resolve the *struct base. For `fn fill(h: *holder)`, h's scan-time
stub from scanseenmark had tnode=nil, so the @tagscr reservation
never fired. Pre-#38 the hardcoded 24B masked this; #38's correctly-
sized slot exposed it. cgfn's param scan loop now sets
c.locals.tnode = scanp.lhs after scanseenmark so localfindnode
resolves param types at scan time.

Test 714 (tagged_return_scratch): 4 rows × 2 stages = 8 fixtures.
Direct adjacency repro; match-arm field-by-field read; **mixed-
sizes-one-fn** (16B pushargsrev widen + 32B cgreturn widen in the
same body — pins the lockstep invariant that a sibling site can't
undersize the shared slot); call-site struct-payload widen. Row 3
specifically would regress if a future refactor ever forgets to
route an emit site through c.tagscrsz.

982 getopt_run green through both stages (was the original surface);
995 self_rebuild byte-id holds.
This commit is contained in:
2026-05-16 13:45:33 +09:00
parent de3bd5cc3b
commit 28f36d84d8
9 changed files with 541 additions and 93 deletions

View File

@@ -165,7 +165,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
let pname: str = rhsstructpayload(c, arg);
if (pname.len > 0) {
let ptype: *node = param.lhs;
let scroff: i32 = localadd(c, "@tagscr", 24, nil);
let scroff: i32 = localadd(c, "@tagscr", c.tagscrsz, nil);
emitline("\tXORQ\tAX, AX\n");
let zz: i32 = 0;
for (zz < widensz) {
@@ -2570,7 +2570,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
emitline(", ");
emitoff(bspill: i64);
emitline("(BP)\n");
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
// Same shared scratch — c.tagscrsz is the per-fn max across every
// reservation site (scanlocals); pinning to slot_sz here would
// undersize the slot if a sibling site (cgreturn, pushargsrev,
// cgindex) needed a larger one and fired second.
let scr: i32 = localadd(c, "@tagscr", c.tagscrsz, nil);
emitline("\tXORQ\tAX, AX\n");
let z: i32 = 0;
for (z < slot_sz) {