From 785fe342fa723bff6bc1f7b5a32c7cfdf8ad6696 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 9 Jun 2026 17:29:11 +0900 Subject: [PATCH] wcc/check: #24 reject composite-element tuple (array/struct/tuple), declared+inferred, both stages A tuple whose element chases to TY_ARRAY/STRUCT/TUPLE (>8B) silently miscompiled both stages: t.0[i] read segfaulted and construction dropped the payload into the 8B slot. Reject the type at resolution (DISP-B); faithful inline layout deferred to #60. cstage resolve_type N_TTUPLE (declared) + N_TUPLE expr (inferred literal, was a cstage-only silent miscompile + cs!=ww asymmetry); wwstage tinfofornode covers both. test/wcc/832 + 941 migrated. --- cmd/wcc/check.c | 26 ++++++++ selfhost/cmd/w6c/main.combined.ww | 12 ++++ selfhost/cmd/wcc/check.ww | 12 ++++ selfhost/cmd/wwdump/main.combined.ww | 12 ++++ test/wcc/832_tuple_elem_overlong.c | 98 ++++++++++++++++++++-------- test/wcc/941_tuple_slot_layout_run.c | 17 +++-- 6 files changed, 144 insertions(+), 33 deletions(-) diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 25324151..7317126b 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -725,6 +725,18 @@ resolve_type(Checker *c, Node *n) * packed-tuple miscompile family (#32/#33/#48). */ if (tp->type) { Type *eu = type_chase_named(tp->type); + /* #24: a composite element (array/struct/nested + * tuple >8B) cannot ride the 8B cursor slot — the + * #60 layout drops it on construction and segvs on + * t.N[i] read. Reject until #60/DISP-A inlines it. + * Hare allows it (harec type_store.c anon-struct). */ + if (eu && (eu->kind == TY_ARRAY + || eu->kind == TY_STRUCT + || eu->kind == TY_TUPLE)) + err(c, n->pos, "tuple element must be a " + "scalar, str, slice, or tagged-union " + "(composite element deferred to task " + "#60)"); if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE || eu->kind == TY_TAGGED)) @@ -2182,6 +2194,20 @@ cexpr(Checker *c, Node *n) Type *ed = type_default(tp->type); if (ed && ed->align > al) al = ed->align; Type *eu = type_chase_named(ed); + /* #24: an INFERRED composite element (array/struct/ + * nested tuple >8B) drops on construction + segvs on + * the t.N read, exactly as the explicit N_TTUPLE twin + * (resolve_type) — wwstage's tinfofornode choke-point + * catches declared AND inferred, so cstage must reject + * the inferred literal here too (rule-10 symmetry). + * Reject until #60/DISP-A inlines it. */ + if (eu && (eu->kind == TY_ARRAY + || eu->kind == TY_STRUCT + || eu->kind == TY_TUPLE)) + err(c, n->pos, "tuple element must be a " + "scalar, str, slice, or tagged-union " + "(composite element deferred to task " + "#60)"); if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE || eu->kind == TY_TAGGED)) sz += (eu->size + 7) & ~(u64)7; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 5ec14204..8ac8b845 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -12245,6 +12245,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { let pt: *tinfo = tinfofornode(c, p.lhs); // #62/#69: tuple-member value cycle — loud, cstage twin. if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; }; + // #24: a composite element (array/struct/nested tuple + // >8B) cannot ride the 8B cursor slot — the #60 layout + // drops it on construction and segvs on t.N[i] read. + // Reject until #60/DISP-A inlines it; cstage twin. + let cu: *tinfo = tichase(pt); + if (cu != nil && (cu.kind == tykind.TY_ARRAY + || cu.kind == tykind.TY_STRUCT + || cu.kind == tykind.TY_TUPLE)) { + cerr("error: tuple element must be a scalar, str, slice, or tagged-union (composite element deferred to task #60)\n"); + c.errs += 1; + pt = c.tc.tyerr; + }; let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!; if (teh == nil) { teh = te; } else { tet.tnext = te; }; tet = te; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index ecc792ae..7bf9eb21 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -1964,6 +1964,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { let pt: *tinfo = tinfofornode(c, p.lhs); // #62/#69: tuple-member value cycle — loud, cstage twin. if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; }; + // #24: a composite element (array/struct/nested tuple + // >8B) cannot ride the 8B cursor slot — the #60 layout + // drops it on construction and segvs on t.N[i] read. + // Reject until #60/DISP-A inlines it; cstage twin. + let cu: *tinfo = tichase(pt); + if (cu != nil && (cu.kind == tykind.TY_ARRAY + || cu.kind == tykind.TY_STRUCT + || cu.kind == tykind.TY_TUPLE)) { + cerr("error: tuple element must be a scalar, str, slice, or tagged-union (composite element deferred to task #60)\n"); + c.errs += 1; + pt = c.tc.tyerr; + }; let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!; if (teh == nil) { teh = te; } else { tet.tnext = te; }; tet = te; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e103128a..edddf8e2 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -12245,6 +12245,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { let pt: *tinfo = tinfofornode(c, p.lhs); // #62/#69: tuple-member value cycle — loud, cstage twin. if (circularnamed(c, pt, p.lhs)) { pt = c.tc.tyerr; }; + // #24: a composite element (array/struct/nested tuple + // >8B) cannot ride the 8B cursor slot — the #60 layout + // drops it on construction and segvs on t.N[i] read. + // Reject until #60/DISP-A inlines it; cstage twin. + let cu: *tinfo = tichase(pt); + if (cu != nil && (cu.kind == tykind.TY_ARRAY + || cu.kind == tykind.TY_STRUCT + || cu.kind == tykind.TY_TUPLE)) { + cerr("error: tuple element must be a scalar, str, slice, or tagged-union (composite element deferred to task #60)\n"); + c.errs += 1; + pt = c.tc.tyerr; + }; let te: *ttupleelem = alloc(ttupleelem{type_=pt, offset=slottotal, tnext=nil})!; if (teh == nil) { teh = te; } else { tet.tnext = te; }; tet = te; diff --git a/test/wcc/832_tuple_elem_overlong.c b/test/wcc/832_tuple_elem_overlong.c index ab327477..c5fd8bf3 100644 --- a/test/wcc/832_tuple_elem_overlong.c +++ b/test/wcc/832_tuple_elem_overlong.c @@ -20,6 +20,17 @@ * asm; selfhost has no overlong tuple-elements, so 990-997 byte-id is * untouched. Do NOT chase message parity. * + * #24 (DISP-B broad reject, rob spec .ai/rob-24-spec.md): a tuple whose + * ELEMENT is a composite (array / struct / nested-tuple >8B) cannot ride the + * 8B cursor slot (#60 layout) — it silently DROPS on construction and SEGVs + * on the t.N[i] read. Both stages now REJECT such a type at N_TTUPLE + * resolution (kind ∈ {TY_ARRAY, TY_STRUCT, TY_TUPLE} after TY_NAMED chase), + * converting two silent miscompiles into one loud checker error. This FLIPS + * the former tuple_arr_exact / tuple_nested_exact positive controls to the + * neg table (their types are now outlawed) and adds slice/str/tagged-element + * positive controls proving DISP-B does NOT over-reject the inline-header + * kinds. Full inline support deferred to task #60 / DISP-A. + * * neg row | shape | gate * -------------------+----------------------------------------------+-------- * tuple_arr_over | let t:([2]int,i32)=([1,2,3],5) | b. FAIL @@ -27,12 +38,16 @@ * tuple_in_tuple | let t:([2]int,([2]int,i32))=([..],([1,2,3],.))| #26 FAIL * tuple_return_over | fn()([2]int,i32){return([1,2,3],5)} | #25 FAIL * tuple_return_nested| fn()([2]int,([2]int,i32)){return(..,([..3],.))| #25 FAIL + * tuple_arr_exact | let t:([2]int,i32)=([1,2],5) | #24 FAIL + * tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)) | #24 FAIL + * tuple_struct_elem | type P=struct{x:int}; let t:(P,i32)=(P{x=1},5)| #24 FAIL * * pos row | shape | want * -------------------+----------------------------------------------+------ - * tuple_arr_exact | let t:([2]int,i32)=([1,2],5); t.1 | 5 * tuple_scalar | let t:(i32,i32)=(1,2); t.1 | 2 - * tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)); t.0 | 9 + * tuple_slice_elem | let t:([]u8,i32)=(a,5); t.1 | 5 + * tuple_str_elem | let t:(str,i32)=("hi",7); t.1 | 7 + * tuple_tagged_elem | let t:((void|size),i32)=(3,9); t.1 | 9 */ #include #include @@ -53,22 +68,7 @@ runwait(const char *cmd) struct row { const char *label; const char *src; int want; }; static const struct row rows[] = { - /* exact-length array element in a tuple still ACCEPTS, builds + runs. - * Readout is the scalar t.1 (=5), NOT t.0[1]: indexing an array - * element THROUGH a tuple is a separate pre-existing cgen read bug - * that segfaults on BOTH stages (w6c byte-identical, so not a #20 - * regression — filed). The point of this control is that the #20 - * over-fill walk does NOT over-reject the valid exact-length tuple- - * with-array-element: the build must succeed and the program run. */ - { "tuple_arr_exact", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet t: ([2]int, i32) = ([1, 2], 5);\n" - "\treturn t.1;\n" - "};\n", - 5 }, - - /* a scalar-only tuple has no array element — the #20 walk no-ops. */ + /* a scalar-only tuple — all elements ride the 8B slot. */ { "tuple_scalar", "package main;\n" "export fn main() i32 = {\n" @@ -77,19 +77,36 @@ static const struct row rows[] = { "};\n", 2 }, - /* #26 positive control — a VALID nested tuple with an exact-length - * inner [2]int must NOT be over-rejected by the new recursion arm. - * Readout is the top-level scalar t.0 (=9), NOT a leaf through the - * inner tuple / array element (those hit pre-existing cgen read bugs - * that miscompile on BOTH stages — filed, byte-id-blind, see the - * tuple_arr_exact note). The point here is that checktuplearrfits - * recurses the inner ([2]int,i32), runs the count check, and lets the - * exact-length build proceed: build succeeds + program runs. */ - { "tuple_nested_exact", + /* #24 positive control — a SLICE element is DISP-B-allowed (its 24B + * header rides the cursor). Must NOT be over-rejected. Readout is the + * scalar t.1 (=5). */ + { "tuple_slice_elem", "package main;\n" "export fn main() i32 = {\n" - "\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n" - "\treturn t.0;\n" + "\tlet hb: [8]u8;\n" + "\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n" + "\tlet t: ([]u8, i32) = (a, 5);\n" + "\treturn t.1;\n" + "};\n", + 5 }, + + /* #24 positive control — a STR element is DISP-B-allowed (24B header). + * Readout is the scalar t.1 (=7). */ + { "tuple_str_elem", + "package main;\n" + "export fn main() i32 = {\n" + "\tlet t: (str, i32) = (\"hi\", 7);\n" + "\treturn t.1;\n" + "};\n", + 7 }, + + /* #24 positive control — a TAGGED-UNION element is DISP-B-allowed (its + * tag+payload box rides the slot). Readout is the scalar t.1 (=9). */ + { "tuple_tagged_elem", + "package main;\n" + "export fn main() i32 = {\n" + "\tlet t: ((void | size), i32) = (3, 9);\n" + "\treturn t.1;\n" "};\n", 9 }, }; @@ -137,6 +154,29 @@ static const char *neg[] = { "\tlet t = f();\n" "\treturn t.0[0]: i32;\n" "};\n", + /* tuple_arr_exact (#24) — was a GREEN positive control; the DISP-B + * broad reject now OUTLAWS an ARRAY tuple element (silent-drop on + * construction + segv on t.0[i] read). MIGRATED to the neg table. */ + "package main;\n" + "export fn main() i32 = {\n" + "\tlet t: ([2]int, i32) = ([1, 2], 5);\n" + "\treturn t.1;\n" + "};\n", + /* tuple_nested_exact (#24) — was a GREEN positive control; a NESTED + * TUPLE element is now outlawed by DISP-B. MIGRATED to the neg table. */ + "package main;\n" + "export fn main() i32 = {\n" + "\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n" + "\treturn t.0;\n" + "};\n", + /* tuple_struct_elem (#24) — a STRUCT tuple element is now a checker + * loud (was a cgen "unsupported field-read shape" loud). */ + "package main;\n" + "type P = struct { x: int };\n" + "export fn main() i32 = {\n" + "\tlet t: (P, i32) = (P { x = 1 }, 5);\n" + "\treturn t.1;\n" + "};\n", }; static int diff --git a/test/wcc/941_tuple_slot_layout_run.c b/test/wcc/941_tuple_slot_layout_run.c index e88d251a..463d2682 100644 --- a/test/wcc/941_tuple_slot_layout_run.c +++ b/test/wcc/941_tuple_slot_layout_run.c @@ -1368,8 +1368,11 @@ static const struct row rows[] = { /* rule-7 (ken demand 1): a NESTED composite element (tuple-in- * tuple here) occupies more than the one GP word the restage walk * counts — pre-guard the checker accepted it and it ran WRONG - * (inner words skewed; wwstage SIGSEGV'd). Loud until a consumer - * motivates the wiring. */ + * (inner words skewed; wwstage SIGSEGV'd). #24 now outlaws the + * composite-element tuple TYPE at resolution (both stages), so the + * reject fires here before the later "tuple arg element kind + * unsupported" arg-pass loud — experr migrated to the #24 text. + * Full inline support deferred to task #60. */ { "t2_reject_nested_elem_arg", "package main;\n" "fn f(t: ((i64, i64), i64)) i64 = {\n" @@ -1381,7 +1384,7 @@ static const struct row rows[] = { " if (f(t) != 9) { return 1; };\n" " return 0;\n" "};\n", 0, - K_BUILDERR, "tuple arg element kind unsupported" }, + K_BUILDERR, "tuple element must be a scalar" }, /* variadic-of-tuples ((i64,i64)...): `ts[0].0` is an indexed-slice * tuple-element FIELD read — #121 leg (a) now resolves it (was LOUD * "unsupported field-read shape"; the dispatch fired only for an @@ -1920,13 +1923,19 @@ static const struct row rows[] = { "};\n", 0, K_RUN, NULL }, /* nested tuple element: slot rule gives an inner tuple one 8B * eightbyte (tuple_eslot parity) — pins the outer layout. */ + /* #24: an INFERRED nested-tuple element `((4,2),36)` drops the inner + * tuple on construction (build-proved 76 not 42 reading t.0) and is now + * outlawed on BOTH stages — same DISP-B reject as 832's tuple_nested_- + * exact, but via the inferred-literal path (check.c N_TUPLE expr + + * wwstage tinfofornode). Migrated K_RUN -> K_BUILDERR. Full inline + * support deferred to task #60. */ { "t44_inferred_nested", "package main;\n" "export fn main() i32 = {\n" " let t = ((4, 2), 36);\n" " if (t.1 != 36) { return 1; };\n" " return 0;\n" - "};\n", 0, K_RUN, NULL }, + "};\n", 0, K_BUILDERR, "tuple element must be a scalar" }, /* #57: an IN-CAP tuple literal's cursor fill keyed each element on * its STAMPED type (element-constructed, check.c N_TUPLE), so a * declared-TAGGED element from a concrete rvalue (`(5: size, 9)`)