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. */