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

@@ -17159,9 +17159,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
@@ -17188,10 +17193,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
@@ -17285,17 +17291,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;
};
};
@@ -17521,11 +17520,10 @@ 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);
};
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
@@ -22678,11 +22676,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// N_IDENT: word-copy from rhs slot.
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
// ABI; load *struct ptr into BX after the
// call, sized stores per natural struct size.
// call, sized stores per the ABI size.
// N_STRUCTLIT: field-walk; reload BX before
// each store so cgexpr can clobber AX/BX.
// si.totsize is slot-padded; use
// structnaturalsize for the type-size query.
// register RECV reads AX/DX/CX at 8-byte
// granularity — size via structabisize (cstage
// SSoT lu->size, check.c:760; cgen.c:7720
// sz=lu->size at the receive twin).
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_CALL
@@ -22691,7 +22691,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -22746,9 +22746,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff, ssz);
fi.foff);
return;
};
};
@@ -22909,7 +22908,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -22960,9 +22959,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff, ssz);
lc.off + fi.foff);
return;
};
};
@@ -23144,7 +23142,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -23199,9 +23197,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff, ssz);
fi.foff);
return;
};
};
@@ -23678,9 +23675,10 @@ fn cgassign(c: *cgen, n: *node) void = {
&& leafstruct) {
let lsi: *structinfo = structlookup(c, leafname);
if (lsi != nil) {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
// register RECV reads AX/DX/CX at 8-byte
// granularity — size via structabisize
// (cstage SSoT lu->size, check.c:760).
let lsz: i32 = structabisize(lsi);
if (lsz <= 24) {
let tlm: i32 = lsz - (lsz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -23761,9 +23759,6 @@ fn cgassign(c: *cgen, n: *node) void = {
&& leafstruct) {
let lsi: *structinfo = structlookup(c, leafname);
if (lsi != nil) {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
let dmode: i32 = 0;
let ddisp: i32 = rootoff + totaloff;
if (ptrroot) {
@@ -23776,7 +23771,7 @@ fn cgassign(c: *cgen, n: *node) void = {
};
cgstructlitfill(c, lsi, n.rhs,
dmode, rootoff, rootname,
ddisp, lsz);
ddisp);
return;
};
};
@@ -25119,7 +25114,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
// the dest pointer IS the struct base.
cgstructlitfill(c, sret_si, rhs,
1, sretargoff, emptys,
0, scs);
0);
};
} else {
let rl: *local = localfindnode(c, rhs.str);
@@ -25992,26 +25987,40 @@ fn cglet(c: *cgen, n: *node) void = {
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; };
};
// Zero-fill extent. cstage sizes the run on `lu->size`
// (the maxalign-rounded ABI size, check.c:760); wwstage's
// `sz` from letslotsize is slot-padded (round-to-8), so a
// struct with maxalign<8 and a sub-8 tail would over-zero
// MOVQ where cstage emits MOVL/MOVB. Read structabisize
// for a struct-typed let to converge; slot allocation
// stays on `sz` (frame uses slot-padded slots).
let zsz: i32 = sz;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TNAME) {
let szi: *structinfo = structlookupchain(c, n.lhs);
if (szi != nil) { zsz = structabisize(szi); };
};
};
if (typeis8byteprimitive(c, n.lhs)) {
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
} else { if (!isarr) { if (sz > 8) {
} else { if (!isarr) { if (zsz > 8) {
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= sz) {
for (zi + 8 <= zsz) {
emitline("\tMOVQ\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= sz) {
for (zi + 4 <= zsz) {
emitline("\tMOVL\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < sz) {
for (zi < zsz) {
emitline("\tMOVB\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");

View File

@@ -5148,11 +5148,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// N_IDENT: word-copy from rhs slot.
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
// ABI; load *struct ptr into BX after the
// call, sized stores per natural struct size.
// call, sized stores per the ABI size.
// N_STRUCTLIT: field-walk; reload BX before
// each store so cgexpr can clobber AX/BX.
// si.totsize is slot-padded; use
// structnaturalsize for the type-size query.
// register RECV reads AX/DX/CX at 8-byte
// granularity — size via structabisize (cstage
// SSoT lu->size, check.c:760; cgen.c:7720
// sz=lu->size at the receive twin).
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_CALL
@@ -5161,7 +5163,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -5216,9 +5218,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff, ssz);
fi.foff);
return;
};
};
@@ -5379,7 +5380,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -5430,9 +5431,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff, ssz);
lc.off + fi.foff);
return;
};
};
@@ -5614,7 +5614,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -5669,9 +5669,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff, ssz);
fi.foff);
return;
};
};
@@ -6148,9 +6147,10 @@ fn cgassign(c: *cgen, n: *node) void = {
&& leafstruct) {
let lsi: *structinfo = structlookup(c, leafname);
if (lsi != nil) {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
// register RECV reads AX/DX/CX at 8-byte
// granularity — size via structabisize
// (cstage SSoT lu->size, check.c:760).
let lsz: i32 = structabisize(lsi);
if (lsz <= 24) {
let tlm: i32 = lsz - (lsz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -6231,9 +6231,6 @@ fn cgassign(c: *cgen, n: *node) void = {
&& leafstruct) {
let lsi: *structinfo = structlookup(c, leafname);
if (lsi != nil) {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
let dmode: i32 = 0;
let ddisp: i32 = rootoff + totaloff;
if (ptrroot) {
@@ -6246,7 +6243,7 @@ fn cgassign(c: *cgen, n: *node) void = {
};
cgstructlitfill(c, lsi, n.rhs,
dmode, rootoff, rootname,
ddisp, lsz);
ddisp);
return;
};
};

View File

@@ -596,7 +596,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
// the dest pointer IS the struct base.
cgstructlitfill(c, sret_si, rhs,
1, sretargoff, emptys,
0, scs);
0);
};
} else {
let rl: *local = localfindnode(c, rhs.str);
@@ -1469,26 +1469,40 @@ fn cglet(c: *cgen, n: *node) void = {
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; };
};
// Zero-fill extent. cstage sizes the run on `lu->size`
// (the maxalign-rounded ABI size, check.c:760); wwstage's
// `sz` from letslotsize is slot-padded (round-to-8), so a
// struct with maxalign<8 and a sub-8 tail would over-zero
// MOVQ where cstage emits MOVL/MOVB. Read structabisize
// for a struct-typed let to converge; slot allocation
// stays on `sz` (frame uses slot-padded slots).
let zsz: i32 = sz;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TNAME) {
let szi: *structinfo = structlookupchain(c, n.lhs);
if (szi != nil) { zsz = structabisize(szi); };
};
};
if (typeis8byteprimitive(c, n.lhs)) {
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
} else { if (!isarr) { if (sz > 8) {
} else { if (!isarr) { if (zsz > 8) {
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= sz) {
for (zi + 8 <= zsz) {
emitline("\tMOVQ\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= sz) {
for (zi + 4 <= zsz) {
emitline("\tMOVL\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < sz) {
for (zi < zsz) {
emitline("\tMOVB\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");

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);
};

View File

@@ -17159,9 +17159,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
@@ -17188,10 +17193,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
@@ -17285,17 +17291,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;
};
};
@@ -17521,11 +17520,10 @@ 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);
};
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
@@ -22678,11 +22676,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// N_IDENT: word-copy from rhs slot.
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
// ABI; load *struct ptr into BX after the
// call, sized stores per natural struct size.
// call, sized stores per the ABI size.
// N_STRUCTLIT: field-walk; reload BX before
// each store so cgexpr can clobber AX/BX.
// si.totsize is slot-padded; use
// structnaturalsize for the type-size query.
// register RECV reads AX/DX/CX at 8-byte
// granularity — size via structabisize (cstage
// SSoT lu->size, check.c:760; cgen.c:7720
// sz=lu->size at the receive twin).
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_CALL
@@ -22691,7 +22691,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -22746,9 +22746,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff, ssz);
fi.foff);
return;
};
};
@@ -22909,7 +22908,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -22960,9 +22959,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff, ssz);
lc.off + fi.foff);
return;
};
};
@@ -23144,7 +23142,7 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
let ssz: i32 = structabisize(ssi);
if (ssz <= 24) {
let tlm: i32 = ssz - (ssz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -23199,9 +23197,8 @@ fn cgassign(c: *cgen, n: *node) void = {
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff, ssz);
fi.foff);
return;
};
};
@@ -23678,9 +23675,10 @@ fn cgassign(c: *cgen, n: *node) void = {
&& leafstruct) {
let lsi: *structinfo = structlookup(c, leafname);
if (lsi != nil) {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
// register RECV reads AX/DX/CX at 8-byte
// granularity — size via structabisize
// (cstage SSoT lu->size, check.c:760).
let lsz: i32 = structabisize(lsi);
if (lsz <= 24) {
let tlm: i32 = lsz - (lsz / 8) * 8;
if (tlm == 0 || tlm == 1
@@ -23761,9 +23759,6 @@ fn cgassign(c: *cgen, n: *node) void = {
&& leafstruct) {
let lsi: *structinfo = structlookup(c, leafname);
if (lsi != nil) {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
let dmode: i32 = 0;
let ddisp: i32 = rootoff + totaloff;
if (ptrroot) {
@@ -23776,7 +23771,7 @@ fn cgassign(c: *cgen, n: *node) void = {
};
cgstructlitfill(c, lsi, n.rhs,
dmode, rootoff, rootname,
ddisp, lsz);
ddisp);
return;
};
};
@@ -25119,7 +25114,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
// the dest pointer IS the struct base.
cgstructlitfill(c, sret_si, rhs,
1, sretargoff, emptys,
0, scs);
0);
};
} else {
let rl: *local = localfindnode(c, rhs.str);
@@ -25992,26 +25987,40 @@ fn cglet(c: *cgen, n: *node) void = {
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; };
};
// Zero-fill extent. cstage sizes the run on `lu->size`
// (the maxalign-rounded ABI size, check.c:760); wwstage's
// `sz` from letslotsize is slot-padded (round-to-8), so a
// struct with maxalign<8 and a sub-8 tail would over-zero
// MOVQ where cstage emits MOVL/MOVB. Read structabisize
// for a struct-typed let to converge; slot allocation
// stays on `sz` (frame uses slot-padded slots).
let zsz: i32 = sz;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TNAME) {
let szi: *structinfo = structlookupchain(c, n.lhs);
if (szi != nil) { zsz = structabisize(szi); };
};
};
if (typeis8byteprimitive(c, n.lhs)) {
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
} else { if (!isarr) { if (sz > 8) {
} else { if (!isarr) { if (zsz > 8) {
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= sz) {
for (zi + 8 <= zsz) {
emitline("\tMOVQ\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= sz) {
for (zi + 4 <= zsz) {
emitline("\tMOVL\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < sz) {
for (zi < zsz) {
emitline("\tMOVB\tAX, ");
emitoff((off + zi): i64);
emitline("(BP)\n");

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