w6c+w6c_ww: size-keyed @tagscr — one tagged scratch per slot size (fix #44)

A fn mixing two tagged slot sizes smaller-first (regex compile(): 56B
append-element widen then 64B sret return) hit the #15/#26c rule-7
grow-fatal — the single shared per-fn @tagscr is first-use-sized and
its pinned offset can't grow. Key the scratch by slot size instead:
@tagscr<sz>, one first-use-allocated slot per distinct size, all three
sites (widen-store via_outer, widen-push, N_INDEX tagged-element
assign) funnelled through cg_tagscr_slot / tagscradd in both stages.
Single-size fns emit byte-identical asm to pre-fix (control row pinned
+ hand-cmp'd vs master w6c). 736's tagscr_size_grow_fatal fixture
pinned the now-unreachable fatal; converted to a byte-id succ row.
Runtime rows live in 926_tagscr_sizes_run.
This commit is contained in:
2026-06-04 04:34:54 +09:00
parent 8999b59ab0
commit c2308a11c7
9 changed files with 483 additions and 176 deletions

View File

@@ -71,22 +71,31 @@ static int cg_aggargscr_sz;
* Two cached slots match wwstage's `@`-prefix namespace exactly:
* cg_tagbase — 8B base-register spill for cg_widen_tagged_store
* via_outer (mirrors wwstage @tagbase, 1 site).
* cg_tagscr — sized scratch shared across THREE sites: cg_widen_
* @tagscr<sz> — sized scratch shared across THREE sites: cg_widen_
* tagged_store via_outer write target, cg_widen_tagged_
* push struct/tagged-source widen, N_INDEX tagged-element
* assign. Mirrors wwstage @tagscr — wwstage shares the
* slot via localadd `@`-prefix dedup against c.atlocals.
* assign. Mirrors wwstage @tagscr<sz> — wwstage shares
* the slot via localadd `@`-prefix dedup against
* c.atlocals.
*
* Both stages now size at first use and fatal() if a later site asks
* for more (rule 7: surface, don't silently corrupt the frame —
* pinned offset can't grow in place once neighbours are allocated).
* Both stages size at first use (per name). Pre-#44 the tagged scratch
* was a SINGLE slot and a later site asking for a larger size fatal'd
* (rule 7 — pinned offset can't grow in place once neighbours are
* allocated); a fn mixing two tagged slot sizes smaller-first (regex
* compile(): 56B append-element widen then 64B sret return) was
* uncompilable. #44 keys the scratch by slot size — one cached slot
* per distinct size, allocated in first-use order in BOTH stages, so
* the grow-fatal is unreachable for @tagscr by construction. All
* three sites funnel through cg_tagscr_slot (no other alloc path).
* Per-fn convergence completed by #15 (#26c follow-up): wwstage
* dropped its scanlocals pre-pass and aligned DOWN to cstage's
* first-use shape. _sz tracks cached allocation size. */
* first-use shape. */
static int cg_tagbase;
static int cg_tagbase_sz;
static int cg_tagscr;
static int cg_tagscr_sz;
enum { CG_NTAGSCR = 16 };
static int cg_tagscr_off[CG_NTAGSCR];
static int cg_tagscr_sz[CG_NTAGSCR];
static int cg_ntagscr;
/* #34: per-fn @appendscr — 8B dst-pointer spill for the append()
* struct-literal element fill (cg_structlit_fill DST_PTR_LOCAL needs
* a BP-rooted slot to reload BX from across its internal cgexprs).
@@ -1687,6 +1696,26 @@ localfind(Local *head, const char *name)
return 0; /* 0 = not found (caller must verify) */
}
/* cg_tagscr_slot — the ONLY alloc path for the per-fn tagged scratch
* (#44). One cached slot per distinct slot size, named "@tagscr<sz>"
* so wwstage's localadd name-dedup keys the same way; first-use
* allocation order is the source order in both stages (byte-id). */
static int
cg_tagscr_slot(Cg *c, Local **locals_p, int sz)
{
for (int i = 0; i < cg_ntagscr; i++)
if (cg_tagscr_sz[i] == sz)
return cg_tagscr_off[i];
if (cg_ntagscr >= CG_NTAGSCR)
fatal("cg_tagscr_slot: more than %d distinct tagged "
"scratch sizes in one fn", CG_NTAGSCR);
cg_tagscr_off[cg_ntagscr] = local_alloc(c, locals_p,
aprintf(c->a, "@tagscr%d", sz), sz, cg_frame);
cg_tagscr_sz[cg_ntagscr] = sz;
cg_ntagscr++;
return cg_tagscr_off[cg_ntagscr - 1];
}
/* cg_base_cap — load the capacity of a sub-slice's UNDERLYING storage
* into `dst` for the #20 cap = base_cap - lo formula (drew: harec
* eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start;
@@ -2080,19 +2109,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
cg_tagbase_sz = 8;
}
ins2(c, A_MOVQ, areg(base_reg), amem(D_BP, base_spill));
if (cg_tagscr != 0) {
if (sz > cg_tagscr_sz)
fatal("cg_widen_tagged_store: @tagscr "
"cached sz %d, need %d (pinned offset "
"can't grow in place; rule 7 — #15/#26c)",
cg_tagscr_sz, sz);
write_off = cg_tagscr;
} else {
write_off = local_alloc(c, locals_p, "@tagscr", sz,
cg_frame);
cg_tagscr = write_off;
cg_tagscr_sz = sz;
}
write_off = cg_tagscr_slot(c, locals_p, sz);
/* Pre-zero so str/scalar branches (which leave high words
* untouched when sz exceeds the variant's footprint) still
* deliver a clean slot to the copy-out. */
@@ -2536,19 +2553,7 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
ins1(c, A_PUSHQ, areg(D_AX)); /* tag at +0 */
return;
}
int scr;
if (cg_tagscr != 0) {
if (sz > cg_tagscr_sz)
fatal("cg_widen_tagged_push: @tagscr cached sz %d, "
"need %d (pinned offset can't grow in place; "
"rule 7 — #15/#26c)",
cg_tagscr_sz, sz);
scr = cg_tagscr;
} else {
scr = local_alloc(c, locals_p, "@tagscr", sz, cg_frame);
cg_tagscr = scr;
cg_tagscr_sz = sz;
}
int scr = cg_tagscr_slot(c, locals_p, sz);
/* Zero the scratch slot first so any pad word the store path
* leaves untouched (struct payload shorter than the slot's value
* area) reads as 0 on the callee. The store path then writes the
@@ -5238,22 +5243,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* the function frame; no cleanup needed. */
if ((is_arr || is_sl || is_ptr) && elem_tagged) {
int ssz = esz;
int scr;
if (cg_tagscr != 0) {
if (ssz > cg_tagscr_sz)
fatal("N_INDEX tagged: "
"@tagscr cached sz %d, "
"need %d (pinned offset "
"can't grow in place; "
"rule 7 — #15/#26c)",
cg_tagscr_sz, ssz);
scr = cg_tagscr;
} else {
scr = local_alloc(c, &locals,
"@tagscr", ssz, cg_frame);
cg_tagscr = scr;
cg_tagscr_sz = ssz;
}
int scr = cg_tagscr_slot(c, &locals, ssz);
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < ssz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
@@ -11242,8 +11232,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
cg_aggargscr_sz = 0;
cg_tagbase = 0;
cg_tagbase_sz = 0;
cg_tagscr = 0;
cg_tagscr_sz = 0;
cg_ntagscr = 0;
cg_appendscr = 0;
cg_sret_arg_off = 0;
cg_sret_dest_off = 0;