diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 8b85015d..8eee2e83 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -20350,6 +20350,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s // NAMED tinfo (check.ww:1160-1173) so typeeq hits // the a==b fast path. let castisdst: bool = false; + let castsubset: bool = false; let castrhs: *node = src.rhs; if (castrhs != nil) { let castt: *tinfo = castrhs.type_: *tinfo; @@ -20361,10 +20362,30 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s } else { if (castu.kind == tykind.TY_TAGGED) { if (typeeq(castt, dst)) { castisdst = true; + } else { + castsubset = true; }; }; }; }; }; + // S1/#35 (rule 7): a widen-SUBSET cast — the cast target + // is a TAGGED union that is NOT dst (castu tagged && + // !typeeq(castt, dst)). The inner-variant tag is never + // remapped to dst's index, so the scalar arm below would + // emit tag=0: a SILENT mis-tag on any 2nd-variant value + // (census S1: `return true: inner`, inner=(int|bool) into + // (int|bool|str), ran the int arm not the bool arm). Loud- + // align to cstage cgen.c:2721-2723. The !inneristagged gate + // matches the src=inner collapse below — a tagged inner is + // handled by the #218 nested arm. Faithful remap (read inner + // tag, inner-idx→outer-idx) = the #23/#40 widen-subset + // feature, deferred post-CSP (needs the nominal variant- + // remap table). + if (castsubset && !inneristagged) { + let m35: str = "#35: tagged cast source shape unwired at the widen subset arm (rule 7)\n"; + os.write(2, m35.ptr, m35.len: u64); + os.exit(1); + }; if (castisdst && !inneristagged) { src = inner; }; @@ -34980,6 +35001,29 @@ fn cgreturn(c: *cgen, n: *node) void = { if (taggedmemread(c, rhs)) { needswiden = true; }; + // S1/#35: a GENUINE-WIDENING tagged source + // (stamped type_ TY_TAGGED and != fnret; + // exact-type rides forwardtagged/plain above) + // routes through the widener — tag remap for + // ident/call (#218), #35 widen-subset loud for + // cast/dot. Mirrors cstage cgreturn istagged→ + // cg_widen_tagged_store (cmd/w6c/cgen.c:13008- + // 13011 → :2721). The ru1 != fu1 exclusion keeps + // EXACT-type tagged casts on cstage's passthrough + // (forwardtagged's own ru==fu equality) so byte-id + // holds. + let ru1: *tinfo = rhs.type_: *tinfo; + ru1 = tichase(ru1); + let fu1: *tinfo = nil; + if (c.fnret != nil) { + fu1 = c.fnret.type_: *tinfo; + fu1 = tichase(fu1); + }; + if (ru1 != nil && fu1 != nil) { + if (ru1.kind == tykind.TY_TAGGED && ru1 != fu1) { + needswiden = true; + }; + }; }; }; if (needswiden) { diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 974f6dff..2d9f7428 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1037,6 +1037,29 @@ fn cgreturn(c: *cgen, n: *node) void = { if (taggedmemread(c, rhs)) { needswiden = true; }; + // S1/#35: a GENUINE-WIDENING tagged source + // (stamped type_ TY_TAGGED and != fnret; + // exact-type rides forwardtagged/plain above) + // routes through the widener — tag remap for + // ident/call (#218), #35 widen-subset loud for + // cast/dot. Mirrors cstage cgreturn istagged→ + // cg_widen_tagged_store (cmd/w6c/cgen.c:13008- + // 13011 → :2721). The ru1 != fu1 exclusion keeps + // EXACT-type tagged casts on cstage's passthrough + // (forwardtagged's own ru==fu equality) so byte-id + // holds. + let ru1: *tinfo = rhs.type_: *tinfo; + ru1 = tichase(ru1); + let fu1: *tinfo = nil; + if (c.fnret != nil) { + fu1 = c.fnret.type_: *tinfo; + fu1 = tichase(fu1); + }; + if (ru1 != nil && fu1 != nil) { + if (ru1.kind == tykind.TY_TAGGED && ru1 != fu1) { + needswiden = true; + }; + }; }; }; if (needswiden) { diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index ba68eea0..5f2bb547 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -3838,6 +3838,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s // NAMED tinfo (check.ww:1160-1173) so typeeq hits // the a==b fast path. let castisdst: bool = false; + let castsubset: bool = false; let castrhs: *node = src.rhs; if (castrhs != nil) { let castt: *tinfo = castrhs.type_: *tinfo; @@ -3849,10 +3850,30 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s } else { if (castu.kind == tykind.TY_TAGGED) { if (typeeq(castt, dst)) { castisdst = true; + } else { + castsubset = true; }; }; }; }; }; + // S1/#35 (rule 7): a widen-SUBSET cast — the cast target + // is a TAGGED union that is NOT dst (castu tagged && + // !typeeq(castt, dst)). The inner-variant tag is never + // remapped to dst's index, so the scalar arm below would + // emit tag=0: a SILENT mis-tag on any 2nd-variant value + // (census S1: `return true: inner`, inner=(int|bool) into + // (int|bool|str), ran the int arm not the bool arm). Loud- + // align to cstage cgen.c:2721-2723. The !inneristagged gate + // matches the src=inner collapse below — a tagged inner is + // handled by the #218 nested arm. Faithful remap (read inner + // tag, inner-idx→outer-idx) = the #23/#40 widen-subset + // feature, deferred post-CSP (needs the nominal variant- + // remap table). + if (castsubset && !inneristagged) { + let m35: str = "#35: tagged cast source shape unwired at the widen subset arm (rule 7)\n"; + os.write(2, m35.ptr, m35.len: u64); + os.exit(1); + }; if (castisdst && !inneristagged) { src = inner; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 516adf28..c7808377 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -20350,6 +20350,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s // NAMED tinfo (check.ww:1160-1173) so typeeq hits // the a==b fast path. let castisdst: bool = false; + let castsubset: bool = false; let castrhs: *node = src.rhs; if (castrhs != nil) { let castt: *tinfo = castrhs.type_: *tinfo; @@ -20361,10 +20362,30 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s } else { if (castu.kind == tykind.TY_TAGGED) { if (typeeq(castt, dst)) { castisdst = true; + } else { + castsubset = true; }; }; }; }; }; + // S1/#35 (rule 7): a widen-SUBSET cast — the cast target + // is a TAGGED union that is NOT dst (castu tagged && + // !typeeq(castt, dst)). The inner-variant tag is never + // remapped to dst's index, so the scalar arm below would + // emit tag=0: a SILENT mis-tag on any 2nd-variant value + // (census S1: `return true: inner`, inner=(int|bool) into + // (int|bool|str), ran the int arm not the bool arm). Loud- + // align to cstage cgen.c:2721-2723. The !inneristagged gate + // matches the src=inner collapse below — a tagged inner is + // handled by the #218 nested arm. Faithful remap (read inner + // tag, inner-idx→outer-idx) = the #23/#40 widen-subset + // feature, deferred post-CSP (needs the nominal variant- + // remap table). + if (castsubset && !inneristagged) { + let m35: str = "#35: tagged cast source shape unwired at the widen subset arm (rule 7)\n"; + os.write(2, m35.ptr, m35.len: u64); + os.exit(1); + }; if (castisdst && !inneristagged) { src = inner; }; @@ -34980,6 +35001,29 @@ fn cgreturn(c: *cgen, n: *node) void = { if (taggedmemread(c, rhs)) { needswiden = true; }; + // S1/#35: a GENUINE-WIDENING tagged source + // (stamped type_ TY_TAGGED and != fnret; + // exact-type rides forwardtagged/plain above) + // routes through the widener — tag remap for + // ident/call (#218), #35 widen-subset loud for + // cast/dot. Mirrors cstage cgreturn istagged→ + // cg_widen_tagged_store (cmd/w6c/cgen.c:13008- + // 13011 → :2721). The ru1 != fu1 exclusion keeps + // EXACT-type tagged casts on cstage's passthrough + // (forwardtagged's own ru==fu equality) so byte-id + // holds. + let ru1: *tinfo = rhs.type_: *tinfo; + ru1 = tichase(ru1); + let fu1: *tinfo = nil; + if (c.fnret != nil) { + fu1 = c.fnret.type_: *tinfo; + fu1 = tichase(fu1); + }; + if (ru1 != nil && fu1 != nil) { + if (ru1.kind == tykind.TY_TAGGED && ru1 != fu1) { + needswiden = true; + }; + }; }; }; if (needswiden) { diff --git a/test/wcc/835_nested_union_int_box.c b/test/wcc/835_nested_union_int_box.c index 581403a9..291f5790 100644 --- a/test/wcc/835_nested_union_int_box.c +++ b/test/wcc/835_nested_union_int_box.c @@ -15,11 +15,23 @@ * (enum)=true) — A3 is the trap: dropping it would flip an enum-variant * union to reject while cstage accepts. wwstage-ONLY; check.c unchanged. * + * S1 / #35 (ken census .ai/miscompile-census.md) — a widen-SUBSET cast in + * a tagged construct: `return true: inner` where inner=(int|bool) boxed + * into outer=(int|bool|str). The cast target is a TAGGED union that is NOT + * dst, and the inner-variant tag is never remapped to dst's index, so ww + * cgen's scalar arm emitted tag=0 — running the int arm on a bool value + * (census S1 returned 70, expected 11). cstage loud-stops at cgen.c:2721- + * 2723 (#35). wwstage gains the twin in cgwidentaggedstorebp (cgenutil.ww). + * A 2ND-VARIANT value (bool) is mandatory: `5:inner` is benign (int is + * variant 0 in both, tag 0 coincidentally right). Faithful inner->outer + * tag remap = the #23/#40 widen-subset feature, deferred post-CSP. + * * neg row | shape | gate * -------------+------------------------------------------------+------ * R1_nested | inner=(int|bool); outer=(inner|str); ret 5 | FAIL * R2_noslot | u=(str|bool); ret 5 | FAIL * R3_struct | A=struct{v:int}; u=(A|str); ret 5 | FAIL + * S1_subcast | return true: inner (inner=(int|bool)->outer) | FAIL * * pos row | shape | want * -------------+------------------------------------------------+------ @@ -30,7 +42,7 @@ * A5_subset | sub=(int|uint); sup=(int|uint|str); ret x | 0 * * Diagnostic TEXT is byte-id-blind (#23 routes through errnotassign "not - * assignable"); both stages REJECT and emit no asm. + * assignable"; S1 emits the #35 line); both stages REJECT and emit no asm. * selfhost has no such construct, so 990-997 byte-id is untouched. */ #include @@ -127,6 +139,25 @@ static const char *neg[] = { "type u = (A | str);\n" "fn mk() u = { return 5; };\n" "export fn main() i32 = { return 0; };\n", + /* S1 / #35 — widen-SUBSET cast in a RETURN, 2nd-variant (bool) value + * (census sx4). ww cgen's cgreturn formerly skipped the widener for an + * N_CAST-tagged source (needswiden=false) -> plain cgexpr -> tag=0 + * SILENT (ran the int arm -> 70, want 11). Now the genuine-widening + * (ru!=fnret) needswiden arm routes it through cgwidentaggedstore -> + * the #35 widen-subset loud-stop. Both stages reject. */ + "package main;\n" + "type inner = (int | bool);\n" + "type outer = (int | bool | str);\n" + "fn mk() outer = { return true: inner; };\n" + "export fn main() i32 = {\n" + "\tlet v = mk();\n" + "\tmatch (v) {\n" + "\tcase let n: int => return 70;\n" + "\tcase let b: bool => return 11;\n" + "\tcase let s: str => return 22;\n" + "\t};\n" + "\treturn 99;\n" + "};\n", }; static int