selfhost+test: graduate wwstage sum-typed N_INDEX call-arg to tagged ABI (#12)

pushargsrev's widening detection was N_IDENT-only — N_INDEX of a
sum-typed slice element fell through to the scalar widening branch,
which hardcoded the param's first-variant tag (MOVQ $1, AX) and
pushed AX as a single scalar word. Callees that match-dispatched
on the runtime tag always ran the static-guess arm on garbage.

cstage knew the arg's type via check.c so its widen[] flag stayed
off and the natural-push tagged-arg arm pushed CX/DX/AX (high → low)
high → low. wwstage now mirrors via two narrow arms in pushargsrev:
the aistagged guard treats N_INDEX-of-sum-typed-element matching
the param slot as already-tagged, and the natural-push fallthrough
emits PUSHQ CX / DX / AX for the same shape. Both arms gate on
istaggedtype(indexvaluetnode(arg)) so literal- and ident-source
sum args stay on their existing paths.

Sentinel 749_sumtype_forward table-drives the three forward shapes
(N_INDEX, N_IDENT, literal) and asserts per-stage runtime plus a
byte-id window over the callsite asm.

Combined.ww regen for wwdump_ww and w6c_ww follows the cgen source
change; smoke.combined.ww unaffected.

Tests: 123/123 pass; bootstrap fixed point holds (ww2==ww3==ww4).
This commit is contained in:
2026-05-19 03:09:34 +09:00
parent c305b04cb5
commit cbf10427df
5 changed files with 427 additions and 0 deletions

View File

@@ -7292,6 +7292,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
aistagged = true;
};
// #12: N_INDEX of a sum-typed slice element —
// cgindex emits the same AX/DX/CX/R8 tagged ABI.
// Without this gate the widening scalar branch
// hardcodes the param's first-variant tag and
// the callee reads a fixed arm on garbage.
if (arg.kind == nkind.N_INDEX) {
let etn: *node = indexvaluetnode(c, arg);
if (etn != nil) {
if (istaggedtype(c, etn)) {
if (slotsize(c, etn) == slotsize(c, ptype)) {
aistagged = true;
};
};
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
@@ -7591,6 +7606,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("\tPUSHQ\tAX\n");
return rest + tcs / 8;
};
// #12: N_INDEX of a sum-typed slice element. cgindex above left
// the tagged-CALL ABI in AX/DX/CX/R8; the bare PUSHQ AX below
// would only carry the tag word and drop the payload.
if (arg.kind == nkind.N_INDEX) {
let etn: *node = indexvaluetnode(c, arg);
if (etn != nil) {
if (istaggedtype(c, etn)) {
let isz: i32 = slotsize(c, etn);
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
emitline("\tPUSHQ\tAX\n");
return rest + isz / 8;
};
};
};
emitline("\tPUSHQ\tAX\n");
return rest + 1;
};

View File

@@ -152,6 +152,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
aistagged = true;
};
// #12: N_INDEX of a sum-typed slice element —
// cgindex emits the same AX/DX/CX/R8 tagged ABI.
// Without this gate the widening scalar branch
// hardcodes the param's first-variant tag and
// the callee reads a fixed arm on garbage.
if (arg.kind == nkind.N_INDEX) {
let etn: *node = indexvaluetnode(c, arg);
if (etn != nil) {
if (istaggedtype(c, etn)) {
if (slotsize(c, etn) == slotsize(c, ptype)) {
aistagged = true;
};
};
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
@@ -451,6 +466,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("\tPUSHQ\tAX\n");
return rest + tcs / 8;
};
// #12: N_INDEX of a sum-typed slice element. cgindex above left
// the tagged-CALL ABI in AX/DX/CX/R8; the bare PUSHQ AX below
// would only carry the tag word and drop the payload.
if (arg.kind == nkind.N_INDEX) {
let etn: *node = indexvaluetnode(c, arg);
if (etn != nil) {
if (istaggedtype(c, etn)) {
let isz: i32 = slotsize(c, etn);
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
emitline("\tPUSHQ\tAX\n");
return rest + isz / 8;
};
};
};
emitline("\tPUSHQ\tAX\n");
return rest + 1;
};

View File

@@ -7292,6 +7292,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
aistagged = true;
};
// #12: N_INDEX of a sum-typed slice element —
// cgindex emits the same AX/DX/CX/R8 tagged ABI.
// Without this gate the widening scalar branch
// hardcodes the param's first-variant tag and
// the callee reads a fixed arm on garbage.
if (arg.kind == nkind.N_INDEX) {
let etn: *node = indexvaluetnode(c, arg);
if (etn != nil) {
if (istaggedtype(c, etn)) {
if (slotsize(c, etn) == slotsize(c, ptype)) {
aistagged = true;
};
};
};
};
if (!aistagged) {
widensz = slotsize(c, ptype);
let tagged: *node = resolvetagged(c, ptype);
@@ -7591,6 +7606,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("\tPUSHQ\tAX\n");
return rest + tcs / 8;
};
// #12: N_INDEX of a sum-typed slice element. cgindex above left
// the tagged-CALL ABI in AX/DX/CX/R8; the bare PUSHQ AX below
// would only carry the tag word and drop the payload.
if (arg.kind == nkind.N_INDEX) {
let etn: *node = indexvaluetnode(c, arg);
if (etn != nil) {
if (istaggedtype(c, etn)) {
let isz: i32 = slotsize(c, etn);
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
emitline("\tPUSHQ\tAX\n");
return rest + isz / 8;
};
};
};
emitline("\tPUSHQ\tAX\n");
return rest + 1;
};