diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 2ab930f6..b865e53c 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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"); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 326dd1c8..32eff84b 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index a1dd4560..d4e5cc1c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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");