w6c: forrange bind cap loud; drain widen gates keyed on type-eq (#55)

Two silent seams in one sweep. The for-range destructure silently
dropped the 9th+ binding in both stages (bind arrays are sized 8);
the cap now hard-stops per the DEFER_MAX/LOOP_MAX discipline (the
shape is unreachable today -- every wide-tuple construction path
already loud-stops). argtaggedwidensz, the drain-side SSoT for
widened call args, still keyed "natural push" on slot-size equality
while pushargsrev flipped to type equality with #55 -- a same-slot
subset was classified widen by push, natural by drain (counts
coincide today; a classification reader desyncs, the #48 shape).
Both sides now share the wsame key; the widen-branch tag miss for a
concrete source joins the task-2 loud-stop family (tagged subset
sources keep t=0 -- their widentag is never read, the scratch store
remaps).

Lead 17 (enum fold-failure prev+1) DISPROVED: enumvalfold gates
every member init in the checker and its fold set is op-for-op
equal to cgen's enumevalmember, so the fallback only runs inside
already-failing compiles.
This commit is contained in:
2026-08-09 00:52:27 +09:00
parent 973dd964c9
commit f06c95e66b
5 changed files with 67 additions and 14 deletions

View File

@@ -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

View File

@@ -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;
};