From cf69cf2f06467ee2cd3e0d5e5c50c9dab2da2bfd Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 19:01:05 +0900 Subject: [PATCH] w6c: key tagged natural-push call args on type equality, not slot size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pushargsrev's five aistagged gates (N_IDENT #55, N_CALL #21, N_INDEX #12, N_DOT #22a, deref #35) treated a tagged arg as already-tagged when its SLOT SIZE matched the param's. A same-slot subset union ((bool|void) into (i64|bool|void), both 16B) then natural-pushed the narrower box's words carrying SOURCE tags — no re-layout, no cg_widen_tag_remap twin — so the callee matched the wrong arm (silent: probes exited 10/90 where cstage exits 30/27). cstage keys widen detection on type equality (cgen.c:10000 same = (pu == au) || type_eq) and routes every non-same tagged source through the zeroed scratch + tag remap; the slot-DIFFER wwstage path already mirrored that byte-identically, so the fix computes cstage's same check once (wsame) and replaces each slot-size test with it. This also erases the last known cs!=ww shape divergence (the 16B-local staging vs direct-push frame delta on prefix subsets). 8 subsetwiden_* fixtures own the class: call-result/ident/str-payload /mid-arg remap (the wrong-arm shapes), prefix (the shape-divergence repro), and bigslot/return-pos/struct-24-to-32 sibling guards. Corpus pin 1740/343/22/209/1166/3480. --- internal/wwfixture/types.ww | 8 +-- selfhost/cmd/wcc/cgenutil.ww | 68 +++++++++++-------- test/wcc/data/subsetwiden_bigslot/case.ww | 11 +++ .../data/subsetwiden_callarg_prefix/case.ww | 11 +++ .../data/subsetwiden_callarg_remap/case.ww | 11 +++ .../data/subsetwiden_identarg_remap/case.ww | 13 ++++ .../wcc/data/subsetwiden_midarg_remap/case.ww | 11 +++ test/wcc/data/subsetwiden_return_pos/case.ww | 12 ++++ .../data/subsetwiden_strpayload_remap/case.ww | 11 +++ .../data/subsetwiden_struct2432_remap/case.ww | 15 ++++ 10 files changed, 139 insertions(+), 32 deletions(-) create mode 100644 test/wcc/data/subsetwiden_bigslot/case.ww create mode 100644 test/wcc/data/subsetwiden_callarg_prefix/case.ww create mode 100644 test/wcc/data/subsetwiden_callarg_remap/case.ww create mode 100644 test/wcc/data/subsetwiden_identarg_remap/case.ww create mode 100644 test/wcc/data/subsetwiden_midarg_remap/case.ww create mode 100644 test/wcc/data/subsetwiden_return_pos/case.ww create mode 100644 test/wcc/data/subsetwiden_strpayload_remap/case.ww create mode 100644 test/wcc/data/subsetwiden_struct2432_remap/case.ww diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index fe172a38..63a5b15d 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1732; +def corpuscount: i32 = 1740; def errorcount: i32 = 343; def compilecount: i32 = 22; def runcount: i32 = 209; -def runexitcount: i32 = 1158; -def nativecount: i32 = 3464; -def corpushash: str = "df3862c647aae744b549089ab4a180b36371fe898f5996c13099f52da203265e"; +def runexitcount: i32 = 1166; +def nativecount: i32 = 3480; +def corpushash: str = "a2f2514f07ca4e20457331e30f051394da3e59fcfa8f5f6ec3b33d9c14490354"; type directive = enum i32 { ERROR = 0, diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 9dc41749..7f6d25f2 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -493,39 +493,51 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, let ptype: *syntax.node = param.lhs; if (istaggedtype(c, ptype)) { let aistagged: bool = false; + // "Already tagged, natural push" is TYPE equality + // with the param, never slot-size equality: a + // same-slot SUBSET ((bool|void) → (i64|bool|void), + // both 16B) still needs the scratch re-layout + + // tag remap below — the old slot-keyed gates + // natural-pushed the narrower box's words with + // SOURCE tags, so the callee matched the wrong + // arm. Mirrors cstage's widen detection + // (cmd/w6c/cgen.c:10000 `same = (pu == au) || + // type_eq(p->type, at)`). + let wsame: bool = false; + let wpt: *syntax.tinfo = ptype.type_: *syntax.tinfo; + let wat: *syntax.tinfo = arg.type_: *syntax.tinfo; + let wpu: *syntax.tinfo = tichase(wpt); + let wau: *syntax.tinfo = tichase(wat); + if (wpu != nil && wpu == wau) { wsame = true; }; + if (!wsame && wpt != nil && wat != nil) { + if (syntax.typeeq(wpt, wat)) { wsame = true; }; + }; if (arg.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, arg.str); if (lc != nil) { - // #55: only treat a tagged ident as - // "already tagged" (natural push, no remap) - // when its slot MATCHES the param. On slot- - // DIFFER the source is a NARROWER union widened - // into a wider one — fall to the widen scratch - // + tag-remap below (the slot-gated INDEX/DOT/ - // STAR arms' twin). Pre-#55 the ungated TRUE - // natural-pushed the narrower box's words with - // no remap (aligned-tag LUCK, misaligned-tag - // wrong). cstage routes every tagged source + // #55: any non-same tagged local — slot- + // differ or same-slot subset — falls to the + // widen scratch + tag-remap below. cstage + // routes every non-same tagged source // through cg_widen_tagged_store (cmd/w6c/ // cgen.c:2982 src_is_tagged). if (istaggedtype(c, lc.tnode)) { - if (slotsize(c, lc.tnode) == slotsize(c, ptype)) { + if (wsame) { aistagged = true; }; }; }; }; - // #21: a CALL returning a tagged-union must - // skip widening — cgexpr leaves AX=tag, - // DX=word0, CX=word1, R8=word2 per the - // tagged-return ABI; the widening branch would - // treat AX as a concrete payload and silently - // drop DX/CX/R8. Restrict to the matching-slot - // case (mirrors cstage type_eq at - // cmd/w6c/cgen.c:4216-4221); tagged-source - // widening into a wider slot is out of scope. - if (taggedcallslot(c, arg) == slotsize(c, ptype)) { - aistagged = true; + // #21: an exact-type CALL result skips widening — + // cgexpr leaves AX=tag, DX=word0, CX=word1, + // R8=word2 per the tagged-return ABI and the + // natural push keeps that cursor. A non-same + // (subset) call result routes through the widen + // scratch, whose cursor arm spills + tag-remaps. + if (taggedcallslot(c, arg) > 0) { + if (wsame) { + aistagged = true; + }; }; // #12: N_INDEX of a sum-typed slice element — // cgindex emits the same AX/DX/CX/R8 tagged ABI. @@ -533,13 +545,13 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, // hardcodes the param's first-variant tag and // the callee reads a fixed arm on garbage. // #60: arg.type_ is the checker-stamped element - // tinfo (check.ww indexresult); istaggedtype/ - // slotsize read .type_, so feed the N_INDEX node + // tinfo (check.ww indexresult); istaggedtype + // reads .type_, so feed the N_INDEX node // directly. cstage reads the element via // base->type->sub (cmd/w6c/cgen.c:3518). if (arg.kind == syntax.nkind.N_INDEX) { if (istaggedtype(c, arg)) { - if (slotsize(c, arg) == slotsize(c, ptype)) { + if (wsame) { aistagged = true; }; }; @@ -555,7 +567,7 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, // check is type-keyed on args[i]->type). if (arg.kind == syntax.nkind.N_DOT) { if (istaggedtype(c, arg)) { - if (slotsize(c, arg) == slotsize(c, ptype)) { + if (wsame) { aistagged = true; }; }; @@ -564,11 +576,11 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, // tagged box too (mem-based, any size) // — without this gate the widening // scalar branch boxed the box. Same - // slotsize key as the N_INDEX/N_DOT + // type key as the N_INDEX/N_DOT // stamped-carrier arms above. if (arg.kind == syntax.nkind.N_UN && arg.op == syntax.tkind.TK_STAR) { if (istaggedtype(c, arg)) { - if (slotsize(c, arg) == slotsize(c, ptype)) { + if (wsame) { aistagged = true; }; }; diff --git a/test/wcc/data/subsetwiden_bigslot/case.ww b/test/wcc/data/subsetwiden_bigslot/case.ww new file mode 100644 index 00000000..5a8fe668 --- /dev/null +++ b/test/wcc/data/subsetwiden_bigslot/case.ww @@ -0,0 +1,11 @@ +//ww:run-exit 30 +package main; +fn mk(v: bool) (i64 | bool) = { return v; }; +fn pick(r: (i64 | bool | str)) i32 = { + match (r) { + case let n: i64 => return 10; + case let b: bool => return 30; + case let s: str => return 40; + }; +}; +export fn main() i32 = { return pick(mk(true)); }; diff --git a/test/wcc/data/subsetwiden_callarg_prefix/case.ww b/test/wcc/data/subsetwiden_callarg_prefix/case.ww new file mode 100644 index 00000000..65de7ea8 --- /dev/null +++ b/test/wcc/data/subsetwiden_callarg_prefix/case.ww @@ -0,0 +1,11 @@ +//ww:run-exit 30 +package main; +fn mk(v: bool) (i64 | bool) = { return v; }; +fn pick(r: (i64 | bool | void)) i32 = { + match (r) { + case let n: i64 => return 10; + case let b: bool => return 30; + case void => return 20; + }; +}; +export fn main() i32 = { return pick(mk(true)); }; diff --git a/test/wcc/data/subsetwiden_callarg_remap/case.ww b/test/wcc/data/subsetwiden_callarg_remap/case.ww new file mode 100644 index 00000000..41dfb93d --- /dev/null +++ b/test/wcc/data/subsetwiden_callarg_remap/case.ww @@ -0,0 +1,11 @@ +//ww:run-exit 30 +package main; +fn mk(v: bool) (bool | void) = { return v; }; +fn pick(r: (i64 | bool | void)) i32 = { + match (r) { + case let n: i64 => return 10; + case let b: bool => return 30; + case void => return 20; + }; +}; +export fn main() i32 = { return pick(mk(true)); }; diff --git a/test/wcc/data/subsetwiden_identarg_remap/case.ww b/test/wcc/data/subsetwiden_identarg_remap/case.ww new file mode 100644 index 00000000..bc95a4a9 --- /dev/null +++ b/test/wcc/data/subsetwiden_identarg_remap/case.ww @@ -0,0 +1,13 @@ +//ww:run-exit 30 +package main; +fn pick(r: (i64 | bool | void)) i32 = { + match (r) { + case let n: i64 => return 10; + case let b: bool => return 30; + case void => return 20; + }; +}; +export fn main() i32 = { + let v: (bool | void) = true; + return pick(v); +}; diff --git a/test/wcc/data/subsetwiden_midarg_remap/case.ww b/test/wcc/data/subsetwiden_midarg_remap/case.ww new file mode 100644 index 00000000..044a7660 --- /dev/null +++ b/test/wcc/data/subsetwiden_midarg_remap/case.ww @@ -0,0 +1,11 @@ +//ww:run-exit 27 +package main; +fn mk(v: bool) (bool | void) = { return v; }; +fn pick3(a: i32, r: (i64 | bool | void), b: i32) i32 = { + match (r) { + case let n: i64 => return 90; + case let b2: bool => return a*10 + b; + case void => return 91; + }; +}; +export fn main() i32 = { return pick3(2, mk(true), 7); }; diff --git a/test/wcc/data/subsetwiden_return_pos/case.ww b/test/wcc/data/subsetwiden_return_pos/case.ww new file mode 100644 index 00000000..f8ffcc49 --- /dev/null +++ b/test/wcc/data/subsetwiden_return_pos/case.ww @@ -0,0 +1,12 @@ +//ww:run-exit 30 +package main; +fn mk(v: bool) (bool | void) = { return v; }; +fn wide(v: bool) (i64 | bool | void) = { return mk(v); }; +fn pick(r: (i64 | bool | void)) i32 = { + match (r) { + case let n: i64 => return 10; + case let b: bool => return 30; + case void => return 20; + }; +}; +export fn main() i32 = { return pick(wide(true)); }; diff --git a/test/wcc/data/subsetwiden_strpayload_remap/case.ww b/test/wcc/data/subsetwiden_strpayload_remap/case.ww new file mode 100644 index 00000000..e7faecbc --- /dev/null +++ b/test/wcc/data/subsetwiden_strpayload_remap/case.ww @@ -0,0 +1,11 @@ +//ww:run-exit 30 +package main; +fn mk(s: str) (str | void) = { return s; }; +fn pick(r: (i64 | str | void)) i32 = { + match (r) { + case let n: i64 => return 10; + case let s: str => { if (len(s) != 5) { return 4; }; return 30; }; + case void => return 20; + }; +}; +export fn main() i32 = { return pick(mk("hello")); }; diff --git a/test/wcc/data/subsetwiden_struct2432_remap/case.ww b/test/wcc/data/subsetwiden_struct2432_remap/case.ww new file mode 100644 index 00000000..685b5e22 --- /dev/null +++ b/test/wcc/data/subsetwiden_struct2432_remap/case.ww @@ -0,0 +1,15 @@ +//ww:run-exit 30 +package main; +type pair = struct { a: i64, b: i64 }; +fn mk(x: i64) (pair | void) = { + let p: pair; p.a = x; p.b = x + 1; + return p; +}; +fn pick(r: (str | pair | void)) i32 = { + match (r) { + case let s: str => return 10; + case let p: pair => { if (p.a != 5) { return 4; }; if (p.b != 6) { return 5; }; return 30; }; + case void => return 20; + }; +}; +export fn main() i32 = { return pick(mk(5)); };