From 24e02b259ccdb4354b859271d0cba8d87bcf7380 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 09:08:01 +0900 Subject: [PATCH] =?UTF-8?q?wcc/check:=20inferred-let=20tuple=20literal=20c?= =?UTF-8?q?arries=20its=20slot-layout=20size=20=E2=80=94=200-size=20local?= =?UTF-8?q?=20smashed=20saved=20BP/RIP=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The N_TUPLE expr arm built its TY_TUPLE with size 0 (only the annotated N_TTUPLE resolve_type route computed the layout), and type_default passes TY_TUPLE through, so an inferred `let t = (4: size, 2: size)` planted a 0-size local at offset 0 — the element stores landed on the saved BP/RIP and main segfaulted on RET (cstage; the arg shape instead fell to the global-symbol path and link-failed). wwstage (exprtype N_TUPLE -> tinfofornode) was runtime-correct throughout — cstage aligns UP to it; all fixed shapes are now byte-id. Slot rule mirrors the N_TTUPLE twin and cgen tuple_eslot, with untyped elements sized at their type_default (element types stay untyped for the consumer-side assignability contract). 7 table rows in 941 pin the class (cast/bare/mixed/float elems, destructure-from-local, call-arg, nested); each fails at master e8977a4 cstage (segfault or link-fail + byte-id NO). --- cmd/wcc/check.c | 24 ++++++++- test/wcc/941_tuple_slot_layout_run.c | 76 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 45cf6ae6..f2b755a1 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -2040,17 +2040,39 @@ cexpr(Checker *c, Node *n) } case N_TUPLE: { /* keep untyped element types; assignability is checked - * element-wise at the consumer (return / mlet / massign). */ + * element-wise at the consumer (return / mlet / massign). + * Size/align must still be planted HERE: an inferred + * `let t = (a, b)` takes this type verbatim as the local's + * type (type_default passes TY_TUPLE through) and cgen's + * N_LET sizes the frame slot from ->size — a 0-size tuple + * planted the local at offset 0, over the saved BP/RIP + * (task #44 segfault; wwstage is the correct reference: + * check.ww exprtype N_TUPLE routes through tinfofornode). + * Slot rule mirrors the N_TTUPLE twin (resolve_type) and + * cgen tuple_eslot; untyped elements are sized at their + * default (tuple_eslot's UNTYPED_STR precedent). */ Type *t = newtype(c->a, TY_TUPLE); Tparam *head = NULL, *tail = NULL; + u64 sz = 0, al = 1; for (Node *e = n->list; e; e = e->next) { Tparam *tp = amalloc(c->a, sizeof *tp); tp->type = cexpr(c, e); + Type *ed = type_default(tp->type); + if (ed && ed->align > al) al = ed->align; + Type *eu = (ed && ed->kind == TY_NAMED) + ? ed->under : ed; + if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE + || eu->kind == TY_TAGGED)) + sz += (eu->size + 7) & ~(u64)7; + else if (eu == NULL || eu->kind != TY_VOID) + sz += 8; /* sizelint-ok: the slot IS the 8B eightbyte */ if (head == NULL) head = tp; else tail->next = tp; tail = tp; } t->params = head; + t->size = sz; + t->align = al; return n->type = t; } default: diff --git a/test/wcc/941_tuple_slot_layout_run.c b/test/wcc/941_tuple_slot_layout_run.c index ad21c147..44ead195 100644 --- a/test/wcc/941_tuple_slot_layout_run.c +++ b/test/wcc/941_tuple_slot_layout_run.c @@ -1849,6 +1849,82 @@ static const struct row rows[] = { " return 0;\n" "};\n", 0, K_BUILDERR, "tagged cast source shape unwired" }, + + /* ---- task #44: INFERRED-let tuple literal — cstage's N_TUPLE + * expr type carried size 0 (only the annotated N_TTUPLE route + * computed the slot layout), so cgen's N_LET planted the local + * at offset 0: the element stores landed on the saved BP/RIP + * and main SEGFAULTED on RET. wwstage (exprtype N_TUPLE → + * tinfofornode) was runtime-correct — the rare cstage-is-the-bug + * polarity (#263 corollary). Every row below segfaulted (or, for + * the arg row, link-failed on the off==0 global fallback) at + * master e8977a4 cstage; all are byte-id post-fix. ---- */ + { "t44_inferred_let_cast", + "package main;\n" + "export fn main() i32 = {\n" + " let t = (4: size, 2: size);\n" + " if (t.0 != 4) { return 1; };\n" + " if (t.1 != 2) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* bare untyped-int elements; the decl alone (no .N read) already + * smashed the frame at master — the noread shape rides the same + * row via the early stores. */ + { "t44_inferred_let_bare", + "package main;\n" + "export fn main() i32 = {\n" + " let t = (4, 2);\n" + " if (t.0 != 4) { return 1; };\n" + " if (t.1 != 2) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "t44_inferred_let_mixed3", + "package main;\n" + "export fn main() i32 = {\n" + " let t = (4: size, 2, 36: size);\n" + " if (t.0 + t.2 != 40) { return 1; };\n" + " if (t.1 != 2) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "t44_inferred_let_float", + "package main;\n" + "export fn main() i32 = {\n" + " let t = (1.5, 2.5);\n" + " if (t.0 + t.1 != 4.0) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* destructure FROM the inferred local — the mlet source ident + * resolved to the 0-size slot at offset 0 (same root). */ + { "t44_inferred_destructure", + "package main;\n" + "export fn main() i32 = {\n" + " let t = (40: size, 2: size);\n" + " let (a, b) = t;\n" + " if (a + b != 42) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* inferred local as a tuple CALL ARG — at master the off==0 local + * fell to the global-symbol path: `w6l: undefined reference to + * 't'` (link-fail, not segfault — same root, different death). */ + { "t44_inferred_arg", + "package main;\n" + "fn add(t: (size, size)) size = {\n" + " return t.0 + t.1;\n" + "};\n" + "export fn main() i32 = {\n" + " let t = (40: size, 2: size);\n" + " if (add(t) != 42) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* nested tuple element: slot rule gives an inner tuple one 8B + * eightbyte (tuple_eslot parity) — pins the outer layout. */ + { "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 }, }; /* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */