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) {

View File

@@ -1695,6 +1695,19 @@ fn loadopsz(sigd: bool, sz: i32) str = {
return "MOVQ";
};
// storeopsz — store op when the size has already been resolved
// upstream off a stamped tinfo (the #31 receiver-layout store choke-
// point). Size-only twin of fieldstoreop's body {1->MOVB, 2->MOVW,
// 4->MOVL, else MOVQ}; the store side carries no signedness. Preserves
// the #15 sub-8 guard (a 2B tail field stores MOVW, not a slot-wide
// MOVQ into saved BP).
fn storeopsz(sz: i32) str = {
if (sz == 1) { return "MOVB"; };
if (sz == 2) { return "MOVW"; };
if (sz == 4) { return "MOVL"; };
return "MOVQ";
};
// localloadop — read instruction for a scalar local/let load. Same
// dispatch as fieldloadop, but keyed on the value's own tnode.type_.
// Lets the caller emit MOVSXD/MOVSWQ/MOVSBQ on a signed-narrow slot
@@ -2214,6 +2227,80 @@ export fn sretretsize(c: *cgen, t: *syntax.node) i32 = {
return n;
};
// tfieldlookup — the named field `fld` in struct tinfo `sti`'s stamped
// field chain, else nil. The #31 receiver-layout store choke-point: the
// tfield twin of the name-keyed `for (fi != nil)` fieldinfo walk. A
// cross-module same-leaf collision that mis-resolved structlookupchain
// cannot reach a stamped tinfo, so every field-OFFSET/width/kind keyed
// off the returned tf is collision-immune.
fn tfieldlookup(sti: *syntax.tinfo, fld: str) *syntax.tfield = {
if (sti == nil) { return nil; };
let tf: *syntax.tfield = sti.fields;
for (tf != nil) {
if (syntax.streq(tf.name, fld)) { return tf; };
tf = tf.tnext;
};
return nil;
};
// sretretsizetn — verbatim port of cstage cg_sret_retsize (cmd/w6c/
// cgen.c:380) keyed on the checker-STAMPED *tinfo. The tinfo twin of
// sretretsize, the #31 store-loop choke-point (the sret hard-stop /
// sret-receive field sub-arms). Walks resolved tinfo, so the #129
// aliassamemod/module-juggling sretretsize carries — pure name-keying
// compensation for structlookup's any-module fallback — is NOT needed
// and absent: tichase(t) peels NAMED then falls through all four kind
// tests to 0, exactly what #129 reconstructed by name (cstage sees a
// resolved Type*, never a name). There is NO TY_BANG tinfo kind — `!T`
// folds into TY_TAGGED's per-variant iserror (typ.ww:82-90), so there
// is no error-peel branch; mirror cstage's four branches exactly. `c`
// kept for call-site symmetry with sretretsize (unused: tinfo carries
// the module-independent layout).
fn sretretsizetn(c: *cgen, t: *syntax.tinfo) i32 = {
let u: *syntax.tinfo = tichase(t);
if (u == nil) { return 0; };
if (u.kind == syntax.tykind.TY_STRUCT) {
if (u.size: i32 <= 24) { return 0; };
return u.size: i32;
};
// #38: a tagged union rides AX(tag)+DX/CX/R8 = TUPLE_GPCAP
// eightbytes; nullable folds to one ptr word. Mirrors cstage
// cg_sret_retsize TY_TAGGED arm.
if (u.kind == syntax.tykind.TY_TAGGED) {
if (u.nullable != 0) { return 0; };
if (u.size: i32 <= TUPLE_GPCAP * 8) { return 0; };
return u.size: i32;
};
// #267: arrays ride the struct-return ABI (≤24 reg / >24 sret).
if (u.kind == syntax.tykind.TY_ARRAY) {
if (u.size: i32 <= 24) { return 0; };
return u.size: i32;
};
// #10: over-cap tuple → sret. Walk the positional element chain
// over the SAME caps the SEND/receive use (a float = 1 SSE
// eightbyte; str/slice/tagged ride their box-rounded GP stride via
// tupeslot; a scalar 1 GP word). Mirrors cstage cg_sret_retsize
// TY_TUPLE arm; tupleelems is the tinfo positional chain.
if (u.kind == syntax.tykind.TY_TUPLE) {
let gptotal: i32 = 0;
let ssecount: i32 = 0;
let te: *syntax.ttupleelem = u.tupleelems;
for (te != nil) {
if (syntax.typeisfloat(te.type_)) {
ssecount = ssecount + 1;
} else {
gptotal = gptotal + tupeslot(te.type_) / 8;
};
te = te.tnext;
};
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP) {
return u.size: i32;
};
return 0;
};
return 0;
};
// callsretsize — if N_CALL `n`'s callee returns a plain TY_STRUCT
// > 24B, return its natural size; else 0. Wraps sretretsize over the
// callee's resolved return type, used by cglet / cgassign receive
@@ -5738,3 +5825,419 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *syntax.node, bpoff: i32) v
if (si == nil) { return; };
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
};
// cgstructlitfilltn — tinfo-native twin of cgstructlitfill, the #31
// receiver-layout fill choke-point. Walks the struct's checker-STAMPED
// tfield chain (lu.fields) and keys every per-field decision off the
// resolved tf.type_ (kind / size / signedness) — NO structlookup, NO
// fi.tnode names. Nested struct fields recurse on tichase(tf.type_), so
// a cross-module same-leaf collision (the #224 cluster) cannot reach a
// name lookup at any nesting level. Faithful mirror of cstage
// cg_structlit_fill, which recurses on type_chase_named(fl->type)
// (cgen.c:3179) over resolved Type*. Emission (mode reloads, register
// staging, store ordering) is byte-identical to cgstructlitfill; only
// the LAYOUT source flips from si/fieldinfo to lu/tfield. tinfo.size
// equals structabisize/fi.fsz for a struct field (check.ww:2468 ABI
// formula; NAMED.size==under.size at :2234), so the size-keyed stores
// match. Modes: 0=DST_BP, 1=DST_PTR_LOCAL(srcoff), 2=DST_GLOBAL(srcname),
// 3=DST_PTR_SP. Routed by the W1/W2/W5 N_STRUCTLIT sub-arms + W4b.
fn cgstructlitfilltn(c: *cgen, lu: *syntax.tinfo, lit: *syntax.node,
mode: i32, srcoff: i32, srcname: str,
disp: i32) void = {
let s: *syntax.tinfo = tichase(lu);
if (s == nil) { return; };
if (s.kind != syntax.tykind.TY_STRUCT) { return; };
let basereg: str = "BP";
if (mode != 0) { basereg = "BX"; };
let totsize: i32 = s.size: i32;
if (lit.op == syntax.tkind.TK_ELLIPSIS) {
// `..., ...` autofill — zero the entire slot first so
// unmentioned fields read as 0. Sized stores: 8/4/1. For
// non-BP modes, reload BX once before the loop.
emitline("\tXORQ\tAX, AX\n");
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
let zi: i32 = 0;
for (zi + 8 <= totsize) {
emitline("\tMOVQ\tAX, ");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 8;
};
for (zi + 4 <= totsize) {
emitline("\tMOVL\tAX, ");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 4;
};
for (zi < totsize) {
emitline("\tMOVB\tAX, ");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 1;
};
};
let fieldnode: *syntax.node = lit.list;
for (fieldnode != nil) {
if (fieldnode.kind == syntax.nkind.N_FIELD) {
let fname: str = fieldnode.str;
let tf: *syntax.tfield = tfieldlookup(s, fname);
if (tf != nil) {
let foff: i32 = tf.offset: i32;
let ftraw: *syntax.tinfo = tf.type_;
let fu: *syntax.tinfo = tichase(ftraw);
let fsz: i32 = 0;
if (fu != nil) { fsz = fu.size: i32; };
// Tagged-union field: delegate to the shared
// widening writer (str/scalar/struct-lit/ident
// payload + tagged-subset tag remap). For non-BP
// modes, reload BX first.
if (syntax.typeistagged(ftraw)) {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
cgwidentaggedstore(c, ftraw,
fieldnode.lhs, basereg,
disp + foff, fsz);
} else {
// Nested struct-typed structlit value: recurse at
// the field's offset off the resolved field tinfo
// (NO structlookup). Pre-#17/#18 the cgexpr-then-
// store tail would land AX = first qword and the
// rest silently stayed zero.
let nested: bool = false;
if (fieldnode.lhs != nil) {
if (fieldnode.lhs.kind == syntax.nkind.N_STRUCTLIT) {
if (fu != nil) {
if (fu.kind == syntax.tykind.TY_STRUCT) {
cgstructlitfilltn(c, fu,
fieldnode.lhs,
mode, srcoff, srcname,
disp + foff);
nested = true;
};
};
};
};
// Nested struct-typed CALL value (#20): cgexpr
// leaves AX/DX/CX per #4's cgreturn ABI; the
// choke-point stores full 8B chunks + a sized tail.
// Guard fu.size <= 24 (#14: every in-cap tail incl.
// 3/5/6/7); >24B falls through (sret deferred).
// INVARIANT: between cgexpr(N_CALL) and the AX/DX/CX
// stores, only the BX reload may intervene.
let callwhole: bool = false;
if (!nested) {
if (fieldnode.lhs != nil) {
if (fieldnode.lhs.kind == syntax.nkind.N_CALL) {
if (fu != nil) {
if (fu.kind == syntax.tykind.TY_STRUCT) {
let cfsz: i32 = fu.size: i32;
if (cfsz <= 24) {
cgexpr(c, fieldnode.lhs);
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
cgaggregstore(c, basereg, disp + foff, cfsz, false);
callwhole = true;
};
};
};
};
};
};
if (nested) {
// recursed above
} else if (callwhole) {
// stored above
} else if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) {
// str IS []u8: 3-word {ptr,len,cap}. cgexpr
// leaves AX/BX/CX; for non-BP modes the dst
// base goes in DX to dodge BX=len / CX=cap.
cgexpr(c, fieldnode.lhs);
if (mode == 0) {
emitline("\tMOVQ\tAX, ");
emitoff((disp + foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((disp + foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((disp + foff + 16): i64);
emitline("(BP)\n");
} else {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), DX\n");
} else { if (mode == 3) {
emitline("\tMOVQ\t(SP), DX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), DX\n");
}; };
emitline("\tMOVQ\tAX, ");
emitdispreg((disp + foff): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((disp + foff + 8): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tCX, ");
emitdispreg((disp + foff + 16): i64, "DX");
emitline("\n");
};
} else if (fu != nil && fu.kind == syntax.tykind.TY_ARRAY
&& fieldnode.lhs != nil
&& fieldnode.lhs.kind == syntax.nkind.N_ARRLIT) {
// #249: array field from an N_ARRLIT. element-
// wise store at disp+foff+i*esz off the array
// element tinfo (fu.sub). int/float elements
// only; str/slice/struct/tagged elements are the
// N_LET multi-word gap — loud rule-7 (cstage
// cg_structlit_fill array arm twin).
let eti: *syntax.tinfo = tichase(fu.sub);
let isstructel: bool = false;
if (eti != nil) {
if (eti.kind == syntax.tykind.TY_STRUCT) { isstructel = true; };
};
if (syntax.typeisstr(fu.sub) || syntax.typeisslice(fu.sub)
|| isstructel || syntax.typeistagged(fu.sub)) {
let e1: str = "ww: struct-literal array field '";
os.write(2, e1.ptr, e1.len: u64);
os.write(2, fname.ptr, fname.len: u64);
let e2: str = "' has a str/slice/struct/tagged element — multi-word element store out of #249 scope (N_LET array-init gap)\n";
os.write(2, e2.ptr, e2.len: u64);
os.exit(1);
};
let esz: i32 = 8;
if (eti != nil) { esz = eti.size: i32; };
let mop: str = storeopsz(esz);
let isfloatel: bool = syntax.typeisfloat(fu.sub);
let fmov: str = "MOVSD";
if (syntax.typeisf32(fu.sub)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *syntax.node = fieldnode.lhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == syntax.nkind.N_FIELD) {
if (syntax.streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
cgexpr(c, e);
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
let eoff: i32 = disp + foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
e = e.next;
};
};
if (repeat) {
let total: i32 = fu.alen: i32;
for (idx < total) {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
let eoff: i32 = disp + foff + idx * esz;
if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
};
if (mode == 0) {
emitoff(eoff: i64);
emitline("(BP)\n");
} else {
emitdispreg(eoff: i64, basereg);
emitline("\n");
};
idx += 1;
};
};
} else if (fu != nil
&& (fu.kind == syntax.tykind.TY_STRUCT
|| fu.kind == syntax.tykind.TY_ARRAY
|| fu.kind == syntax.tykind.TY_TUPLE)) {
// #49: aggregate (struct/array/tuple) field from
// an ADDRESSABLE source expr — `outer{.., r = r}`.
// Funnel: source addr via aggargsrcaddr (SI),
// field addr via LEAQ/ADDQ (BX, AFTER the source
// walk clobbers BX/AX), then aggcopy. Width = the
// resolved field tinfo size. Non-addressable
// sources die loud (rule 7). cstage #49 arm twin.
if (!aggargsrcaddr(c, fieldnode.lhs, "SI")) {
let m49g: str = "structlit fill: aggregate field '";
os.write(2, m49g.ptr, m49g.len: u64);
os.write(2, fname.ptr, fname.len: u64);
let m49h: str = "' from a non-addressable source unwired (task #49/rule-7)\n";
os.write(2, m49h.ptr, m49h.len: u64);
os.exit(1);
};
if (mode == 0) {
emitline("\tLEAQ\t");
emitoff((disp + foff): i64);
emitline("(BP), BX\n");
} else {
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
} else { if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
}; };
if (disp + foff != 0) {
emitline("\tADDQ\t$");
emitint((disp + foff): i64);
emitline(", BX\n");
};
};
aggcopy(c, fsz);
} else {
cgexpr(c, fieldnode.lhs);
// For non-BP modes, cgexpr just clobbered BX;
// reload it before the store.
if (mode == 1) {
emitline("\tMOVQ\t");
emitoff(srcoff: i64);
emitline("(BP), BX\n");
};
if (mode == 2) {
emitline("\tLEAQ\t");
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
if (syntax.typeisfloat(ftraw)) {
let mov: str = "MOVSD";
if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\tX0, ");
if (mode == 0) {
emitoff((disp + foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + foff): i64, basereg);
emitline("\n");
};
} else {
// Full field-width store {1->MOVB, 2->MOVW,
// 4->MOVL, else MOVQ}; the #15 sub-8 guard.
let op: str = storeopsz(fsz);
emitline("\t");
emitline(op);
emitline("\tAX, ");
if (mode == 0) {
emitoff((disp + foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + foff): i64, basereg);
emitline("\n");
};
};
};
};
};
};
fieldnode = fieldnode.next;
};
};