wcc/ww: whole-struct field copy uses the source's natural length

The four wwstage whole-struct field-copy sites (cgenexpr.ww) copied
`ssi.totsize` — the slot-padded, round-8 structinfo size — instead of
the SOURCE struct's natural size. cstage copies `f->type->size` (the
field struct's aligned r.size; cmd/w6c/cgen.c:5302). wwstage over-copied
into the field's slot padding.

Fix: length = copysrcnatsize(c, n.rhs) = tichase(src.type_).size, read
from the SOURCE node's stamped tinfo (the checker's natural r.size,
check.ww N_TSTRUCT). This never reads structinfo / fi.foff / fi.fsz, so
it is correct at HEAD unconditionally and independent of the
registerstruct natural-offset change (#44/#55) that follows — a pure
wwstage convergence onto the length cstage already emits. Distinct from
the existing structnaturalsize (structinfo max(foff+fsz), a #44-coupled
source).

LENGTH ONLY. The ragged-tail completeness (both stages' field copies
inline a tail handling only {4,1}; a natural size %8 in {2,3,5,6,7}
falls through to an 8-byte MOVQ over-read) is a SEPARATE both-stage
class — cstage cgen.c:5302 has the identical incomplete tail — folded
into #73 (route both stages' field copies through the canonical greedy
aggcopy emitter). Touching only ww's tail here would create a gate-blind
cs!=ww on narrow-tail inputs, so it is deliberately left for the
both-stage fix.

No isolated runtime repro: the over-copy writes [natural, totsize),
which under HEAD's slot-padded field layout is the field's OWN padding
(the successor parks at the next slot). It only becomes a clobber once
#44 packs the successor at its natural offset (the 681 ragged_tail_12B
regression that forced this ordering). So this commit is byte-id-clean
and a no-op on the present corpus; its proof is the all-green run plus
the #44 commit that depends on it.
This commit is contained in:
2026-06-13 13:15:31 +09:00
parent 559b77db40
commit a00d052833
3 changed files with 96 additions and 12 deletions

View File

@@ -25018,6 +25018,34 @@ fn aggcopy(c: *cgen, sz: i32) void = {
};
};
// copysrcnatsize — natural byte width of a whole-struct copy's SOURCE
// operand, read from the source node's stamped tinfo (tichase peels
// NAMED). #71: the four whole-struct field-copy sites must move this many
// bytes, NOT structinfo.totsize (slot-padded, round-8) which over-copies
// into slot padding and clobbers a natural-offset successor once #44 packs
// it there. Reads the source NODE's type, never fi.foff/fi.fsz or
// structinfo, so #71 stays independent of #44's registerstruct offset
// change. Equals cstage's `str_fu->size` (cmd/w6c/cgen.c:5302 SSoT) — the
// field struct's aligned r.size (check.ww N_TSTRUCT), distinct from the
// existing structnaturalsize (which reads structinfo max(foff+fsz), a
// #44-coupled source). The ragged-tail completeness shared by both stages'
// field copies is a separate class, tracked under #73 / the aggcopy
// emitter choke-point.
fn copysrcnatsize(c: *cgen, src: *node) i32 = {
if (src == nil || src.type_ == nil) {
let msg: str = "#71: whole-struct copy source has no stamped tinfo\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let ti: *tinfo = tichase(src.type_: *tinfo);
if (ti == nil) {
let msg: str = "#71: whole-struct copy source tinfo chase yielded nil\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
return ti.size: i32;
};
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
// expression into dstreg; returns true when the shape is wired, false
// otherwise (the caller loud-stops — rule 7, never a silent drop).
@@ -33259,7 +33287,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -33485,7 +33513,7 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
let srhs: *local = localfindnode(c, n.rhs.str);
if (ssi != nil) { if (srhs != nil) {
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -33772,7 +33800,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -34415,7 +34443,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("(SB), CX\n");
};
};
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");

View File

@@ -1699,6 +1699,34 @@ fn aggcopy(c: *cgen, sz: i32) void = {
};
};
// copysrcnatsize — natural byte width of a whole-struct copy's SOURCE
// operand, read from the source node's stamped tinfo (tichase peels
// NAMED). #71: the four whole-struct field-copy sites must move this many
// bytes, NOT structinfo.totsize (slot-padded, round-8) which over-copies
// into slot padding and clobbers a natural-offset successor once #44 packs
// it there. Reads the source NODE's type, never fi.foff/fi.fsz or
// structinfo, so #71 stays independent of #44's registerstruct offset
// change. Equals cstage's `str_fu->size` (cmd/w6c/cgen.c:5302 SSoT) — the
// field struct's aligned r.size (check.ww N_TSTRUCT), distinct from the
// existing structnaturalsize (which reads structinfo max(foff+fsz), a
// #44-coupled source). The ragged-tail completeness shared by both stages'
// field copies is a separate class, tracked under #73 / the aggcopy
// emitter choke-point.
fn copysrcnatsize(c: *cgen, src: *node) i32 = {
if (src == nil || src.type_ == nil) {
let msg: str = "#71: whole-struct copy source has no stamped tinfo\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let ti: *tinfo = tichase(src.type_: *tinfo);
if (ti == nil) {
let msg: str = "#71: whole-struct copy source tinfo chase yielded nil\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
return ti.size: i32;
};
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
// expression into dstreg; returns true when the shape is wired, false
// otherwise (the caller loud-stops — rule 7, never a silent drop).
@@ -9940,7 +9968,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -10166,7 +10194,7 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
let srhs: *local = localfindnode(c, n.rhs.str);
if (ssi != nil) { if (srhs != nil) {
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -10453,7 +10481,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -11096,7 +11124,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("(SB), CX\n");
};
};
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");

View File

@@ -25018,6 +25018,34 @@ fn aggcopy(c: *cgen, sz: i32) void = {
};
};
// copysrcnatsize — natural byte width of a whole-struct copy's SOURCE
// operand, read from the source node's stamped tinfo (tichase peels
// NAMED). #71: the four whole-struct field-copy sites must move this many
// bytes, NOT structinfo.totsize (slot-padded, round-8) which over-copies
// into slot padding and clobbers a natural-offset successor once #44 packs
// it there. Reads the source NODE's type, never fi.foff/fi.fsz or
// structinfo, so #71 stays independent of #44's registerstruct offset
// change. Equals cstage's `str_fu->size` (cmd/w6c/cgen.c:5302 SSoT) — the
// field struct's aligned r.size (check.ww N_TSTRUCT), distinct from the
// existing structnaturalsize (which reads structinfo max(foff+fsz), a
// #44-coupled source). The ragged-tail completeness shared by both stages'
// field copies is a separate class, tracked under #73 / the aggcopy
// emitter choke-point.
fn copysrcnatsize(c: *cgen, src: *node) i32 = {
if (src == nil || src.type_ == nil) {
let msg: str = "#71: whole-struct copy source has no stamped tinfo\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let ti: *tinfo = tichase(src.type_: *tinfo);
if (ti == nil) {
let msg: str = "#71: whole-struct copy source tinfo chase yielded nil\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
return ti.size: i32;
};
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
// expression into dstreg; returns true when the shape is wired, false
// otherwise (the caller loud-stops — rule 7, never a silent drop).
@@ -33259,7 +33287,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -33485,7 +33513,7 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
let srhs: *local = localfindnode(c, n.rhs.str);
if (ssi != nil) { if (srhs != nil) {
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -33772,7 +33800,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");
@@ -34415,7 +34443,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("(SB), CX\n");
};
};
let ssz: i32 = ssi.totsize;
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
emitline("\tMOVQ\t");