selfhost: cgmatch bsz for TY_STRUCT variant bind (closes #31)

Match-arm bind size in wwstage hardcoded str=16, []T=24, else=8 in
both cgmatch (emit) and scanlocals (frame pre-scan). A TY_STRUCT
variant fell into the 8B fallback: only the first quadword reached
the bind, and the prologue SUBQ underbooked the frame so the
emit-time localalloc(bsz=24+) wrote past SP.

Replace the hand-rolled table with slotsize(c, pat) at both sites.
slotsize already covers N_TNAME named structs (returns si.totsize),
str (16), []T (24), tuples, aliases, and primitives (8). Mirrors
cstage cgen.c cgmatch which falls through to bu->size for TY_STRUCT.

Cstage is correct; no mirror needed.

Test 695 covers seven shapes: the 3xi64 headline repro, a 4xi64
struct via let-init scrut (exercises >24B bind), a mixed-quadword
struct (i32+i32+i64+i64), str/slice/i32 negative controls, and a
direct let-init scrutinee variant. The wider 4xi64 row uses the
let-init shape because the N_DOT spill path in cgmatch tops out at
AX/DX/CX/R8 — a separate, unrelated gap from the bind size.
This commit is contained in:
2026-05-15 02:07:34 +09:00
parent 19fb3a3b3c
commit df74d9c429
6 changed files with 405 additions and 30 deletions

View File

@@ -150,9 +150,17 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
if (bn.len > 0) {
let pat: *node = n.lhs;
if (pat != nil) {
if (isstrtype(c, pat)) { total += 16; }
else { if (isslicetype(c, pat)) { total += 24; }
else { total += 8; }; };
// Must mirror cgmatch's bind-slot sizing in
// cgenexpr.ww (`bsz = slotsize(c, pat)`):
// hardcoding str/slice/8 here underbooked the
// frame for TY_STRUCT variants — the emit-time
// localalloc(bsz=24) then wrote past the SUBQ'd
// SP, smashing whatever the OS put under it
// (project #31).
let psz: i32 = slotsize(c, pat);
if (psz <= 0) { psz = 8; };
if ((psz & 7) != 0) { psz = (psz + 7) & ~7; };
total += psz;
};
};
// Match arms get a fresh local scope at emission time