selfhost+test: size match-spill slot by scrutinee, not 24B (#9)

wwstage cgmatch hardcoded `spillsz = 24` + unconditional CX write
where cstage emits `slot_size = (su->kind == TY_TAGGED) ? su->size
: 16` with `if (slot_size > 16)` gating. For 1-word-payload variants
like `(*u8 | oserror)` the slot is 16B; wwstage over-allocated and
over-wrote past the receiver's read window.

Factor cgmatch's non-ident scrutinee-type resolution + spill sizing
into matchscrutt + matchspillsz in cgenutil.ww. cgmatch gates CX
write on `spillsz > 16`; R8 gate `> 24` already correct. scanlocals
N_MATCH branch uses the same helpers — scan+emit lockstep.

Test 716: 4 rows × {cstage runtime, wwstage runtime, asm-byte-id}.
Aliased (*u8 | oserror) ok/err arms, raw (*u8 | i64) for hypothesis
breadth, (str | i64) 24B regression guard.
This commit is contained in:
2026-05-17 00:00:55 +09:00
parent deaa777eb8
commit f4176b8749
7 changed files with 561 additions and 210 deletions

View File

@@ -971,65 +971,15 @@ fn cgmatch(c: *cgen, n: *node) void = {
// Non-ident scrutinee (call result, arr[i], p.field,
// ?, etc.). Spill into an `@match_spill` scratch slot
// and dispatch off it. Tagged returns (N_CALL) follow
// the AX:DX:CX convention; tagged-element loads
// the AX:DX:CX[:R8] convention; tagged-element loads
// (N_INDEX) and tagged-field loads (N_DOT, fixed by
// #28) produce the same triple. Nullable returns are
// single-word (AX = ptr); only +0 is read, so the
// extra stores are harmless. We recover the scrutinee
// type from fnretlookup (N_CALL), the base local's
// array element type (N_INDEX), or the struct field
// type (N_DOT) so dispatch can compute variant
// indices. Slot size is then derived from the
// scrutinee type so slice-variant tagged-unions
// (32B slot) don't overflow a hardcoded 24B scratch.
if (scrut.kind == nkind.N_CALL) {
let callee: *node = scrut.lhs;
if (callee != nil) {
let cnm: str;
cnm.ptr = nil; cnm.len = 0;
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
if (cnm.len > 0) {
let rt: *node = fnretlookup(c, cnm);
if (rt != nil) { scrutt = resolvetagged(c, rt); };
};
};
};
if (scrut.kind == nkind.N_INDEX) {
let ibase: *node = scrut.lhs;
if (ibase != nil) {
if (ibase.kind == nkind.N_IDENT) {
let bl: *local = localfindnode(c, ibase.str);
let btn: *node = nil;
if (bl != nil) { btn = bl.tnode; }
else { btn = letvartnode(c, ibase.str); };
if (btn != nil) {
let bk: nkind = btn.kind;
let etn: *node = nil;
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
if (etn != nil) {
scrutt = resolvetagged(c, etn);
};
};
};
};
};
if (scrut.kind == nkind.N_DOT) {
let ft: *node = dotfieldtnode(c, scrut);
if (ft != nil) {
scrutt = resolvetagged(c, ft);
};
};
// Size the spill to the scrutinee slot. Default 24B
// preserves the historical alloc for non-tagged or
// unresolved cases (nullable, str-returning, etc.).
let spillsz: i32 = 24;
if (scrutt != nil) {
let resolved: i32 = slotsize(c, scrutt);
if (resolved > spillsz) { spillsz = resolved; };
};
// single-word (AX = ptr); only +0 is read.
// Scrutinee type + spill size factored into matchscrutt
// / matchspillsz so scanlocals stays lockstep — see
// cgenutil.ww (task #9 align-down to cstage).
scrutt = matchscrutt(c, scrut);
let spillsz: i32 = matchspillsz(c, scrutt);
scrutoff = localalloc(c, "@match_spill", spillsz, nil);
cgexpr(c, scrut);
emitline("\tMOVQ\tAX, ");
@@ -1039,15 +989,17 @@ fn cgmatch(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((scrutoff + 16): i64);
emitline("(BP)\n");
// R8 carries the 4th return word when the
// scrutinee's tagged union has a slice-payload
// variant (slot 32B). Harmless for narrower
// returns — R8 is callee-clobbered either way.
let ssz: i32 = slotsize(c, scrutt);
if (ssz > 24) {
// CX/R8 writes gated on spill size so 1-word-
// payload variants (slot 16B) don't bump the
// frame past the tag+word0 the receiver reads.
// Mirrors cmd/w6c/cgen.c cgmatch's
// `if (slot_size > 16)` / `> 24` guards.
if (spillsz > 16) {
emitline("\tMOVQ\tCX, ");
emitoff((scrutoff + 16): i64);
emitline("(BP)\n");
};
if (spillsz > 24) {
emitline("\tMOVQ\tR8, ");
emitoff((scrutoff + 24): i64);
emitline("(BP)\n");