w6c+wwstage: source sub-8 value-struct ABI-size from tinfo.size at zero-init+DATAW (#254)

wwstage conflated SLOT-size (round-to-8, for frame) with ABI-size (true)
for a nested value-struct. A nested value-struct field is sized via
fieldsize() (TY_STRUCT -> ti.slotsize = 8), poisoning structabisize and
registerstruct si.totsize to 8 for a struct whose true ABI size is 4.
Two emission sites then over-sized, both SILENT cs!=ww divergences:

  D1 (local, cgenstmt.ww cglet): zsz = structabisize = 8 hit the
     `zsz == 8` zero arm (#213) -> a stray `MOVQ $0, off(BP)` cstage
     never emits (ABI 4 is sub-8 -> left uninit per the shared no-rhs
     zero-init policy).
  D2 (global, cgen.ww emitletdataw): the struct zero arm wrote
     letemitsize/si.totsize = 8 DATAW bytes; cstage cg_let_emit_size
     returns u->size = 4.

Fix sources the zero-init extent from the type table's tinfo.size
(peeling TY_NAMED) at both sites — the same value cstage reads
(cgen.c:8397 / :978). fieldsize / registerstruct / frame slot-padding
stay UNTOUCHED: moving the fix into the size helpers would shift
nested-struct field offsets and re-diverge other byte-id. Pure
wwstage-align-down; cstage cmd/w6c/cgen.c unchanged.

Test 949_valstruct_subsize_run: D1 local + D2 global over ABI sizes
1/2/4 (the whole sub-8 / non-8-multiple class), each cstage-run +
cs==ww .s byte-id; plus a >8 (16B) local+global NEGATIVE control
proving the fix didn't disable legitimate multi-word zero-init.

Regen w6c + wwdump main.combined.ww (cgen is compiler-imported, #110).
This commit is contained in:
2026-06-02 05:37:47 +09:00
parent e92708ecda
commit be23d7227a
6 changed files with 369 additions and 24 deletions

View File

@@ -28899,17 +28899,29 @@ fn cglet(c: *cgen, n: *node) void = {
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).
// (the natural ABI size from the type table, cgen.c:8397);
// 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 nothing. Source the extent from the
// type table's tinfo.size for a struct-typed let to converge;
// slot allocation stays on `sz` (frame uses slot-padded slots).
// #254: structabisize is NOT a sound ABI-size source here — it
// sums fieldsize(), which slot-pads a nested value-struct field
// to 8, so a sub-8 outer struct (e.g. `struct{struct{[4]u8}}`,
// ABI 4) read 8 and emitted a stray MOVQ $0 cstage doesn't.
// fieldsize / registerstruct / frame slot-padding stay
// UNTOUCHED — moving the fix there would shift field offsets.
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 (szi != nil) {
let ti: *tinfo = n.lhs.type_: *tinfo;
for (ti != nil && ti.kind == tykind.TY_NAMED) {
ti = ti.under;
};
if (ti != nil) { zsz = ti.size: i32; };
};
};
};
if (typeis8byteprimitive(c, n.lhs)) {
@@ -32324,13 +32336,29 @@ fn emitletdataw(c: *cgen, file: *node) void = {
// A struct literal init isn't compile-time
// evaluated yet; skip and the link will surface
// an undefined-symbol error if referenced.
// #254: the zero-fill byte count comes from the
// type table's tinfo.size (cstage cg_let_emit_size
// returns u->size, cgen.c:978), NOT letemitsize/
// si.totsize — registerstruct rounds the nested
// value-struct field's slot to 8, so a sub-8 outer
// struct (ABI 4) over-emitted DATAW 8 bytes vs
// cstage's 4. registerstruct / fieldsize / frame
// slot-padding stay UNTOUCHED (field offsets).
if (issg) {
if (d.rhs == nil) {
let zsz: i32 = sz;
if (d.lhs != nil) {
let ti: *tinfo = d.lhs.type_: *tinfo;
for (ti != nil && ti.kind == tykind.TY_NAMED) {
ti = ti.under;
};
if (ti != nil) { zsz = ti.size: i32; };
};
emitline("DATAW ");
emitsymnamehint(c, nm, d.nmod);
emitline("(SB),\"");
let i: i32 = 0;
for (i < sz) {
for (i < zsz) {
emitdatawbyte(0u8);
i += 1;
};