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

@@ -3092,9 +3092,14 @@ export fn dotchainresolve(c: *cgen, n: *node,
// - `srcoff` (DST_PTR_LOCAL) and `srcname` (DST_GLOBAL) are
// *constant* across the whole call tree — they identify the
// root dst, which doesn't change with depth.
// - `totsize` is also constant; pass the natural size for dot
// sites (structnaturalsize) and si.totsize for BP-rel sites,
// matching each site's pre-#18 zero-fill bound.
// - the ELLIPSIS zero-fill extent is read internally as
// structabisize(si) — cstage's cg_structlit_fill computes
// `sz = lu->size` (cgen.c:2085), the maxalign-rounded ABI size
// (check.c:760 lu->size = (off+maxalign-1)&~(maxalign-1)). The
// pre-#169 callers passed two different sizes (natural at DOT
// sites, slot-padded at BP-rel sites); neither matched cstage
// for maxalign<8 structs (the zero-fill ran MOVQ where cstage
// ran MOVL — value-correct, asm-divergent).
//
// Why a helper? The inline field-walk previously did
// `cgexpr(field.lhs); store AX sized`. For struct-typed fields whose
@@ -3121,10 +3126,11 @@ export fn dotchainresolve(c: *cgen, n: *node,
// which already returns MOVW where appropriate.
fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
mode: i32, srcoff: i32, srcname: str,
disp: i32, totsize: i32) void = {
disp: i32) void = {
if (si == nil) { return; };
let basereg: str = "BP";
if (mode != 0) { basereg = "BX"; };
let totsize: i32 = structabisize(si);
if (lit.op == tkind.TK_ELLIPSIS) {
// `..., ...` autofill — zero the entire slot first so
// unmentioned fields read as 0. Sized stores: 8/4/1. For
@@ -3218,17 +3224,10 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
if (primsize(fi.tnode.str) == 0) {
let isi: *structinfo = structlookup(c, fi.tnode.str);
if (isi != nil) {
// Nested fill: pick the size
// discipline matching the outer
// site — dot sites pass natural
// size, BP-rel sites pass
// totsize. Mirror it.
let inner_tot: i32 = isi.totsize;
if (mode != 0) { inner_tot = structnaturalsize(isi); };
cgstructlitfill(c, isi,
fieldnode.lhs,
mode, srcoff, srcname,
disp + fi.foff, inner_tot);
disp + fi.foff);
nested = true;
};
};
@@ -3454,9 +3453,8 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
};
// Thin wrapper preserving the BP-rel call shape used by cglet,
// cgreturn, and cgassign N_IDENT-lhs N_STRUCTLIT. Byte-identical to
// the pre-#18 cgstructlitfillbp.
// cgreturn, and cgassign N_IDENT-lhs N_STRUCTLIT.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (si == nil) { return; };
cgstructlitfill(c, si, lit, 0, 0, "", bpoff, si.totsize);
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
};