wcc: converge DOT-recv, structlit-fill, and bare-let zero-init onto structabisize

Three wwstage cgen sites still used the unrounded structnaturalsize where cstage
rounds via lu->size (check.c:760), pre-existing gate-blind cs!=ww latents
flagged in #169's reviewer notes: cgenexpr DOT register-RECV for obj.f = mk()
(~5175/5393/5628/6164); cgstructlitfill's TK_ELLIPSIS zero-fill branch
(cgenutil); and cglet bare 'let z: T;' zero-init of a maxalign<8 struct
(cgenstmt). Each produced MOVQ-vs-MOVL or wider-write divergence vs cstage on
the trailing word of a sub-eightbyte tail.

Converge all three onto the maxalign-rounded structabisize the #169 work
established at the register-ABI sites (cite cstage cgen.c:7720 RECV twin +
cgen.c:2085 cg_structlit_fill). cgstructlitfill's signature drops the external
totsize parameter in favor of one internal source; the field-walk path is
untouched, only the ELLIPSIS zero-fill uses the ABI size. cglet's slot
allocation stays on the frame size; only the zero-fill extent uses ABI.

Gate-blind (the bootstrap exercises none of these shapes); covered by 5 new
rows in probe 698 with cs==ww .s byte-cmp and a pre-fix-rebuild proving the
exact MOVQ-vs-MOVL discrimination. 990-997 byte-id hold.
This commit is contained in:
2026-05-28 13:02:47 +09:00
parent d92c199d25
commit 39f9267bc9
6 changed files with 210 additions and 117 deletions

View File

@@ -176,6 +176,72 @@ static const struct row rows[] = {
"{ return s6 { n = 4i32, a = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8] }; };\n"
"fn main() i32 = { let p: s6 = mk(); return p.n - 4; };\n",
0 },
/* DOT register-RECV: `obj.f = mk();` where the field type is a
* struct{i64,i32} (natural 12, maxalign 8 → cstage lu->size 16).
* Pre-fix wwstage sized the AX/DX/CX → field stores on the
* unrounded structnaturalsize (12) → MOVQ+MOVL where cstage emits
* MOVQ+MOVQ. The byte-id discriminator hits regardless of value
* read-back. Three flavors cover the *struct base, direct local,
* and global base sites that share the class. */
{ "dot_recv_call_local",
"type pt = struct { a: i64, b: i32 };\n"
"type box = struct { v: pt };\n"
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
"fn main() i32 = {\n"
" let b: box;\n"
" b.v = mk();\n"
" return b.v.b - 9;\n"
"};\n",
0 },
{ "dot_recv_call_viaptr",
"type pt = struct { a: i64, b: i32 };\n"
"type box = struct { v: pt };\n"
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
"fn fill(p: *box) void = { p.v = mk(); return; };\n"
"fn main() i32 = {\n"
" let b: box;\n"
" fill(&b);\n"
" return b.v.b - 9;\n"
"};\n",
0 },
{ "dot_recv_call_global",
"type pt = struct { a: i64, b: i32 };\n"
"type box = struct { v: pt };\n"
"let g: box = box { v = pt { a = 0i64, b = 0i32 } };\n"
"fn mk() pt = { return pt { a = 7i64, b = 9i32 }; };\n"
"fn main() i32 = { g.v = mk(); return g.v.b - 9; };\n",
0 },
/* structlit ELLIPSIS zero-fill at a DOT site. cstage zeroes the
* slot at `lu->size` (maxalign-rounded); wwstage pre-fix passed
* structnaturalsize (12 for struct{i64,i32}) as the totsize
* bound but cstage rounds to maxalign (16) before zeroing. The
* `..., ...` literal forces the ELLIPSIS path. Outer box keeps
* maxalign==8 so its own zero-init / structlit-init stays out
* of the registerstruct fsz-ladder latent (task #18). */
{ "dot_structlit_ellipsis",
"type s = struct { a: i64, b: i32 };\n"
"type box = struct { v: s };\n"
"fn mk() box = "
"{ return box { v = s { a = 0i64, b = 0i32 } }; };\n"
"fn main() i32 = {\n"
" let b: box = mk();\n"
" b.v = s { a = 5i64, ... };\n"
" return b.v.b;\n"
"};\n",
0 },
/* Bare `let z: T;` zero-init of a maxalign<8 struct. Slot is
* round-to-8 (ti.slotsize); cstage zeroes only `lu->size`
* (maxalign-rounded, check.c:760). struct{i32,i32,i32} natural 12,
* maxalign 4 → cstage emits XORQ + MOVQ + MOVL (12B); pre-fix
* wwstage emitted XORQ + MOVQ + MOVQ (slot 16B). Value-correct
* either way; byte-id catches the trailing 4B widen. */
{ "bare_let_struct_maxalign4",
"type t3 = struct { a: i32, b: i32, c: i32 };\n"
"fn main() i32 = {\n"
" let z: t3;\n"
" return z.a + z.b + z.c;\n"
"};\n",
0 },
};
static int