wcc/cgen: S1 loud-stop subset-cast tagged widen in let+return (wwstage align to cstage #35)

A widening sub-union cast (return/let `v: inner` into a union whose member
is the nested `inner`) skipped the nested widen arm and emitted tag=0 (a
2nd-variant value returned the wrong payload). cstage loud-stops #35;
wwstage now matches: cgwidentaggedstorebp louds on the subset cast, and
cgreturn routes genuine-widening tagged returns (rhs TY_TAGGED, ru1!=fu1)
through the widener choke-point so the same loud fires. Same-type returns
stay on the proven passthrough. Faithful inner-tag->outer-index remap
deferred. test/wcc/835 S1 row.
This commit is contained in:
2026-06-09 17:32:06 +09:00
parent 64606de8af
commit 7e19d282f4
5 changed files with 164 additions and 1 deletions

View File

@@ -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 <stdio.h>
@@ -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