cgen: type-key wwstage struct-layout at 3 sites via stamped tinfo (#21)

wwstage cgen resolved struct LAYOUT by bare-leaf name (structlookup /
structparamsize) at three caller-side sites — the by-value arg push
(cgenutil), the let-receive copy width (cgenstmt), and the field-read
offset (cgenexpr). Under a cross-module same-leaf collision (two modules
each exporting a `pair`, 16B vs 24B) the name lookup first-matches the
WRONG type, so the push dropped the 2nd eightbyte, the receive over-copied,
and the field read the wrong offset. cstage type-keys off the stamped
tinfo and is correct; this aligns wwstage UP to it (ww-only change).

Route all three sites through the stamped node.type_ via a new
structabisizetn(*tinfo) accessor (push + receive) and tichase(type_).fields
(field-read, structlookupchain removed). One commit (rule-11 carve-out):
the collision drives all three at once and no per-site fixture isolates, so
it cannot bisect-split. A scoped slice of the #209/#211 name-keyed-cgen
cluster retirement; the cgdot *struct-ptr/global and let-copy siblings stay
name-keyed and are filed (#31).

New table-driven test 793_xmod_struct_argpush_collide_run (4 scenarios:
push/recv/field over 16B and 12B tails) reddens under a revert of the three
cgen files. Full make test green (336 passed); make sizelint clean.
This commit is contained in:
2026-06-29 11:45:08 +09:00
parent 7b9488706b
commit 5ae6e3419e
5 changed files with 435 additions and 69 deletions

View File

@@ -2583,29 +2583,38 @@ fn cgletbody(c: *cgen, n: *syntax.node, off: i32) void = {
};
};
if (rhs.kind == syntax.nkind.N_CALL) {
let sname: str;
sname.ptr = nil; sname.len = 0;
if (tn != nil) {
if (tn.kind == syntax.nkind.N_TNAME) {
sname = tn.str;
};
};
if (sname.len > 0) {
let lsi: *structinfo = structlookup(c, sname);
if (lsi != nil) {
// ≤24B register RECV: the value arrives packed
// in AX/DX/CX, so size by the maxalign-rounded
// ABI size (cstage lu->size), not the natural
// extent — see structabisize (#169).
let lsz: i32 = structabisize(lsi);
let tlm: i32 = lsz - (lsz / 8) * 8;
if (lsz <= 24) {
if (tlm == 0 || tlm == 1
|| tlm == 2 || tlm == 4) {
cgexpr(c, rhs);
cgaggregstore(c, "BP", off, lsz, true);
c.lastwasreturn = 0;
return;
//
// #21/#224: size off the checker-STAMPED tinfo
// (structabisizetn = tichase(tn.type_).size, the
// maxalign-rounded ABI size = check.ww:2467),
// NOT structlookup(tn.str). On a cross-module
// same-leaf collision the inferred-let's tn.str
// is a bare leaf that structlookup mis-resolves to
// a FOREIGN same-leaf struct → the recv copied that
// struct's word count (a 24B foreign over-copies a
// 16B local, spilling into a neighbour slot). The
// stamped tinfo carries the right size regardless of
// collision; byte-id with structabisize on a
// resolving lookup. Mirrors the sibling array arm
// below (already tn.type_-keyed). cstage is
// type-keyed (lu->size) — align ww UP.
let lsz: i32 = structabisizetn(tn.type_: *syntax.tinfo);
if (lsz > 0) {
let tlm: i32 = lsz - (lsz / 8) * 8;
if (lsz <= 24) {
if (tlm == 0 || tlm == 1
|| tlm == 2 || tlm == 4) {
cgexpr(c, rhs);
cgaggregstore(c, "BP", off, lsz, true);
c.lastwasreturn = 0;
return;
};
};
};
};