cgen: convert the struct-store loop arms to a tinfo-native fill (#31 c2)

The W1/W2/W5/W4b store / structlit-fill arms still resolved struct field
LAYOUT by bare-leaf name (structlookupchain / structlookup / letvarstructinfo)
inside the per-field store loop -- the write-side twin of commit-1's read fix.
Under a cross-module same-leaf collision (two modules each exporting a `pair`,
16B vs 24B) the field is stored at the wrong offset/width; an interior 2-byte
field store silently corrupted neighbours. cstage walks the resolved Type*
(never a name) and is correct; this aligns wwstage UP (ww-only change).

Convert each store loop to a tinfo-native fill. Two new helpers in cgenutil.ww:
sretretsizetn (a verbatim port of cstage cg_sret_retsize -- 4 kind-branches,
no TY_BANG, dropping the #129 module-juggling that was pure name-keying
compensation, redundant once the lookup walks resolved tinfo) and
cgstructlitfilltn (the tinfo-native twin of cgstructlitfill, recursing on
tichase(tf.type_).fields with NO name lookup at any nesting level). Supporting
twins storeopsz / tfieldlookup / cgdotfieldhardstoptn. Because the fill never
bottoms out on a name, the W4b "decisive wall" (a structlit that needed a
(sname,smod)-keyed structinfo the stamped leaf-only tinfo couldn't supply)
dissolves by construction, with no module-aware tinfo->structinfo bridge.

This CLOSES the nine in-loop nested sub-arms (N_CALL / N_STRUCTLIT / N_IDENT
field receive) by construction -- the tf walk has no name lookup. The
non-reddenable global arms (W5/W4b) are converted-for-construction: a qualified
global decl resolves its bare leaf correctly today, so byte-id (cs.s==ww.s) is
their net, not a value pin. `!T` is kind-preserving in both stages
(resolve_type / tinfofornode), so a `!struct` sret sizes as the inner struct,
never TAGGED -- the #129 drop changes no size.

Completes the 2-commit #31 arc (RULING R2 / Opt-2, .ai/ken-31-spec.md): the
#224 name-keyed receiver-layout cluster is now closed by construction. The
residual nested-field surface (the shared cgstructlitfill kept for its non-#31
callers + the cgenstmt/cgenutil let-receive/fill family) is tracked as #32.

Pins: test/wcc/797 value-asserts ptr-WRITE (W1) and val-WRITE (W2), each
reddening under independent per-arm revert; nestfill_box drives
cgstructlitfilltn's nested recursion (bootstrap-uncovered -- the corpus has no
struct-literal field store -- so the value pin is its only net).
This commit is contained in:
2026-06-29 15:19:41 +09:00
parent eb28dcd5b7
commit 5bfcd8bd6b
3 changed files with 813 additions and 185 deletions

View File

@@ -350,6 +350,34 @@ fn cgdotfieldhardstop(c: *cgen, ftn: *syntax.node) void = {
};
};
// cgdotfieldhardstoptn — tinfo twin of cgdotfieldhardstop (#31): the
// single-dot field compound combine speaks integer ABI only, so a
// tagged/str/slice/float field compound loud-stops. Keyed off the
// checker-STAMPED field tinfo (the receiver-layout tf walk), not a
// tnode. Messages verbatim from cgdotfieldhardstop. (#34/rule-7)
fn cgdotfieldhardstoptn(c: *cgen, ft: *syntax.tinfo) void = {
if (syntax.typeistagged(ft)) {
let m: str = "single-dot field compound on tagged field not wired (#34/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (syntax.typeisstr(ft)) {
let m: str = "single-dot field compound on str field not wired (#34/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (syntax.typeisslice(ft)) {
let m: str = "single-dot field compound on slice field not wired (#34/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (syntax.typeisfloat(ft)) {
let m: str = "single-dot field compound on float field not wired (#34/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
};
fn cgtryunwcursor(c: *cgen, n: *syntax.node, opname: str) void = {
let u: *syntax.tinfo = nil;
if (n.lhs != nil) { u = n.lhs.type_: *syntax.tinfo; };
@@ -10592,34 +10620,39 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (tn != nil) { lkind = tn.kind; };
// Pointer-to-struct: deref then store.
if (lkind == syntax.nkind.N_TPTR) {
let inner: *syntax.node = tn.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (inner != nil) {
if (inner.kind == syntax.nkind.N_TNAME) { sname = inner.str; };
};
if (sname.len > 0) {
// structlookupchain (#22) handles the
// alias-chain miss; same shape as the
// cgdot pointer-to-struct read site.
let si: *structinfo = structlookupchain(c, inner);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
let fn_: str = fi.fname;
if (syntax.streq(fn_, fld)) {
// #31: resolve the *struct field OFFSET + type off the
// checker-STAMPED receiver tinfo (tichase(base.type_)->.sub),
// NOT the name-keyed leaf lookup. A cross-module same-leaf
// collision makes the bare leaf mis-resolve to a FOREIGN
// same-leaf struct -> the field is STORED at the wrong
// offset/width (the inferred-local ptr-WRITE row). The
// stamped tinfo carries the right layout; mirror cstage
// type_chase_named(bu->sub)->fields. The in-loop #32 nested
// struct-receive/structlit/ident sub-arms close by
// construction (the tf walk has no name lookup). Non-struct
// pointees fall through to the str/slice arm.
let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); };
if (sti != nil) {
if (sti.kind == syntax.tykind.TY_STRUCT) {
let tf: *syntax.tfield = sti.fields;
for (tf != nil) {
if (syntax.streq(tf.name, fld)) {
let foff: i32 = tf.offset: i32;
let ftraw: *syntax.tinfo = tf.type_;
let fu: *syntax.tinfo = tichase(ftraw);
// Tagged-union field via *struct base — full slot
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
// fell through to the scalar store and dropped tag
// + payload.
if (n.op == syntax.tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
&& syntax.typeistagged(ftraw)) {
let fsz: i32 = fu.size: i32;
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo,
n.rhs, "BX", fi.foff, fsz);
cgwidentaggedstore(c, ftraw,
n.rhs, "BX", foff, fsz);
return;
};
// #234-tail: via-ptr (`p.f`) sret field STORE. The dest must
@@ -10629,7 +10662,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_CALL
&& sretretsize(c, fi.tnode) > 0) {
&& sretretsizetn(c, ftraw) > 0) {
let m234: str = "#234-tail: over-cap tuple sret store to via-ptr field dest unsupported\n";
os.write(2, m234.ptr, m234.len: u64);
os.exit(1);
@@ -10654,20 +10687,16 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
&& n.rhs != nil
&& (n.rhs.kind == syntax.nkind.N_CALL
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|| n.rhs.kind == syntax.nkind.N_TRYPROP)
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structabisize(ssi);
|| n.rhs.kind == syntax.nkind.N_TRYPROP)) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
let ssz: i32 = fu.size: i32;
if (ssz <= 24) {
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
cgexpr(c, n.rhs);
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
cgaggregstore(c, "BX", fi.foff, ssz, false);
cgaggregstore(c, "BX", foff, ssz, false);
return;
};
};
@@ -10679,26 +10708,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// field store.
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff);
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
cgstructlitfilltn(c, fu, n.rhs, 1, lc.off, "",
foff);
return;
};
};
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_IDENT
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
&& n.rhs.kind == syntax.nkind.N_IDENT) {
let srhs: *local = localfindnode(c, n.rhs.str);
if (ssi != nil) { if (srhs != nil) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
@@ -10709,7 +10730,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 8;
};
@@ -10718,7 +10739,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 4;
};
@@ -10727,7 +10748,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 2;
};
@@ -10736,7 +10757,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 1;
};
@@ -10748,11 +10769,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let lop: str = fieldloadop(c, fi);
let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32);
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline(", BX\n");
emitline("\tPUSHQ\tBX\n");
};
@@ -10762,9 +10783,9 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// PLUSEQ is commutative; MINUSEQ
// needs lhs - rhs (BX is old lhs,
// AX is rhs).
cgdotfieldhardstop(c, fi.tnode);
cgdotfieldhardstoptn(c, ftraw);
let uns34: bool = false;
if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { uns34 = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; };
uns34 = syntax.typeisunsigned(ftraw);
cgdotfieldcombine(c, n.op, uns34);
};
if (n.op == syntax.tkind.TK_ASSIGN) {
@@ -10772,33 +10793,33 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// store the full 3-word {ptr,len,cap} from (AX,BX,CX).
// CX holds cap, so stage the struct addr in DX and
// store at foff/+8/+16 (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "DX");
emitdispreg(foff: i64, "DX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "DX");
emitdispreg((foff + 8): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tCX, ");
emitdispreg((fi.foff + 16): i64, "DX");
emitdispreg((foff + 16): i64, "DX");
emitline("\n");
return;
};
// f64/f32 plain `=` via *struct: cgexpr left the
// value in X0. Reload struct ptr and MOVSD/MOVSS.
if (isfloattype(c, fi.tnode)) {
if (syntax.typeisfloat(ftraw)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline("\n");
return;
};
@@ -10806,38 +10827,48 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let sop: str = fieldstoreop(c, fi);
let sop: str = storeopsz(fu.size: i32);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline("\n");
return;
};
fi = fi.finext;
tf = tf.tnext;
};
};
};
};
// Direct struct local: store at off+foff.
if (lkind == syntax.nkind.N_TNAME) {
// structlookupchain (#22) — same shape
// as the cgdot direct-local read site.
let si: *structinfo = structlookupchain(c, tn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
let fn_: str = fi.fname;
if (syntax.streq(fn_, fld)) {
// #31: resolve the value-struct field OFFSET + type off the
// checker-STAMPED receiver tinfo (tichase(base.type_)), NOT
// the name-keyed leaf lookup. A cross-module same-leaf
// collision makes the bare leaf mis-resolve to a FOREIGN
// same-leaf struct -> the field is STORED at the wrong
// offset/width (the inferred-local val-WRITE row, ww=107).
// The stamped tinfo carries the right layout; mirror cstage
// type_chase_named(base->type)->fields. The in-loop #32
// nested struct-receive/structlit/ident sub-arms close by
// construction (the tf walk has no name lookup).
let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
if (sti != nil && sti.kind == syntax.tykind.TY_STRUCT) {
let tf: *syntax.tfield = sti.fields;
for (tf != nil) {
if (syntax.streq(tf.name, fld)) {
let foff: i32 = tf.offset: i32;
let ftraw: *syntax.tinfo = tf.type_;
let fu: *syntax.tinfo = tichase(ftraw);
// Tagged-union field in a direct struct local —
// full slot rewrite at (lc.off + fi.foff)(BP)
// full slot rewrite at (lc.off + foff)(BP)
// via cgwidentaggedstore basereg="BP". Pre-#26
// fell through and dropped tag + payload.
if (n.op == syntax.tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo,
n.rhs, "BP", lc.off + fi.foff, fsz);
&& syntax.typeistagged(ftraw)) {
let fsz: i32 = fu.size: i32;
cgwidentaggedstore(c, ftraw,
n.rhs, "BP", lc.off + foff, fsz);
return;
};
// #234: over-cap sret STORE into a LOCAL struct field —
@@ -10845,14 +10876,14 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// (sretretsize > 0: a >24B struct OR an over-cap tuple).
// STORE-twin of the Fold-B sret RECEIVE (a937d67): point
// the callee's hidden RDI dest at the field slot
// (c.sretdestoff = lc.off + fi.foff) so it writes the
// (c.sretdestoff = lc.off + foff) so it writes the
// WHOLE value there, never the truncating generic store
// below. Mirror of cstage cgen.c (#234) field local arm.
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_CALL
&& sretretsize(c, fi.tnode) > 0) {
c.sretdestoff = lc.off + fi.foff;
&& sretretsizetn(c, ftraw) > 0) {
c.sretdestoff = lc.off + foff;
cgexpr(c, n.rhs);
c.sretdestoff = 0;
return;
@@ -10862,9 +10893,9 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// added with #5; closes #27 marker here):
// N_IDENT: word-copy from rhs slot.
// N_CALL: cgexpr → AX/DX/CX; sized stores
// directly at (lc.off+fi.foff)(BP).
// directly at (lc.off+foff)(BP).
// N_STRUCTLIT: field-walk; each inner
// field stored at +fi.foff+inner_foff(BP).
// field stored at +foff+inner_foff(BP).
// BP-rel direct, no addr scratch needed. #12: an
// unwrap `b.f = mk()!` rides the same {AX,DX,CX}
// shape (producer shift) — admit it alongside
@@ -10873,17 +10904,13 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
&& n.rhs != nil
&& (n.rhs.kind == syntax.nkind.N_CALL
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|| n.rhs.kind == syntax.nkind.N_TRYPROP)
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structabisize(ssi);
|| n.rhs.kind == syntax.nkind.N_TRYPROP)) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
let ssz: i32 = fu.size: i32;
if (ssz <= 24) {
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
cgexpr(c, n.rhs);
cgaggregstore(c, "BP", lc.off + fi.foff, ssz, false);
cgaggregstore(c, "BP", lc.off + foff, ssz, false);
return;
};
};
@@ -10894,26 +10921,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// no BX reload.
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff);
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
cgstructlitfilltn(c, fu, n.rhs, 0, 0, "",
lc.off + foff);
return;
};
};
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_IDENT
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
&& n.rhs.kind == syntax.nkind.N_IDENT) {
let srhs: *local = localfindnode(c, n.rhs.str);
if (ssi != nil) { if (srhs != nil) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) {
let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize
let k: i32 = 0;
for (k + 8 <= ssz) {
@@ -10921,7 +10940,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff + k): i64);
emitoff((lc.off + foff + k): i64);
emitline("(BP)\n");
k += 8;
};
@@ -10930,7 +10949,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((lc.off + fi.foff + k): i64);
emitoff((lc.off + foff + k): i64);
emitline("(BP)\n");
k += 4;
};
@@ -10939,7 +10958,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((lc.off + fi.foff + k): i64);
emitoff((lc.off + foff + k): i64);
emitline("(BP)\n");
k += 2;
};
@@ -10948,7 +10967,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((lc.off + fi.foff + k): i64);
emitoff((lc.off + foff + k): i64);
emitline("(BP)\n");
k += 1;
};
@@ -10960,11 +10979,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// scalar field: load current → push
// → eval rhs → combine → store
// (mirror cgen.c:3477 local arm).
let lop: str = fieldloadop(c, fi);
let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32);
emitline("\t");
emitline(lop);
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitoff((lc.off + foff): i64);
emitline("(BP), BX\n");
emitline("\tPUSHQ\tBX\n");
};
@@ -10973,47 +10992,47 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitline("\tPOPQ\tBX\n");
// PLUSEQ commutes; MINUSEQ needs
// lhs-rhs (BX old lhs, AX rhs).
cgdotfieldhardstop(c, fi.tnode);
cgdotfieldhardstoptn(c, ftraw);
let uns34: bool = false;
if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { uns34 = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; };
uns34 = syntax.typeisunsigned(ftraw);
cgdotfieldcombine(c, n.op, uns34);
};
// str/slice field direct: str IS []u8, so both store the
// full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16.
// BP base, no scratch reload needed; the generic fldstoreop
// below would write only AX, dropping .len/.cap (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitoff((lc.off + foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((lc.off + fi.foff + 8): i64);
emitoff((lc.off + foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((lc.off + fi.foff + 16): i64);
emitoff((lc.off + foff + 16): i64);
emitline("(BP)\n");
return;
};
// f64/f32 direct struct local store: route via X0.
if (isfloattype(c, fi.tnode)) {
if (syntax.typeisfloat(ftraw)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((lc.off + fi.foff): i64);
emitoff((lc.off + foff): i64);
emitline("(BP)\n");
return;
};
let sop: str = fieldstoreop(c, fi);
let sop: str = storeopsz(fu.size: i32);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitoff((lc.off + foff): i64);
emitline("(BP)\n");
return;
};
fi = fi.finext;
tf = tf.tnext;
};
};
};
@@ -11108,11 +11127,22 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (base.kind == syntax.nkind.N_IDENT) {
let bn: str = base.str;
if (localfindnode(c, bn) == nil) {
let si: *structinfo = letvarstructinfo(c, bn);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (syntax.streq(fi.fname, fld)) {
// #31: resolve the global value-struct field OFFSET + type
// off the checker-STAMPED receiver tinfo (tichase(base.type_)),
// NOT the name-keyed global-struct leaf lookup. A global decl
// is qualified -> non-reddenable; converted for close-by-
// construction (byte-id). Mirror cstage type-keyed N_DOT global
// arm. The in-loop #32 nested struct-receive/structlit/ident
// sub-arms close by construction (the tf walk has no name
// lookup).
let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
if (sti != nil && sti.kind == syntax.tykind.TY_STRUCT) {
let tf: *syntax.tfield = sti.fields;
for (tf != nil) {
if (syntax.streq(tf.name, fld)) {
let foff: i32 = tf.offset: i32;
let ftraw: *syntax.tinfo = tf.type_;
let fu: *syntax.tinfo = tichase(ftraw);
// #234-tail: GLOBAL (`g.f`) sret field STORE. c.sretdestoff is
// BP-relative only and can't name a global slot; the runtime
// RDI-pointer dest variant is deferred. HARD-STOP loud, never
@@ -11120,7 +11150,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_CALL
&& sretretsize(c, fi.tnode) > 0) {
&& sretretsizetn(c, ftraw) > 0) {
let m234: str = "#234-tail: over-cap tuple sret store to global field dest unsupported\n";
os.write(2, m234.ptr, m234.len: u64);
os.exit(1);
@@ -11134,20 +11164,16 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// N_STRUCTLIT: field-walk; reload BX per store.
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_CALL
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structabisize(ssi);
&& n.rhs.kind == syntax.nkind.N_CALL) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
let ssz: i32 = fu.size: i32;
if (ssz <= 24) {
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
cgexpr(c, n.rhs);
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
cgaggregstore(c, "BX", fi.foff, ssz, false);
cgaggregstore(c, "BX", foff, ssz, false);
return;
};
};
@@ -11159,26 +11185,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// field store.
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff);
&& n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) {
cgstructlitfilltn(c, fu, n.rhs, 2, 0, bn,
foff);
return;
};
};
if (n.op == syntax.tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == syntax.nkind.N_IDENT
&& fi.tnode != nil
&& fi.tnode.kind == syntax.nkind.N_TNAME
&& aliasprimsize(c, fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
&& n.rhs.kind == syntax.nkind.N_IDENT) {
let srhs: *local = localfindnode(c, n.rhs.str);
if (ssi != nil) { if (srhs != nil) {
if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) {
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
@@ -11189,7 +11207,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 8;
};
@@ -11198,7 +11216,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 4;
};
@@ -11207,7 +11225,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 2;
};
@@ -11216,7 +11234,7 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitoff((srhs.off + k): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + k): i64, "BX");
emitdispreg((foff + k): i64, "BX");
emitline("\n");
k += 1;
};
@@ -11230,19 +11248,19 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// below truncates to 1 word, silently dropping tag
// and payload.
if (n.op == syntax.tkind.TK_ASSIGN
&& istaggedtype(c, fi.tnode)) {
let fsz: i32 = slotsize(c, fi.tnode);
&& syntax.typeistagged(ftraw)) {
let fsz: i32 = fu.size: i32;
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
cgwidentaggedstore(c,
fi.tnode.type_: *syntax.tinfo,
n.rhs, "BX", fi.foff, fsz);
ftraw,
n.rhs, "BX", foff, fsz);
return;
};
if (n.op == syntax.tkind.TK_ASSIGN) {
cgexpr(c, n.rhs);
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
// str OR slice field: str IS []u8, so
// both store the full {ptr,len,cap}
// header (cstage cgen.c:5055 gates
@@ -11256,39 +11274,39 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitsymname(c, bn);
emitline("(SB), DX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "DX");
emitdispreg(foff: i64, "DX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "DX");
emitdispreg((foff + 8): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tCX, ");
emitdispreg((fi.foff + 16): i64, "DX");
emitdispreg((foff + 16): i64, "DX");
emitline("\n");
return;
};
// f64/f32 plain `=` on global struct field: value is
// in X0; LEAQ the base into BX and MOVSD/MOVSS.
if (isfloattype(c, fi.tnode)) {
if (syntax.typeisfloat(ftraw)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline("\n");
return;
};
let sop: str = fieldstoreop(c, fi);
let sop: str = storeopsz(fu.size: i32);
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline("\n");
return;
};
@@ -11296,34 +11314,34 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
// → push → eval rhs → combine →
// store. cgexpr clobbers BX, so
// re-LEAQ for the store.
let lop: str = fieldloadop(c, fi);
let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32);
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline(", BX\n");
emitline("\tPUSHQ\tBX\n");
cgexpr(c, n.rhs);
emitline("\tPOPQ\tBX\n");
cgdotfieldhardstop(c, fi.tnode);
cgdotfieldhardstoptn(c, ftraw);
let uns34: bool = false;
if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { uns34 = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; };
uns34 = syntax.typeisunsigned(ftraw);
cgdotfieldcombine(c, n.op, uns34);
let sop: str = fieldstoreop(c, fi);
let sop: str = storeopsz(fu.size: i32);
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitdispreg(foff: i64, "BX");
emitline("\n");
return;
};
fi = fi.finext;
tf = tf.tnext;
};
};
};
@@ -12305,18 +12323,20 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|| gau.kind == syntax.tykind.TY_ARRAY
|| gau.kind == syntax.tykind.TY_TUPLE) {
if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) {
let gsi49: *structinfo = nil;
if (lvftn.kind == syntax.nkind.N_TNAME) {
gsi49 = structlookup(c, lvftn.str);
};
if (gsi49 == nil) {
// #31: fill off the stamped struct tinfo (gau =
// tichase(lvftn.type_)), NOT structlookup(lvftn.str). A
// global decl is qualified -> non-reddenable; converted
// for close-by-construction (byte-id). The W4b nested
// in-loop structlookup sub-arms close by construction
// (cgstructlitfilltn recurses on tichase(tf.type_)).
if (gau.kind != syntax.tykind.TY_STRUCT) {
// wwstage-only bail (anonymous
// type; the @placescr precedent).
let m49d: str = "assign: structlit layout unresolved (rule-7)\n";
os.write(2, m49d.ptr, m49d.len: u64);
os.exit(1);
};
cgstructlitfill(c, gsi49, n.rhs, 2, 0, nm, 0);
cgstructlitfilltn(c, gau, n.rhs, 2, 0, nm, 0);
return;
};
if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) {