From 83025b03a635339c69658f8f5fe08bbfa71d058f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 8 Jun 2026 23:12:06 +0900 Subject: [PATCH] =?UTF-8?q?wcc:=20#99=20alias-of-tuple=20=E2=80=94=20chase?= =?UTF-8?q?=20TY=5FNAMED=20in=20tuple=20coercion=20(cstage)=20+=20param=20?= =?UTF-8?q?spill=20(wwstage)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit type pair = (int, int); let x: pair = (3, 4) -- an alias of a tuple initialized from an untyped literal, and passing such a value to a fn -- was a both-stage bug, mirror-twins of the same TY_NAMED-not-chased root: cstage CHECKER over-rejected the init (not assignable to declared pair): type.c's tuple-assignable arm gated on the un-chased dst kind, so a TY_NAMED alias skipped the per-element untyped->int coercion the direct tuple path applies. Fix: chase TY_NAMED both sides (mirrors the #258 slice-borrow arm). Direct and typed-alias tuples already worked; only alias+untyped was rejected. wwstage CGEN dropped the second word of an alias-tuple fn-arg: the tuple-param spill at cgendecl.ww gated on the syntactic N_TTUPLE, so an alias param (N_TNAME) fell to the scalar path and spilled one slot -> t.1 read frame garbage. Fix: chase the alias via aliaslookup to the resolved N_TTUPLE and spill all its slots. cstage cgen was already correct -- the bug was checker-only there. Converges cs==ww byte-id. One commit: same construct, the two halves must ship together (either alone leaves cs!=ww). test/wcc/826 (init/fn-arg/return, 2-field byte-id); test/wcc/944 4 rows graduated err->run-correct. byte-id 990-997 8/8. --- Makefile | 7 + cmd/wcc/type.c | 23 +- selfhost/cmd/w6c/main.combined.ww | 19 +- selfhost/cmd/wcc/cgendecl.ww | 19 +- selfhost/cmd/wwdump/main.combined.ww | 19 +- test/wcc/826_alias_tuple_coerce.c | 360 +++++++++++++++++++++++++++ test/wcc/944_alias_cgen_b5_run.c | 64 ++--- 7 files changed, 458 insertions(+), 53 deletions(-) create mode 100644 test/wcc/826_alias_tuple_coerce.c diff --git a/Makefile b/Makefile index 9a1aeb28..bf775ccd 100644 --- a/Makefile +++ b/Makefile @@ -254,6 +254,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_inferred_global_let \ $(BIN)/test_inferred_float_global \ $(BIN)/test_errfirst_union_try \ + $(BIN)/test_alias_tuple_coerce \ $(BIN)/test_slice_str_global_zero \ $(BIN)/test_slice_literal_global \ $(BIN)/test_global_arr_elem_field \ @@ -686,6 +687,12 @@ $(BIN)/test_errfirst_union_try: test/wcc/825_errfirst_union_try.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_alias_tuple_coerce: test/wcc/826_alias_tuple_coerce.c $(BIN)/ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_def_arr_infer_len: test/wcc/814_def_arr_infer_len.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index eb4e4fc5..b3798fbc 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -398,14 +398,23 @@ type_assignable(Type *dst, Type *src) return 1; } - /* Tuple-to-tuple: element-wise assignable. */ - if (dst->kind == TY_TUPLE && src->kind == TY_TUPLE) { - Tparam *pa = dst->params, *pb = src->params; - while (pa && pb) { - if (!type_assignable(pa->type, pb->type)) return 0; - pa = pa->next; pb = pb->next; + /* Tuple-to-tuple: element-wise assignable. Chase a TY_NAMED alias on + * either side first (#99): `type pair=(int,int); let x: pair = (3,4)` + * was rejected because dst->kind is TY_NAMED, skipping this arm — the + * direct-tuple path coerces the untyped elements fine. Aliases are + * transparent; mirrors the #258 slice-borrow arm just below which + * already type_chase_named's both sides. */ + { + Type *du = type_chase_named(dst); + Type *su = type_chase_named(src); + if (du && su && du->kind == TY_TUPLE && su->kind == TY_TUPLE) { + Tparam *pa = du->params, *pb = su->params; + while (pa && pb) { + if (!type_assignable(pa->type, pb->type)) return 0; + pa = pa->next; pb = pb->next; + } + return pa == NULL && pb == NULL; } - return pa == NULL && pb == NULL; } /* #258: implicit [N]T → []T array-to-slice borrow. Hare admits an diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index befaa6aa..170c4951 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -37878,7 +37878,22 @@ fn cgfnparams(c: *cgen, params: *node) void = { p = p.next; continue; }; - if (p.lhs != nil) { if (p.lhs.kind == nkind.N_TTUPLE) { + // #99: chase a TY_NAMED alias (multi-level) to its + // underlying tuple — the param twin of the cstage type.c + // type_chase_named tuple-arm. A bare (i64,i64) is N_TTUPLE + // (no chase); `type tp=(i64,i64)` is an N_TNAME resolved via + // aliaslookup. Without the chase the alias fell to the scalar + // path → 1 slot, SI dropped, t.1 garbage. Slot size + element + // walk source the RESOLVED node; localadd keeps the declared + // p.lhs so field reads chase identically to cstage (byte-id). + let tt99: *node = nil; + if (p.lhs != nil) { + tt99 = p.lhs; + for (tt99 != nil && tt99.kind == nkind.N_TNAME) { + tt99 = aliaslookup(c, tt99.str); + }; + }; + if (p.lhs != nil) { if (tt99 != nil && tt99.kind == nkind.N_TTUPLE) { // #163: tuple PARAM receive (param twin of #164's // return). Walk the tuple's elements over the SysV // arg cursor — a float reads its XMM (X0..X7), @@ -37890,7 +37905,7 @@ fn cgfnparams(c: *cgen, params: *node) void = { // partial-spill stitch is out of scope (twin of #164). let off: i32 = localadd(c, nm, slotsize(c, p.lhs), p.lhs); let eoff: i32 = 0; - let te: *node = p.lhs.list; + let te: *node = tt99.list; for (te != nil) { let et: *node = te.lhs; if (isfloattype(c, et)) { diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 5f931d39..2d1fdc37 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -103,7 +103,22 @@ fn cgfnparams(c: *cgen, params: *node) void = { p = p.next; continue; }; - if (p.lhs != nil) { if (p.lhs.kind == nkind.N_TTUPLE) { + // #99: chase a TY_NAMED alias (multi-level) to its + // underlying tuple — the param twin of the cstage type.c + // type_chase_named tuple-arm. A bare (i64,i64) is N_TTUPLE + // (no chase); `type tp=(i64,i64)` is an N_TNAME resolved via + // aliaslookup. Without the chase the alias fell to the scalar + // path → 1 slot, SI dropped, t.1 garbage. Slot size + element + // walk source the RESOLVED node; localadd keeps the declared + // p.lhs so field reads chase identically to cstage (byte-id). + let tt99: *node = nil; + if (p.lhs != nil) { + tt99 = p.lhs; + for (tt99 != nil && tt99.kind == nkind.N_TNAME) { + tt99 = aliaslookup(c, tt99.str); + }; + }; + if (p.lhs != nil) { if (tt99 != nil && tt99.kind == nkind.N_TTUPLE) { // #163: tuple PARAM receive (param twin of #164's // return). Walk the tuple's elements over the SysV // arg cursor — a float reads its XMM (X0..X7), @@ -115,7 +130,7 @@ fn cgfnparams(c: *cgen, params: *node) void = { // partial-spill stitch is out of scope (twin of #164). let off: i32 = localadd(c, nm, slotsize(c, p.lhs), p.lhs); let eoff: i32 = 0; - let te: *node = p.lhs.list; + let te: *node = tt99.list; for (te != nil) { let et: *node = te.lhs; if (isfloattype(c, et)) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 416c0f56..623be831 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -37878,7 +37878,22 @@ fn cgfnparams(c: *cgen, params: *node) void = { p = p.next; continue; }; - if (p.lhs != nil) { if (p.lhs.kind == nkind.N_TTUPLE) { + // #99: chase a TY_NAMED alias (multi-level) to its + // underlying tuple — the param twin of the cstage type.c + // type_chase_named tuple-arm. A bare (i64,i64) is N_TTUPLE + // (no chase); `type tp=(i64,i64)` is an N_TNAME resolved via + // aliaslookup. Without the chase the alias fell to the scalar + // path → 1 slot, SI dropped, t.1 garbage. Slot size + element + // walk source the RESOLVED node; localadd keeps the declared + // p.lhs so field reads chase identically to cstage (byte-id). + let tt99: *node = nil; + if (p.lhs != nil) { + tt99 = p.lhs; + for (tt99 != nil && tt99.kind == nkind.N_TNAME) { + tt99 = aliaslookup(c, tt99.str); + }; + }; + if (p.lhs != nil) { if (tt99 != nil && tt99.kind == nkind.N_TTUPLE) { // #163: tuple PARAM receive (param twin of #164's // return). Walk the tuple's elements over the SysV // arg cursor — a float reads its XMM (X0..X7), @@ -37890,7 +37905,7 @@ fn cgfnparams(c: *cgen, params: *node) void = { // partial-spill stitch is out of scope (twin of #164). let off: i32 = localadd(c, nm, slotsize(c, p.lhs), p.lhs); let eoff: i32 = 0; - let te: *node = p.lhs.list; + let te: *node = tt99.list; for (te != nil) { let et: *node = te.lhs; if (isfloattype(c, et)) { diff --git a/test/wcc/826_alias_tuple_coerce.c b/test/wcc/826_alias_tuple_coerce.c new file mode 100644 index 00000000..a3906346 --- /dev/null +++ b/test/wcc/826_alias_tuple_coerce.c @@ -0,0 +1,360 @@ +/* + * 826_alias_tuple_coerce — an alias of a tuple type, initialised with an + * UNTYPED tuple literal, must coerce element-wise just like a direct tuple + * (task #99). `type pair = (int, int); let x: pair = (3, 4)` was rejected by + * cstage — `init (untyped_int, untyped_int) not assignable to declared pair` + * — while the direct `let x: (int,int) = (3,4)` and the typed-alias + * `let x: pair = (3:int, 4:int)` both compiled. wwstage already accepted+ran + * the untyped-alias form correctly. + * + * ROOT (cstage only): cmd/wcc/type.c type_assignable's tuple-to-tuple arm + * gated on the UN-chased `dst->kind == TY_TUPLE`. For `pair` (a TY_NAMED + * alias) dst->kind is TY_NAMED, so the arm was skipped and the per-element + * untyped->int coercion never ran. The fix chases TY_NAMED on both sides + * before the tuple check (mirroring the #258 slice-borrow arm just below), + * so an alias-of-tuple gets the same element-wise coercion as a direct tuple. + * type_assignable is the shared assignability predicate, so INIT, FN-ARG and + * RETURN positions all flip reject->accept in one spot. cstage-only — + * wwstage check.ww/cgen are unchanged. + * + * The coercion still type-checks per element, so a genuinely mismatched + * element type or wrong arity STILL louds — those are the negative controls. + * + * row | shape | want + * ------------+------------------------------------------------+------ + * init | type pair=(int,int); let x:pair=(3,4); x.0+x.1 | 7 + * fnarg | fn g(p:pair); g((3,4)); p.0+p.1 | 7 + * ret | fn f() pair = { return (3,4); }; r.0+r.1 | 7 + * mixed | type box=(int,u16); let x:box=(3,4); coerce ea | 7 + * ctrl_dir | direct (int,int)=(3,4) (no-regress) | 7 + * ctrl_typed | alias (3:int,4:int) (no-regress) | 7 + * + * init / fnarg / ret / mixed are the mutation-sane rows: pre-fix cstage + * build-FAILS them (the untyped->alias coercion was rejected); pre-fix + * wwstage built but a two-field alias-tuple fn-arg read garbage (the param + * spill half). Post-fix BOTH stages build+run the want. + * + * Negative controls (cstage must STILL loud — don't over-accept): + * neg_type | let x:pair=(3,"s") (element type mismatch) + * neg_arity | let x:pair=(3,4,5) (wrong arity) + * + * byte-id: the init, fnarg and ret rows are asserted byte-identical + * (cstage==wwstage .s). #99 shipped in two halves — the cstage type.c chase + * (init/fnarg/ret reject->accept) AND the wwstage cgendecl.ww tuple-PARAM + * spill chase (the param twin: an alias param gated on the SYNTACTIC + * N_TTUPLE node fell through to the scalar path, spilling one arg reg and + * dropping the second tuple word, so a two-field read of an alias-tuple + * fn-arg returned garbage in wwstage). With the wwstage half landed the + * fnarg/ret prologues converge to cstage, so both read both fields and + * match byte-for-byte. mixed (an init row) stays runtime-only. + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want; int byteid; }; + +static const struct row rows[] = { + /* init — THE construct: untyped tuple literal -> alias, read both + * fields. byte-id clean across stages. */ + { "init", + "package main;\n" + "type pair = (int, int);\n" + "export fn main() i32 = {\n" + "\tlet x: pair = (3, 4);\n" + "\treturn (x.0 + x.1): i32;\n" + "};\n", + 7, 1 }, + + /* fnarg — untyped tuple literal coerced into an alias-typed param at + * the call site, reading BOTH fields. The wwstage tuple-PARAM spill + * chase (cgendecl.ww, #99 second half) sizes the alias param slot 16B + * and spills both arg regs, so p.0+p.1 is correct and byte-identical + * to cstage. */ + { "fnarg", + "package main;\n" + "type pair = (int, int);\n" + "fn g(p: pair) i32 = {\n" + "\treturn (p.0 + p.1): i32;\n" + "};\n" + "export fn main() i32 = {\n" + "\treturn g((3, 4));\n" + "};\n", + 7, 1 }, + + /* ret — untyped tuple literal coerced into the declared alias return + * type; the receiving alias-tuple let reads both fields, byte-id. */ + { "ret", + "package main;\n" + "type pair = (int, int);\n" + "fn f() pair = {\n" + "\treturn (3, 4);\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet r: pair = f();\n" + "\treturn (r.0 + r.1): i32;\n" + "};\n", + 7, 1 }, + + /* mixed — heterogeneous element types under the alias; each untyped + * element coerces to its declared field type. runtime-only. */ + { "mixed", + "package main;\n" + "type box = (int, u16);\n" + "export fn main() i32 = {\n" + "\tlet x: box = (3, 4);\n" + "\treturn (x.0 + (x.1: int)): i32;\n" + "};\n", + 7, 0 }, + + /* ctrl_dir — direct (un-aliased) tuple init; already worked, guards + * against a regression in the original tuple path. */ + { "ctrl_dir", + "package main;\n" + "export fn main() i32 = {\n" + "\tlet x: (int, int) = (3, 4);\n" + "\treturn (x.0 + x.1): i32;\n" + "};\n", + 7, 0 }, + + /* ctrl_typed — alias init with already-typed elements (no untyped + * coercion); already worked via the named-arm type_eq. */ + { "ctrl_typed", + "package main;\n" + "type pair = (int, int);\n" + "export fn main() i32 = {\n" + "\tlet x: pair = (3: int, 4: int);\n" + "\treturn (x.0 + x.1): i32;\n" + "};\n", + 7, 0 }, +}; + +/* neg — cstage must STILL reject these (the coercion type-checks per + * element; a type mismatch or wrong arity is not assignable). cstage-only: + * wwstage pre-existingly over-accepts a mismatched tuple init (a separate + * wwstage bug, out of #99 scope), so the negative is asserted against the + * cstage driver only — the side #99 touches. */ +static const char *neg[] = { + /* neg_type — second element str vs declared int. */ + "package main;\n" + "type pair = (int, int);\n" + "export fn main() i32 = {\n" + "\tlet x: pair = (3, \"s\");\n" + "\treturn x.0: i32;\n" + "};\n", + + /* neg_arity — three elements vs the two-element alias. */ + "package main;\n" + "type pair = (int, int);\n" + "export fn main() i32 = {\n" + "\tlet x: pair = (3, 4, 5);\n" + "\treturn x.0: i32;\n" + "};\n", +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[64], tmpdir[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/atc_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/atc_%d_d_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); rmdir(tmpdir); + return got; +} + +/* build_should_fail — the negative control must error on `driver`; returns + * 0 when the build correctly FAILS, non-zero when it wrongly succeeded. */ +static int +build_should_fail(const char *driver, const char *src, int i) +{ + char s[64], tmpdir[64], cmd[1024]; + snprintf(s, sizeof s, "/tmp/atc_neg_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/atc_neg_%d_d_%d", getpid(), i); + + FILE *f = fopen(s, "wb"); + if (!f) return -1; + fputs(src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, s); + int rc = runwait(cmd); + unlink(s); + const char *base = strrchr(s, '/'); + base = base ? base + 1 : s; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + unlink(outbin); + rmdir(tmpdir); + return rc == 0 ? -1 : 0; /* build must NOT succeed */ +} + +/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[64], cs[64], ws[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/atc_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/atc_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/atc_asm_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + + FILE *fc = fopen(cs, "rb"); + FILE *fw = fopen(ws, "rb"); + int rc = 0; + if (!fc || !fw) { + rc = -1; + } else { + for (;;) { + int a = fgetc(fc); + int b = fgetc(fw); + if (a != b) { rc = -1; break; } + if (a == EOF) break; + } + } + if (fc) fclose(fc); + if (fw) fclose(fw); + if (rc != 0) + fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", + r->label); + unlink(src); unlink(cs); unlink(ws); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char cdrv[1024]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + char wdrv[1024]; + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + struct { const char *name; const char *path; int gated_on_existence; } + drivers[] = { + { "cstage", cdrv, 0 }, + { "wwstage", wdrv, 1 }, + { NULL, NULL, 0 }, + }; + + int n = (int)(sizeof rows / sizeof rows[0]); + int nneg = (int)(sizeof neg / sizeof neg[0]); + int total = 0, fail = 0; + + for (int d = 0; drivers[d].name; d++) { + if (drivers[d].gated_on_existence + && access(drivers[d].path, X_OK) != 0) { + fprintf(stderr, "alias_tuple_coerce: skip %s (no %s)\n", + drivers[d].name, drivers[d].path); + continue; + } + for (int i = 0; i < n; i++) { + int got = run_driver(drivers[d].path, &rows[i], i); + total++; + if (got != rows[i].want) { + fprintf(stderr, + "alias_tuple_coerce[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + } + + /* Negative controls — cstage must STILL reject (don't over-accept). + * cstage-only: the #99 fix is cstage-side, and wwstage pre-existingly + * over-accepts a mismatched tuple init (out-of-scope wwstage bug). */ + for (int i = 0; i < nneg; i++) { + total++; + if (build_should_fail(cdrv, neg[i], i) != 0) { + fprintf(stderr, + "alias_tuple_coerce[cstage][neg_%d]: built (should loud)\n", + i); + fail++; + } + } + + /* byte-id — only rows flagged byteid (the INIT form). */ + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + if (!rows[i].byteid) continue; + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) + fail++; + } + } + + if (fail) { + fprintf(stderr, + "alias_tuple_coerce: %d/%d fixtures failed\n", fail, total); + return 1; + } + printf("alias_tuple_coerce: %d/%d ok\n", total, total); + return 0; +} diff --git a/test/wcc/944_alias_cgen_b5_run.c b/test/wcc/944_alias_cgen_b5_run.c index 15d6d3bd..c3b5241e 100644 --- a/test/wcc/944_alias_cgen_b5_run.c +++ b/test/wcc/944_alias_cgen_b5_run.c @@ -50,23 +50,14 @@ * | R4-blessed train; #100's ww twin | * | chase closed the window — LOUD-HOLD | * | pair, twin texts share the experr | err/err - * tuparg_bound99 / | kb5_tuparg(1): literal named-tuple | - * tuparg1_bound99 | arg — cs checker-rejects (#86-kin), | - * | WW RUNS WRONG exit 1 (task #99, | - * | metric-1 ww side). Dual-cell pin | err/1 - * tuparg_cast_bound99 | kb5_tuparg_c: cast spelling dodges | - * | the cs checker; the :249 chase made | - * | cs run CORRECT (LIVE c1 graduation, | - * | C1-CORR-3 — corrects rob R3 + ken | - * | FLAG-2); ww still WRONG exit 1 | - * | (#99). Two-key pin: cs-0 earned + | - * | ww-1 pinned-wrong; byte-id waived | - * | until #99 re-pins to full byte-id | 0/1 - * tupglobal_bound86 | kb5_tupglobal: alias-tuple global — | - * | cs checker-dead (#86), ww runs 0. | - * | #85 type_unwrap close is SITE- | - * | closure with zero live graduations | - * | — this row pins the bound | err/0 + * tuparg_bound99 / | alias-tuple fn-arg (2-lvl / 1-lvl | + * tuparg1_bound99 / | / cast spelling) + alias-tuple | + * tuparg_cast_bound99/| global. #99 GRADUATED: cstage | + * tupglobal_bound99 | type.c chase accepts the coercion, | + * | wwstage cgendecl.ww spill chase | + * | sizes the alias param 16B (was | + * | dropping SI → t.1 garbage). All | + * | now 0/0 byte-id | 0/0 * ---- c2 (#73 graduation: tripwire deleted, 6 fu gates chased) -- * g73_idxstore / | slice / str / tagged 2-lvl alias | * g73_strfield / | field at the indexed-elem STORE | @@ -365,10 +356,12 @@ static const struct row rows[] = { " return 0;\n" "};\n", 0, 0, K_BUILDERR, "has a str/slice/struct/tagged element" }, - /* node_tuplearg:249 pin (ken FLAG-2): the LITERAL spelling has - * NO GREEN TARGET — cs checker-rejects even at 1 level (#86-kin) - * while WW ACCEPTS AND RUNS WRONG (exit 1, task #99 metric-1 - * ww-side). Cells pinned as observed. */ + /* #99 GRADUATION: both halves landed — the cstage type.c + * type_chase_named tuple-arm (init/fn-arg untyped->alias coercion + * reject->accept) AND the wwstage cgendecl.ww tuple-PARAM spill + * chase (alias param fell to the scalar path, dropped SI, t.1 + * garbage). All four rows now build+run 0 on BOTH stages and the + * .s is byte-identical (full 0/0 byte-id, the designed path). */ { "tuparg_bound99", "package main;\n" "type tp0 = (i64, i64);\n" @@ -378,8 +371,7 @@ static const struct row rows[] = { " let t: tp = (4, 9);\n" " if (f(t) != 13) { return 1; };\n" " return 0;\n" - "};\n", 0, 1, K_CSERR_WWRUN, - "not assignable" }, /* ww: task #99 */ + "};\n", 0, 0, K_RUN, NULL }, { "tuparg1_bound99", /* 1-LEVEL identical — not alias-depth */ "package main;\n" "type tp = (i64, i64);\n" @@ -388,15 +380,10 @@ static const struct row rows[] = { " let t: tp = (4, 9);\n" " if (f(t) != 13) { return 1; };\n" " return 0;\n" - "};\n", 0, 1, K_CSERR_WWRUN, - "not assignable" }, /* ww: task #99 */ - /* C1-CORR-3 (corrects rob R3 + ken FLAG-2): the CAST spelling - * dodges the cs checker, and the :249 chase gave it a LIVE cs - * graduation — cs now classifies the alias-tuple arg and runs - * CORRECT. ww still runs WRONG (#99). Two-key pin: cs-0 earned - * + ww-1 pinned OBSERVED-WRONG; byte-id waived — when #99's ww - * fix lands, the ww cell reds and forces the re-pin to full 0/0 - * byte-id (the designed graduation path). */ + "};\n", 0, 0, K_RUN, NULL }, + /* cast spelling — was the LIVE-cs / wrong-ww K_RUN_NOID pin; the + * wwstage spill chase reds the old ww-1 cell and graduates it to + * full 0/0 byte-id alongside its siblings. */ { "tuparg_cast_bound99", "package main;\n" "type tp0 = (i64, i64);\n" @@ -406,12 +393,10 @@ static const struct row rows[] = { " let t: tp = (4, 9): tp;\n" " if (f(t) != 13) { return 1; };\n" " return 0;\n" - "};\n", 0, 1, K_RUN_NOID, NULL }, /* ww+byte-id: task #99 */ - /* #85 SITE-closure bound: type_unwrap's consumers (:14716/:14907 - * tuple-global layout walks) are checker-DEAD on cs for alias - * tuples (#86 upstream) — the c1 chase graduates NOTHING live - * here; ww runs the global correctly. */ - { "tupglobal_bound86", + "};\n", 0, 0, K_RUN, NULL }, + /* alias-tuple module-global — the cstage chase accepts the global + * init and ww already read it correctly; now both 0/0 byte-id. */ + { "tupglobal_bound99", "package main;\n" "type tp0 = (i64, i64);\n" "type tp = tp0;\n" @@ -419,8 +404,7 @@ static const struct row rows[] = { "export fn main() i32 = {\n" " if (G.0 + G.1 != 13) { return 1; };\n" " return 0;\n" - "};\n", 0, 0, K_CSERR_WWRUN, - "not assignable" }, /* cs: task #86 */ + "};\n", 0, 0, K_RUN, NULL }, /* ---- c2: #73 graduation — the F1 tripwire's containment * replaced by the designed chase (close-by-construction). Each * row was the loud "#73" fatal on cs at the c1 tip (ww ok/0);