diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index dbbb22d6..3eb99cfc 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -15209,7 +15209,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) Tparam *tp = (etu && etu->kind == TY_TUPLE) ? etu->params : NULL; int field_off = 0; - for (Node *nm = n->list; nm && nbinds < 8; nm = nm->next) { + for (Node *nm = n->list; nm; nm = nm->next) { + /* binds[] is sized 8; a 9th binding silently + * vanished pre-gate (rule 7 — the DEFER_MAX/ + * LOOP_MAX cap discipline). */ + if (nbinds >= 8) + fatal("for-range destructure: more " + "than 8 bindings (rule 7)"); int fsz = tp && tp->type ? (int)tp->type->size : 8; int slot_sz = (fsz < 8) ? 8 : fsz; binds[nbinds].sz = fsz; diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index a3bacb49..643e2845 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1750; +def corpuscount: i32 = 1751; def errorcount: i32 = 349; def compilecount: i32 = 21; def runcount: i32 = 209; -def runexitcount: i32 = 1171; -def nativecount: i32 = 3500; -def corpushash: str = "d52fa0df91c89bf51bca70045a2dbf39d5012ac03bf687e231240dc1c5b3fc53"; +def runexitcount: i32 = 1172; +def nativecount: i32 = 3502; +def corpushash: str = "0d0401f0d80ca682b0872343a62db4f91c0ff4c68e71f97cd5358cf92faffebc"; type directive = enum i32 { ERROR = 0, diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index efc6b9c4..9616d705 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -3892,8 +3892,14 @@ fn cgforrange(c: *cgen, n: *syntax.node) void = { let field_off: i32 = 0; let m: *syntax.node = n.list; for (m != nil) { - if (nbinds >= 8) { m = nil; } - else { + // bind_* arrays are sized 8; a 9th binding silently + // vanished pre-gate (rule 7 — the DEFER_MAX/LOOP_MAX + // cap discipline). + if (nbinds >= 8) { + let mfr: str = "for-range destructure: more than 8 bindings (rule 7)\n"; + os.write(2, mfr.ptr, mfr.len: u64); + os.exit(1); + } else { let fsz: i32 = 8; let signf: bool = false; // tp walks the N_TPARAM wrapper chain; tpt is the diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 60781ee5..a10dda8a 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -293,25 +293,40 @@ fn argtaggedwidensz(c: *cgen, arg0: *syntax.node, param: *syntax.node) i32 = { if (taggedmemargsize(ptype.type_: *syntax.tinfo) > 0) { return 0; }; let arg: *syntax.node = taggedcastpeel(c, arg0); let pslot: i32 = slotsize(c, ptype); - // Already a matching-slot tagged source → natural push, no widen - // (mirrors pushargsrev's aistagged gates: ident/call/index/dot/deref). + // "Already tagged, natural push" is TYPE equality with the param, + // never slot-size equality (#55) — a same-slot SUBSET is classified + // widen by pushargsrev, so the drain SSoT must agree word-for-word + // (word counts coincide for same-slot subsets, but a classification + // reader desyncs — the #48 float-misclassify shape). Same wsame key + // as pushargsrev's aistagged gates. + 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); + let wsame: bool = false; + 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) { if (istaggedtype(c, lc.tnode)) { - if (slotsize(c, lc.tnode) == pslot) { return 0; }; + if (wsame) { return 0; }; }; }; }; - if (taggedcallslot(c, arg) == pslot) { return 0; }; + if (taggedcallslot(c, arg) > 0) { + if (wsame) { return 0; }; + }; if (arg.kind == syntax.nkind.N_INDEX) { - if (istaggedtype(c, arg)) { if (slotsize(c, arg) == pslot) { return 0; }; }; + if (istaggedtype(c, arg)) { if (wsame) { return 0; }; }; }; if (arg.kind == syntax.nkind.N_DOT) { - if (istaggedtype(c, arg)) { if (slotsize(c, arg) == pslot) { return 0; }; }; + if (istaggedtype(c, arg)) { if (wsame) { return 0; }; }; }; if (arg.kind == syntax.nkind.N_UN && arg.op == syntax.tkind.TK_STAR) { - if (istaggedtype(c, arg)) { if (slotsize(c, arg) == pslot) { return 0; }; }; + if (istaggedtype(c, arg)) { if (wsame) { return 0; }; }; }; // The 8B nullable fold pushes one pointer word the scalar drain path // already pops correctly; only the multi-word tagged widen needs the @@ -574,6 +589,15 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, widensz = slotsize(c, ptype); let tagged: *syntax.node = resolvetagged(c, ptype); let t: i32 = taggedvariantindex(c, tagged, arg); + // tagged→tagged subset sources synthesize + // no scalar tag here (the widen store + // remaps); only a CONCRETE source with no + // variant is the rule-7 miss. + if (t < 0 && !istaggedtype(c, arg)) { + let mwt: str = "tagged widen: no variant tag for source (rule 7)\n"; + os.write(2, mwt.ptr, mwt.len: u64); + os.exit(1); + }; if (t < 0) { t = 0; }; widentag = t; }; diff --git a/test/wcc/data/subset_arg_widen/case.ww b/test/wcc/data/subset_arg_widen/case.ww new file mode 100644 index 00000000..510368d6 --- /dev/null +++ b/test/wcc/data/subset_arg_widen/case.ww @@ -0,0 +1,17 @@ +//ww:run-exit 0 +// #55 drain-SSoT leg: a same-slot subset ((bool|void) -> (i64|bool|void), +// both 16B) call arg is classified WIDEN by push AND drain — the old +// slot-size drain gates called it natural. The 0->1 tag remap through +// the widen scratch is what this pins. +package main; +type narrow = (bool | void); +type wide = (i64 | bool | void); +fn take(w: wide) i32 = { + if (w is bool) { return 7; }; + return 1; +}; +export fn main() i32 = { + let n: narrow = true; + if (take(n) != 7) { return 1; }; + return 0; +};