diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 317fcca7..274b7162 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -12155,190 +12155,39 @@ export fn letslotsize(c: *cgen, n: *node) i32 = { return 8; }; +// #48 A.6.3d: AST walker retired. resolvewalk (check.ww:426-436) stamps +// `n.type_` on every N_T* kind via tinfofornode, which folds TBANG +// (inner unchanged, check.ww:1154-1161), TNAME alias chains +// (resolvealias, check.ww:1128-1153), TARRAY/TPTR/TSLICE/TCHAN/TFN/ +// TENUM/TTUPLE/TSTRUCT/TTAGGED with size + slot-padded slotsize. +// +// cstage SSoT is distributed — there is no single slot_size(Type*). +// Tagged slot follows cstage cmd/wcc/check.c:348 + :998 (tag (8) + +// max variant payload rounded to 8); the wwstage TTAGGED arm at +// check.ww:1296-1346 mirrors that layout. cgen.c:4850 / :5193 use +// the same `(su->kind == TY_TAGGED) ? su->size : 16` pattern for the +// match-spill slot. Pointer-and-narrow → 8 is the local-frame +// convention encoded at every cstage `localoff(…, 8, …)` callsite +// (cmd/w6c/cgen.c throughout); wwstage encodes the same pad-to-8 at +// this read site so `[N]i32` stride stays 4 (natural) — moving it +// into ti.slotsize would lift array stride to 8/elem. +// +// `c: *cgen` retained unused for callsite stability (localloadop +// precedent, 68219a1). fn slotsize(c: *cgen, typn: *node) i32 = { - // #61 audit §1.8 — A.3 / A.4 / A.5 fast-path expansion. Read the - // slot-padded width off the populated type-expression node when the - // kind matches the cstage natural-size SSoT. Coverage: - // - pointer-like (PTR/CHAN/FN), slice, str, tagged: ti.size === - // ti.slotsize (slot already equals natural). TY_TAGGED is safe - // now that tinfofornode folds `(*T | void)` to 8B (#61 A.3 step - // 1, mirrors cmd/wcc/check.c:412-426). - // - narrow scalars (BOOL/RUNE/I8..I32/U8..U32/ENUM/F32) pad UP to - // 8 — cstage's let_emit_size (cmd/w6c/cgen.c:691-720) spills - // every primitive into an 8B stack slot regardless of - // tinfo.size. Pad-to-8 lives at the read site, not in - // tinfo.slotsize, so `[N]i32` stride stays 4 (natural) — moving - // the pad into ti.slotsize would lift array stride to 8/elem. - // - TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm, - // same as #61 A.4). - // - #61 A.5 adds: TY_STRUCT / TY_TUPLE / TY_ARRAY read ti.slotsize - // (slot-padded). tinfofornode populates the slot total mirroring - // cgenutil.ww registerstruct (size-derived align, nested struct - // fields → si.totsize, final round to 8), and TARRAY threads - // stride through sub.slotsize so `[N]Triplet` lifts to padded * - // N. `size(T)` stays natural — split SSoT in tinfo. - if (typn != nil && typn.type_ != nil) { - let ti: *tinfo = typn.type_: *tinfo; - let kk: tykind = ti.kind; - if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE || - kk == tykind.TY_CHAN || kk == tykind.TY_FN || - kk == tykind.TY_STR || kk == tykind.TY_TAGGED || - kk == tykind.TY_VOID) { - return ti.size: i32; - }; - if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE) { - if (ti.slotsize > 0u64) { return ti.slotsize: i32; }; - }; - if (kk == tykind.TY_ARRAY) { - if (ti.alen > 0u64) { return ti.slotsize: i32; }; - }; - if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE || - kk == tykind.TY_I8 || kk == tykind.TY_I16 || - kk == tykind.TY_I32 || kk == tykind.TY_I64 || - kk == tykind.TY_U8 || kk == tykind.TY_U16 || - kk == tykind.TY_U32 || kk == tykind.TY_U64 || - kk == tykind.TY_INT || kk == tykind.TY_UINT || - kk == tykind.TY_UINTPTR || kk == tykind.TY_ENUM || - kk == tykind.TY_F32 || kk == tykind.TY_F64) { - return 8; - }; - }; if (typn == nil) { return 8; }; - let k: nkind = typn.kind; - // `!T` carries T's memory layout; the error-tag bit lives in the - // enclosing union's discriminant, not the variant payload. cstage - // resolve_type N_TBANG copies the inner size (cmd/wcc/check.c:303- - // 306); wwstage previously fell through to the catch-all 8, so a - // bare `!void` variant sized to 8 instead of 0. (#48 part-A.) - if (k == nkind.N_TBANG) { return slotsize(c, typn.lhs); }; - if (k == nkind.N_TPTR) { return 8; }; - if (k == nkind.N_TFN) { return 8; }; - if (k == nkind.N_TCHAN) { return 8; }; - if (k == nkind.N_TSLICE) { return tyslicesize(): i32; }; - if (k == nkind.N_TTUPLE) { - // Sum element sizes. Mirrors C cgen which uses raw type - // sizes; padding to 8 happens inside slotsize for primitives, - // so a `(i64, str)` resolves to 8 + 16 = 24 (matches the C - // cgen 24B init / positional-access layout). - let total: i32 = 0; - let p: *node = typn.list; - for (p != nil) { - total += slotsize(c, p.lhs); - p = p.next; - }; - return total; + let ti: *tinfo = typn.type_: *tinfo; + if (ti == nil) { return 8; }; + let kk: tykind = ti.kind; + if (kk == tykind.TY_VOID) { return 0; }; + if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE || + kk == tykind.TY_CHAN || kk == tykind.TY_FN || + kk == tykind.TY_STR || kk == tykind.TY_TAGGED) { + return ti.size: i32; }; - if (k == nkind.N_TTAGGED){ - // Nullable `(*T | void)` collapses to a single 8B pointer. - if (isnullabletype(typn)) { return 8; }; - // Slot = 8 (tag) + max(variant payload sizes), rounded up - // to an 8-byte multiple so the reg-passing ABI (size/8 - // words) doesn't drop the last value register. Mirrors C - // cgen's resolve_type for nkind.N_TTAGGED. - let v: *node = typn.list; - let maxsz: i32 = 0; - for (v != nil) { - let sz: i32 = slotsize(c, v); - if (sz > maxsz) { maxsz = sz; }; - v = v.next; - }; - let pad: i32 = (maxsz + 7) & ~7; - return 8 + pad; - }; - if (k == nkind.N_TNAME) { - let nm: str = typn.str; - // `void` is zero-sized per Hare design; cstage's ty_void.size = 0 - // at cmd/wcc/type.c:46. Pre-#48 wwstage fell through to the - // catch-all 8, so (void | !void) sized as tag + 8 = 16 and let- - // init through cgwidentaggedstore emitted a phantom DX spill at - // slot+8 picking up the callee's stale-DX. Bypassing primsize's - // `> 0` guard keeps the rest of the prim-pad-to-8 contract intact. - if (streq(nm, "void")) { return 0; }; - if (streq(nm, "str")) { return primtypesize("str"): i32; }; - let ps: i32 = primsize(nm); - if (ps > 0) { - // Pad to 8 for stack slots — matches C cgen which spills - // every primitive into an 8-byte slot. - return 8; - }; - // Named struct lookup. - let si: *structinfo = structlookup(c, nm); - if (si != nil) { return si.totsize; }; - // Type alias (`type foo = !str;` / `type foo = bar;`): - // follow it so a tagged-union variant of a !str-aliased - // error type contributes 16 bytes to the max payload - // rather than 8 (the default). - if (c != nil) { - let aliased: *node = aliaslookup(c, nm); - if (aliased != nil) { - if (aliased.kind == nkind.N_TBANG) { - return slotsize(c, aliased.lhs); - }; - return slotsize(c, aliased); - }; - }; - return 8; - }; - if (k == nkind.N_TARRAY) { - let lenn: *node = typn.rhs; - let elemn: *node = typn.lhs; - let elen: i64 = 1i64; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { elen = lenn.uval: i64; }; - }; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let en: str = elemn.str; - // `str` is a composite primitive (ptr+len, 16B); - // primsize returns 0 for it, so without this - // explicit case a `[N]str` would slot 8B/elem, - // collapsing the per-element stride and losing - // every .len half. - if (streq(en, "str")) { esz = primtypesize("str"): i32; }; - let ps: i32 = primsize(en); - if (esz == 8) { if (ps > 0) { esz = ps; } - else { - // Named struct / aliased type: size off - // the structinfo if present, else follow - // the alias via aliaslookup so - // `[N]formattable` reads the resolved - // tagged slot (e.g. 24B for - // `(i64|str|bool)`), not the fall- - // through 8B. - let si: *structinfo = structlookup(c, en); - if (si != nil) { esz = si.totsize; } - else { if (c != nil) { - let al: *node = aliaslookup(c, en); - if (al != nil) { - esz = slotsize(c, al); - }; - }; }; - }; }; - } else { if (elemn.kind == nkind.N_TTAGGED) { - // Tagged-union element: full slot (8 tag + - // padded max payload). Matches C cgen's - // resolve_type for `[N]TAGGED`. - esz = slotsize(c, elemn); - } else { if (elemn.kind == nkind.N_TPTR) { - esz = 8; - } else { if (elemn.kind == nkind.N_TSTRUCT) { - esz = slotsize(c, elemn); - }; }; }; }; - }; - return (esz: i64 * elen): i32; - }; - if (k == nkind.N_TSTRUCT) { - // Inline anonymous struct — sum of field sizes. - let f: *node = typn.list; - let total: i32 = 0; - for (f != nil) { - if (f.kind == nkind.N_TFIELD) { - total += slotsize(c, f.lhs); - }; - f = f.next; - }; - return total; + if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE || + kk == tykind.TY_ARRAY) { + return ti.slotsize: i32; }; return 8; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 648f6954..e1f46ff7 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1783,190 +1783,39 @@ export fn letslotsize(c: *cgen, n: *node) i32 = { return 8; }; +// #48 A.6.3d: AST walker retired. resolvewalk (check.ww:426-436) stamps +// `n.type_` on every N_T* kind via tinfofornode, which folds TBANG +// (inner unchanged, check.ww:1154-1161), TNAME alias chains +// (resolvealias, check.ww:1128-1153), TARRAY/TPTR/TSLICE/TCHAN/TFN/ +// TENUM/TTUPLE/TSTRUCT/TTAGGED with size + slot-padded slotsize. +// +// cstage SSoT is distributed — there is no single slot_size(Type*). +// Tagged slot follows cstage cmd/wcc/check.c:348 + :998 (tag (8) + +// max variant payload rounded to 8); the wwstage TTAGGED arm at +// check.ww:1296-1346 mirrors that layout. cgen.c:4850 / :5193 use +// the same `(su->kind == TY_TAGGED) ? su->size : 16` pattern for the +// match-spill slot. Pointer-and-narrow → 8 is the local-frame +// convention encoded at every cstage `localoff(…, 8, …)` callsite +// (cmd/w6c/cgen.c throughout); wwstage encodes the same pad-to-8 at +// this read site so `[N]i32` stride stays 4 (natural) — moving it +// into ti.slotsize would lift array stride to 8/elem. +// +// `c: *cgen` retained unused for callsite stability (localloadop +// precedent, 68219a1). fn slotsize(c: *cgen, typn: *node) i32 = { - // #61 audit §1.8 — A.3 / A.4 / A.5 fast-path expansion. Read the - // slot-padded width off the populated type-expression node when the - // kind matches the cstage natural-size SSoT. Coverage: - // - pointer-like (PTR/CHAN/FN), slice, str, tagged: ti.size === - // ti.slotsize (slot already equals natural). TY_TAGGED is safe - // now that tinfofornode folds `(*T | void)` to 8B (#61 A.3 step - // 1, mirrors cmd/wcc/check.c:412-426). - // - narrow scalars (BOOL/RUNE/I8..I32/U8..U32/ENUM/F32) pad UP to - // 8 — cstage's let_emit_size (cmd/w6c/cgen.c:691-720) spills - // every primitive into an 8B stack slot regardless of - // tinfo.size. Pad-to-8 lives at the read site, not in - // tinfo.slotsize, so `[N]i32` stride stays 4 (natural) — moving - // the pad into ti.slotsize would lift array stride to 8/elem. - // - TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm, - // same as #61 A.4). - // - #61 A.5 adds: TY_STRUCT / TY_TUPLE / TY_ARRAY read ti.slotsize - // (slot-padded). tinfofornode populates the slot total mirroring - // cgenutil.ww registerstruct (size-derived align, nested struct - // fields → si.totsize, final round to 8), and TARRAY threads - // stride through sub.slotsize so `[N]Triplet` lifts to padded * - // N. `size(T)` stays natural — split SSoT in tinfo. - if (typn != nil && typn.type_ != nil) { - let ti: *tinfo = typn.type_: *tinfo; - let kk: tykind = ti.kind; - if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE || - kk == tykind.TY_CHAN || kk == tykind.TY_FN || - kk == tykind.TY_STR || kk == tykind.TY_TAGGED || - kk == tykind.TY_VOID) { - return ti.size: i32; - }; - if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE) { - if (ti.slotsize > 0u64) { return ti.slotsize: i32; }; - }; - if (kk == tykind.TY_ARRAY) { - if (ti.alen > 0u64) { return ti.slotsize: i32; }; - }; - if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE || - kk == tykind.TY_I8 || kk == tykind.TY_I16 || - kk == tykind.TY_I32 || kk == tykind.TY_I64 || - kk == tykind.TY_U8 || kk == tykind.TY_U16 || - kk == tykind.TY_U32 || kk == tykind.TY_U64 || - kk == tykind.TY_INT || kk == tykind.TY_UINT || - kk == tykind.TY_UINTPTR || kk == tykind.TY_ENUM || - kk == tykind.TY_F32 || kk == tykind.TY_F64) { - return 8; - }; - }; if (typn == nil) { return 8; }; - let k: nkind = typn.kind; - // `!T` carries T's memory layout; the error-tag bit lives in the - // enclosing union's discriminant, not the variant payload. cstage - // resolve_type N_TBANG copies the inner size (cmd/wcc/check.c:303- - // 306); wwstage previously fell through to the catch-all 8, so a - // bare `!void` variant sized to 8 instead of 0. (#48 part-A.) - if (k == nkind.N_TBANG) { return slotsize(c, typn.lhs); }; - if (k == nkind.N_TPTR) { return 8; }; - if (k == nkind.N_TFN) { return 8; }; - if (k == nkind.N_TCHAN) { return 8; }; - if (k == nkind.N_TSLICE) { return tyslicesize(): i32; }; - if (k == nkind.N_TTUPLE) { - // Sum element sizes. Mirrors C cgen which uses raw type - // sizes; padding to 8 happens inside slotsize for primitives, - // so a `(i64, str)` resolves to 8 + 16 = 24 (matches the C - // cgen 24B init / positional-access layout). - let total: i32 = 0; - let p: *node = typn.list; - for (p != nil) { - total += slotsize(c, p.lhs); - p = p.next; - }; - return total; + let ti: *tinfo = typn.type_: *tinfo; + if (ti == nil) { return 8; }; + let kk: tykind = ti.kind; + if (kk == tykind.TY_VOID) { return 0; }; + if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE || + kk == tykind.TY_CHAN || kk == tykind.TY_FN || + kk == tykind.TY_STR || kk == tykind.TY_TAGGED) { + return ti.size: i32; }; - if (k == nkind.N_TTAGGED){ - // Nullable `(*T | void)` collapses to a single 8B pointer. - if (isnullabletype(typn)) { return 8; }; - // Slot = 8 (tag) + max(variant payload sizes), rounded up - // to an 8-byte multiple so the reg-passing ABI (size/8 - // words) doesn't drop the last value register. Mirrors C - // cgen's resolve_type for nkind.N_TTAGGED. - let v: *node = typn.list; - let maxsz: i32 = 0; - for (v != nil) { - let sz: i32 = slotsize(c, v); - if (sz > maxsz) { maxsz = sz; }; - v = v.next; - }; - let pad: i32 = (maxsz + 7) & ~7; - return 8 + pad; - }; - if (k == nkind.N_TNAME) { - let nm: str = typn.str; - // `void` is zero-sized per Hare design; cstage's ty_void.size = 0 - // at cmd/wcc/type.c:46. Pre-#48 wwstage fell through to the - // catch-all 8, so (void | !void) sized as tag + 8 = 16 and let- - // init through cgwidentaggedstore emitted a phantom DX spill at - // slot+8 picking up the callee's stale-DX. Bypassing primsize's - // `> 0` guard keeps the rest of the prim-pad-to-8 contract intact. - if (streq(nm, "void")) { return 0; }; - if (streq(nm, "str")) { return primtypesize("str"): i32; }; - let ps: i32 = primsize(nm); - if (ps > 0) { - // Pad to 8 for stack slots — matches C cgen which spills - // every primitive into an 8-byte slot. - return 8; - }; - // Named struct lookup. - let si: *structinfo = structlookup(c, nm); - if (si != nil) { return si.totsize; }; - // Type alias (`type foo = !str;` / `type foo = bar;`): - // follow it so a tagged-union variant of a !str-aliased - // error type contributes 16 bytes to the max payload - // rather than 8 (the default). - if (c != nil) { - let aliased: *node = aliaslookup(c, nm); - if (aliased != nil) { - if (aliased.kind == nkind.N_TBANG) { - return slotsize(c, aliased.lhs); - }; - return slotsize(c, aliased); - }; - }; - return 8; - }; - if (k == nkind.N_TARRAY) { - let lenn: *node = typn.rhs; - let elemn: *node = typn.lhs; - let elen: i64 = 1i64; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { elen = lenn.uval: i64; }; - }; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let en: str = elemn.str; - // `str` is a composite primitive (ptr+len, 16B); - // primsize returns 0 for it, so without this - // explicit case a `[N]str` would slot 8B/elem, - // collapsing the per-element stride and losing - // every .len half. - if (streq(en, "str")) { esz = primtypesize("str"): i32; }; - let ps: i32 = primsize(en); - if (esz == 8) { if (ps > 0) { esz = ps; } - else { - // Named struct / aliased type: size off - // the structinfo if present, else follow - // the alias via aliaslookup so - // `[N]formattable` reads the resolved - // tagged slot (e.g. 24B for - // `(i64|str|bool)`), not the fall- - // through 8B. - let si: *structinfo = structlookup(c, en); - if (si != nil) { esz = si.totsize; } - else { if (c != nil) { - let al: *node = aliaslookup(c, en); - if (al != nil) { - esz = slotsize(c, al); - }; - }; }; - }; }; - } else { if (elemn.kind == nkind.N_TTAGGED) { - // Tagged-union element: full slot (8 tag + - // padded max payload). Matches C cgen's - // resolve_type for `[N]TAGGED`. - esz = slotsize(c, elemn); - } else { if (elemn.kind == nkind.N_TPTR) { - esz = 8; - } else { if (elemn.kind == nkind.N_TSTRUCT) { - esz = slotsize(c, elemn); - }; }; }; }; - }; - return (esz: i64 * elen): i32; - }; - if (k == nkind.N_TSTRUCT) { - // Inline anonymous struct — sum of field sizes. - let f: *node = typn.list; - let total: i32 = 0; - for (f != nil) { - if (f.kind == nkind.N_TFIELD) { - total += slotsize(c, f.lhs); - }; - f = f.next; - }; - return total; + if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE || + kk == tykind.TY_ARRAY) { + return ti.slotsize: i32; }; return 8; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 725536ff..500013c6 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -12155,190 +12155,39 @@ export fn letslotsize(c: *cgen, n: *node) i32 = { return 8; }; +// #48 A.6.3d: AST walker retired. resolvewalk (check.ww:426-436) stamps +// `n.type_` on every N_T* kind via tinfofornode, which folds TBANG +// (inner unchanged, check.ww:1154-1161), TNAME alias chains +// (resolvealias, check.ww:1128-1153), TARRAY/TPTR/TSLICE/TCHAN/TFN/ +// TENUM/TTUPLE/TSTRUCT/TTAGGED with size + slot-padded slotsize. +// +// cstage SSoT is distributed — there is no single slot_size(Type*). +// Tagged slot follows cstage cmd/wcc/check.c:348 + :998 (tag (8) + +// max variant payload rounded to 8); the wwstage TTAGGED arm at +// check.ww:1296-1346 mirrors that layout. cgen.c:4850 / :5193 use +// the same `(su->kind == TY_TAGGED) ? su->size : 16` pattern for the +// match-spill slot. Pointer-and-narrow → 8 is the local-frame +// convention encoded at every cstage `localoff(…, 8, …)` callsite +// (cmd/w6c/cgen.c throughout); wwstage encodes the same pad-to-8 at +// this read site so `[N]i32` stride stays 4 (natural) — moving it +// into ti.slotsize would lift array stride to 8/elem. +// +// `c: *cgen` retained unused for callsite stability (localloadop +// precedent, 68219a1). fn slotsize(c: *cgen, typn: *node) i32 = { - // #61 audit §1.8 — A.3 / A.4 / A.5 fast-path expansion. Read the - // slot-padded width off the populated type-expression node when the - // kind matches the cstage natural-size SSoT. Coverage: - // - pointer-like (PTR/CHAN/FN), slice, str, tagged: ti.size === - // ti.slotsize (slot already equals natural). TY_TAGGED is safe - // now that tinfofornode folds `(*T | void)` to 8B (#61 A.3 step - // 1, mirrors cmd/wcc/check.c:412-426). - // - narrow scalars (BOOL/RUNE/I8..I32/U8..U32/ENUM/F32) pad UP to - // 8 — cstage's let_emit_size (cmd/w6c/cgen.c:691-720) spills - // every primitive into an 8B stack slot regardless of - // tinfo.size. Pad-to-8 lives at the read site, not in - // tinfo.slotsize, so `[N]i32` stride stays 4 (natural) — moving - // the pad into ti.slotsize would lift array stride to 8/elem. - // - TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm, - // same as #61 A.4). - // - #61 A.5 adds: TY_STRUCT / TY_TUPLE / TY_ARRAY read ti.slotsize - // (slot-padded). tinfofornode populates the slot total mirroring - // cgenutil.ww registerstruct (size-derived align, nested struct - // fields → si.totsize, final round to 8), and TARRAY threads - // stride through sub.slotsize so `[N]Triplet` lifts to padded * - // N. `size(T)` stays natural — split SSoT in tinfo. - if (typn != nil && typn.type_ != nil) { - let ti: *tinfo = typn.type_: *tinfo; - let kk: tykind = ti.kind; - if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE || - kk == tykind.TY_CHAN || kk == tykind.TY_FN || - kk == tykind.TY_STR || kk == tykind.TY_TAGGED || - kk == tykind.TY_VOID) { - return ti.size: i32; - }; - if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE) { - if (ti.slotsize > 0u64) { return ti.slotsize: i32; }; - }; - if (kk == tykind.TY_ARRAY) { - if (ti.alen > 0u64) { return ti.slotsize: i32; }; - }; - if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE || - kk == tykind.TY_I8 || kk == tykind.TY_I16 || - kk == tykind.TY_I32 || kk == tykind.TY_I64 || - kk == tykind.TY_U8 || kk == tykind.TY_U16 || - kk == tykind.TY_U32 || kk == tykind.TY_U64 || - kk == tykind.TY_INT || kk == tykind.TY_UINT || - kk == tykind.TY_UINTPTR || kk == tykind.TY_ENUM || - kk == tykind.TY_F32 || kk == tykind.TY_F64) { - return 8; - }; - }; if (typn == nil) { return 8; }; - let k: nkind = typn.kind; - // `!T` carries T's memory layout; the error-tag bit lives in the - // enclosing union's discriminant, not the variant payload. cstage - // resolve_type N_TBANG copies the inner size (cmd/wcc/check.c:303- - // 306); wwstage previously fell through to the catch-all 8, so a - // bare `!void` variant sized to 8 instead of 0. (#48 part-A.) - if (k == nkind.N_TBANG) { return slotsize(c, typn.lhs); }; - if (k == nkind.N_TPTR) { return 8; }; - if (k == nkind.N_TFN) { return 8; }; - if (k == nkind.N_TCHAN) { return 8; }; - if (k == nkind.N_TSLICE) { return tyslicesize(): i32; }; - if (k == nkind.N_TTUPLE) { - // Sum element sizes. Mirrors C cgen which uses raw type - // sizes; padding to 8 happens inside slotsize for primitives, - // so a `(i64, str)` resolves to 8 + 16 = 24 (matches the C - // cgen 24B init / positional-access layout). - let total: i32 = 0; - let p: *node = typn.list; - for (p != nil) { - total += slotsize(c, p.lhs); - p = p.next; - }; - return total; + let ti: *tinfo = typn.type_: *tinfo; + if (ti == nil) { return 8; }; + let kk: tykind = ti.kind; + if (kk == tykind.TY_VOID) { return 0; }; + if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE || + kk == tykind.TY_CHAN || kk == tykind.TY_FN || + kk == tykind.TY_STR || kk == tykind.TY_TAGGED) { + return ti.size: i32; }; - if (k == nkind.N_TTAGGED){ - // Nullable `(*T | void)` collapses to a single 8B pointer. - if (isnullabletype(typn)) { return 8; }; - // Slot = 8 (tag) + max(variant payload sizes), rounded up - // to an 8-byte multiple so the reg-passing ABI (size/8 - // words) doesn't drop the last value register. Mirrors C - // cgen's resolve_type for nkind.N_TTAGGED. - let v: *node = typn.list; - let maxsz: i32 = 0; - for (v != nil) { - let sz: i32 = slotsize(c, v); - if (sz > maxsz) { maxsz = sz; }; - v = v.next; - }; - let pad: i32 = (maxsz + 7) & ~7; - return 8 + pad; - }; - if (k == nkind.N_TNAME) { - let nm: str = typn.str; - // `void` is zero-sized per Hare design; cstage's ty_void.size = 0 - // at cmd/wcc/type.c:46. Pre-#48 wwstage fell through to the - // catch-all 8, so (void | !void) sized as tag + 8 = 16 and let- - // init through cgwidentaggedstore emitted a phantom DX spill at - // slot+8 picking up the callee's stale-DX. Bypassing primsize's - // `> 0` guard keeps the rest of the prim-pad-to-8 contract intact. - if (streq(nm, "void")) { return 0; }; - if (streq(nm, "str")) { return primtypesize("str"): i32; }; - let ps: i32 = primsize(nm); - if (ps > 0) { - // Pad to 8 for stack slots — matches C cgen which spills - // every primitive into an 8-byte slot. - return 8; - }; - // Named struct lookup. - let si: *structinfo = structlookup(c, nm); - if (si != nil) { return si.totsize; }; - // Type alias (`type foo = !str;` / `type foo = bar;`): - // follow it so a tagged-union variant of a !str-aliased - // error type contributes 16 bytes to the max payload - // rather than 8 (the default). - if (c != nil) { - let aliased: *node = aliaslookup(c, nm); - if (aliased != nil) { - if (aliased.kind == nkind.N_TBANG) { - return slotsize(c, aliased.lhs); - }; - return slotsize(c, aliased); - }; - }; - return 8; - }; - if (k == nkind.N_TARRAY) { - let lenn: *node = typn.rhs; - let elemn: *node = typn.lhs; - let elen: i64 = 1i64; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { elen = lenn.uval: i64; }; - }; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let en: str = elemn.str; - // `str` is a composite primitive (ptr+len, 16B); - // primsize returns 0 for it, so without this - // explicit case a `[N]str` would slot 8B/elem, - // collapsing the per-element stride and losing - // every .len half. - if (streq(en, "str")) { esz = primtypesize("str"): i32; }; - let ps: i32 = primsize(en); - if (esz == 8) { if (ps > 0) { esz = ps; } - else { - // Named struct / aliased type: size off - // the structinfo if present, else follow - // the alias via aliaslookup so - // `[N]formattable` reads the resolved - // tagged slot (e.g. 24B for - // `(i64|str|bool)`), not the fall- - // through 8B. - let si: *structinfo = structlookup(c, en); - if (si != nil) { esz = si.totsize; } - else { if (c != nil) { - let al: *node = aliaslookup(c, en); - if (al != nil) { - esz = slotsize(c, al); - }; - }; }; - }; }; - } else { if (elemn.kind == nkind.N_TTAGGED) { - // Tagged-union element: full slot (8 tag + - // padded max payload). Matches C cgen's - // resolve_type for `[N]TAGGED`. - esz = slotsize(c, elemn); - } else { if (elemn.kind == nkind.N_TPTR) { - esz = 8; - } else { if (elemn.kind == nkind.N_TSTRUCT) { - esz = slotsize(c, elemn); - }; }; }; }; - }; - return (esz: i64 * elen): i32; - }; - if (k == nkind.N_TSTRUCT) { - // Inline anonymous struct — sum of field sizes. - let f: *node = typn.list; - let total: i32 = 0; - for (f != nil) { - if (f.kind == nkind.N_TFIELD) { - total += slotsize(c, f.lhs); - }; - f = f.next; - }; - return total; + if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE || + kk == tykind.TY_ARRAY) { + return ti.slotsize: i32; }; return 8; };