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:
@@ -8197,6 +8197,68 @@ export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// matchscrutt — resolve a non-ident match scrutinee node to its tagged
|
||||
// type (or nil if unresolvable). Mirrors cgmatch's inline scrutinee
|
||||
// type resolution; factored so cgmatch (emit) and scanlocals (count)
|
||||
// agree on the spill slot's size per the scan+emit lockstep invariant.
|
||||
// IDENT scrutinees use a different lookup path (read off the local
|
||||
// directly, no spill) so this returns nil for them too.
|
||||
fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
if (scrut == nil) { return nil; };
|
||||
let k: nkind = scrut.kind;
|
||||
if (k == nkind.N_IDENT) { return nil; };
|
||||
if (k == 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) { return resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase == nil) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) { return nil; };
|
||||
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) { return 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) { return nil; };
|
||||
return resolvetagged(c, etn);
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
let ft: *node = dotfieldtnode(c, scrut);
|
||||
if (ft == nil) { return nil; };
|
||||
return resolvetagged(c, ft);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// matchspillsz — slot size for the @match_spill scratch a non-ident
|
||||
// scrutinee lands in. Mirrors cstage's `slot_size = (su->kind ==
|
||||
// TY_TAGGED) ? su->size : 16` (cmd/w6c/cgen.c cgmatch). 16 default
|
||||
// when the scrutinee type can't be resolved keeps the historical
|
||||
// alloc for non-tagged / unresolved cases. Used by both scanlocals
|
||||
// (counting) and cgmatch (emitting) per rule-10 align-to-cstage.
|
||||
fn matchspillsz(c: *cgen, scrutt: *node) i32 = {
|
||||
if (scrutt == nil) { return 16; };
|
||||
let sz: i32 = slotsize(c, scrutt);
|
||||
if (sz <= 0) { return 16; };
|
||||
return sz;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
@@ -10676,65 +10738,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, ");
|
||||
@@ -10744,15 +10756,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");
|
||||
@@ -16634,14 +16648,19 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
||||
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
||||
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
||||
// `match (non-ident)` needs an `@match_spill` scratch slot sized
|
||||
// to the scrutinee's tagged-union slot (16/24/32 for 1/2/3-word
|
||||
// payload). Mirrors cstage's `slot_size = su->size` default 16
|
||||
// in cmd/w6c/cgen.c cgmatch (task #9 align-down to cstage).
|
||||
// matchspillsz must agree with cgmatch's emit-time computation
|
||||
// for scan+emit lockstep. N_IDENT scrutinees read the slot
|
||||
// directly off the local — no spill needed.
|
||||
if (n.kind == nkind.N_MATCH) {
|
||||
let sc: *node = n.lhs;
|
||||
if (sc != nil) {
|
||||
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
||||
if (sc.kind != nkind.N_IDENT) {
|
||||
total += matchspillsz(c, matchscrutt(c, sc));
|
||||
};
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
|
||||
@@ -168,14 +168,19 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
||||
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
||||
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
||||
// `match (non-ident)` needs an `@match_spill` scratch slot sized
|
||||
// to the scrutinee's tagged-union slot (16/24/32 for 1/2/3-word
|
||||
// payload). Mirrors cstage's `slot_size = su->size` default 16
|
||||
// in cmd/w6c/cgen.c cgmatch (task #9 align-down to cstage).
|
||||
// matchspillsz must agree with cgmatch's emit-time computation
|
||||
// for scan+emit lockstep. N_IDENT scrutinees read the slot
|
||||
// directly off the local — no spill needed.
|
||||
if (n.kind == nkind.N_MATCH) {
|
||||
let sc: *node = n.lhs;
|
||||
if (sc != nil) {
|
||||
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
||||
if (sc.kind != nkind.N_IDENT) {
|
||||
total += matchspillsz(c, matchscrutt(c, sc));
|
||||
};
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1899,6 +1899,68 @@ export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// matchscrutt — resolve a non-ident match scrutinee node to its tagged
|
||||
// type (or nil if unresolvable). Mirrors cgmatch's inline scrutinee
|
||||
// type resolution; factored so cgmatch (emit) and scanlocals (count)
|
||||
// agree on the spill slot's size per the scan+emit lockstep invariant.
|
||||
// IDENT scrutinees use a different lookup path (read off the local
|
||||
// directly, no spill) so this returns nil for them too.
|
||||
fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
if (scrut == nil) { return nil; };
|
||||
let k: nkind = scrut.kind;
|
||||
if (k == nkind.N_IDENT) { return nil; };
|
||||
if (k == 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) { return resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase == nil) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) { return nil; };
|
||||
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) { return 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) { return nil; };
|
||||
return resolvetagged(c, etn);
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
let ft: *node = dotfieldtnode(c, scrut);
|
||||
if (ft == nil) { return nil; };
|
||||
return resolvetagged(c, ft);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// matchspillsz — slot size for the @match_spill scratch a non-ident
|
||||
// scrutinee lands in. Mirrors cstage's `slot_size = (su->kind ==
|
||||
// TY_TAGGED) ? su->size : 16` (cmd/w6c/cgen.c cgmatch). 16 default
|
||||
// when the scrutinee type can't be resolved keeps the historical
|
||||
// alloc for non-tagged / unresolved cases. Used by both scanlocals
|
||||
// (counting) and cgmatch (emitting) per rule-10 align-to-cstage.
|
||||
fn matchspillsz(c: *cgen, scrutt: *node) i32 = {
|
||||
if (scrutt == nil) { return 16; };
|
||||
let sz: i32 = slotsize(c, scrutt);
|
||||
if (sz <= 0) { return 16; };
|
||||
return sz;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
|
||||
@@ -8197,6 +8197,68 @@ export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// matchscrutt — resolve a non-ident match scrutinee node to its tagged
|
||||
// type (or nil if unresolvable). Mirrors cgmatch's inline scrutinee
|
||||
// type resolution; factored so cgmatch (emit) and scanlocals (count)
|
||||
// agree on the spill slot's size per the scan+emit lockstep invariant.
|
||||
// IDENT scrutinees use a different lookup path (read off the local
|
||||
// directly, no spill) so this returns nil for them too.
|
||||
fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
if (scrut == nil) { return nil; };
|
||||
let k: nkind = scrut.kind;
|
||||
if (k == nkind.N_IDENT) { return nil; };
|
||||
if (k == 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) { return resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase == nil) { return nil; };
|
||||
if (ibase.kind != nkind.N_IDENT) { return nil; };
|
||||
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) { return 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) { return nil; };
|
||||
return resolvetagged(c, etn);
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
let ft: *node = dotfieldtnode(c, scrut);
|
||||
if (ft == nil) { return nil; };
|
||||
return resolvetagged(c, ft);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// matchspillsz — slot size for the @match_spill scratch a non-ident
|
||||
// scrutinee lands in. Mirrors cstage's `slot_size = (su->kind ==
|
||||
// TY_TAGGED) ? su->size : 16` (cmd/w6c/cgen.c cgmatch). 16 default
|
||||
// when the scrutinee type can't be resolved keeps the historical
|
||||
// alloc for non-tagged / unresolved cases. Used by both scanlocals
|
||||
// (counting) and cgmatch (emitting) per rule-10 align-to-cstage.
|
||||
fn matchspillsz(c: *cgen, scrutt: *node) i32 = {
|
||||
if (scrutt == nil) { return 16; };
|
||||
let sz: i32 = slotsize(c, scrutt);
|
||||
if (sz <= 0) { return 16; };
|
||||
return sz;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
@@ -10676,65 +10738,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, ");
|
||||
@@ -10744,15 +10756,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");
|
||||
@@ -16634,14 +16648,19 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
||||
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
||||
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
||||
// `match (non-ident)` needs an `@match_spill` scratch slot sized
|
||||
// to the scrutinee's tagged-union slot (16/24/32 for 1/2/3-word
|
||||
// payload). Mirrors cstage's `slot_size = su->size` default 16
|
||||
// in cmd/w6c/cgen.c cgmatch (task #9 align-down to cstage).
|
||||
// matchspillsz must agree with cgmatch's emit-time computation
|
||||
// for scan+emit lockstep. N_IDENT scrutinees read the slot
|
||||
// directly off the local — no spill needed.
|
||||
if (n.kind == nkind.N_MATCH) {
|
||||
let sc: *node = n.lhs;
|
||||
if (sc != nil) {
|
||||
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
||||
if (sc.kind != nkind.N_IDENT) {
|
||||
total += matchspillsz(c, matchscrutt(c, sc));
|
||||
};
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
|
||||
Reference in New Issue
Block a user