cstage+selfhost+test: extend structlit-fill helper to N_ASSIGN N_DOT lhs (4 flavors)

Sister fix to #17. The BP-rel helper from #17 covered N_LET /
N_ASSIGN N_IDENT-lhs / N_RETURN; the four N_ASSIGN N_DOT-lhs
structlit walks still went through the inline `cgexpr(field.lhs);
store-AX-sized` shape and silently dropped trailing bytes when a
struct-typed field's value was itself an N_STRUCTLIT. Affected dot
flavors: single-dot via_ptr / global / BP-rel and the chained-dot
walker (depth >= 2, all three root flavors).

Extend `cg_structlit_fill_bp` / `cgstructlitfillbp` into
`cg_structlit_fill` / `cgstructlitfill` taking a destination mode
(DST_BP / DST_PTR_LOCAL / DST_GLOBAL = 0/1/2), srcoff (PTR_LOCAL),
srcname (GLOBAL), and disp accumulator. `disp` grows by foff on
descent; srcoff/srcname stay constant across the call tree. The
pre-#17 wrappers are preserved byte-identically by delegating with
mode=DST_BP — 995_self_rebuild byte-identity holds for the no-
nested-STRUCTLIT case that selfhost source actually uses.

The non-BP modes reload BX before the ELLIPSIS zero-fill loop AND
before every field store (tagged, scalar, and the cgexpr leaf).
This is correctness-by-construction — cgexpr clobbers BX between
fields, and the redundant reload only fires on shapes that didn't
compile before. The four dot-flavor sites in each stage now compute
their dst mode + disp and call the shared helper (reducing each
from ~80-130 inline lines to ~5-12 lines of dispatch).

Stage signature asymmetry: cstage threads Local** for cgexpr; ww-
stage takes explicit totsize because #15 (split totsize into
naturalsize + slotsize) is still pending and the dot sites need
structnaturalsize while the BP-rel sites need si.totsize. Both
asymmetries are documented in the helper docstrings.

704 covers 8 rows (24 checks: 8 cstage exits, 8 wwstage exits, 8
cstage-vs-wwstage .s byte-identity diffs): 6 dst-flavors (single-
dot local/ptr/global, chained-dot local/ptr/global) plus single-
local 3-deep and single-ptr 3-deep to pin disp threading through
the helper's recursion and through DST_PTR_LOCAL BX reloads.

The nested struct-typed CALL rhs in field-walks has the same shape
as the STRUCTLIT bug fixed here but the helper only handles
STRUCTLIT — tracked as task #20.
This commit is contained in:
2026-05-15 18:45:21 +09:00
parent 9d03e02881
commit 99a68a6a57
7 changed files with 1070 additions and 1450 deletions

View File

@@ -8656,59 +8656,113 @@ export fn dotchainresolve(c: *cgen, n: *node,
return false;
};
// cgstructlitfillbp — fill a struct-typed slot from an N_STRUCTLIT
// value into BP-relative memory at `bpoff`. Mirror of cstage cgen.c's
// cg_structlit_fill_bp. Used by cglet, cgreturn N_STRUCTLIT, and
// cgassign N_IDENT-lhs N_STRUCTLIT sites where the dst is BP-
// relative.
// cgstructlitfill — fill a struct-typed slot from an N_STRUCTLIT
// value into one of three destination flavors. Mirror of cstage
// cgen.c's cg_structlit_fill. Used by cglet, cgreturn N_STRUCTLIT,
// cgassign N_IDENT-lhs N_STRUCTLIT (BP-rel) AND cgassign N_DOT-lhs
// N_STRUCTLIT (BP-rel / via *struct local / via struct global) at
// single-dot and chained-dot sites.
//
// The inline field-walk previously did `cgexpr(field.lhs); store AX
// sized`. For struct-typed fields whose value is itself a nested
// N_STRUCTLIT, cgexpr has no whole-struct-in-register convention —
// it lands AX = first qword and the trailing bytes silently stay
// zero. Selfhost source used write-by-field as a workaround; this
// helper recurses cleanly so the workaround is no longer required.
// Destination modes:
// 0 = DST_BP — base = BP, no reload. Stores at disp+i(BP).
// srcoff/srcname unused.
// 1 = DST_PTR_LOCAL — base = BX, reloaded from srcoff(BP) before
// the ELLIPSIS zero-fill loop and before EVERY
// field store (cgexpr clobbers BX between
// fields). Stores at disp+i(BX). srcname
// unused.
// 2 = DST_GLOBAL — base = BX, reloaded via `LEAQ srcname(SB),
// BX` with the same cadence as DST_PTR_LOCAL.
// srcoff unused.
//
// Sister branches (cgassign single-dot / via_ptr / global structlit
// walks) keep their inline field-walk in v1 since their addressing
// has additional BX-reload concerns; nested-STRUCTLIT silent-zero
// still drops on those paths — separate follow-up task.
// Param semantics (locked in here so the recursion contract is
// clear):
// - `disp` is the per-recursion accumulator — grows by `fi.foff`
// as we descend into a nested struct-typed structlit field.
// - `srcoff` (DST_PTR_LOCAL) and `srcname` (DST_GLOBAL) are
// *constant* across the whole call tree — they identify the
// root dst, which doesn't change with depth.
// - `totsize` is also constant; pass the natural size for dot
// sites (structnaturalsize) and si.totsize for BP-rel sites,
// matching each site's pre-#18 zero-fill bound.
//
// Output is byte-identical to the prior inline code for any input
// that has no nested-STRUCTLIT field (the only shape selfhost source
// actually compiles today), preserving 995_self_rebuild byte-
// identity.
// Why a helper? The inline field-walk previously did
// `cgexpr(field.lhs); store AX sized`. For struct-typed fields whose
// value is itself a nested N_STRUCTLIT, cgexpr has no whole-struct-
// in-register convention — it lands AX = first qword and the
// trailing bytes silently stay zero. #17 fixed the BP-rel sites;
// #18 extends the same recursion to the four cgassign N_DOT-lhs
// structlit walks (single-dot via_ptr/global/local + chained
// depth>=2).
//
// The non-BP modes emit a redundant BX reload at the start of each
// recursive nested zero-fill / each recursive scalar store — this is
// correctness-by-construction (BX is always freshly loaded right
// before use), and the redundancy only fires on the nested-STRUCTLIT
// shapes that didn't compile before. Byte-identity for the no-
// nested case (the only shape selfhost source uses today) is
// preserved because the existing inline code's reload-before-each-
// store pattern matches the helper's per-store reload exactly.
//
// Graduation note (task #13): the scalar store currently uses the
// explicit {1→MOVB, 4→MOVL, else MOVQ} dispatch to match cstage
// byte-identically — cstage hasn't yet learned MOVW for fsz==2. Once
// #13 aligns both stages, the dispatch can switch to fieldstoreop
// which already returns MOVW where appropriate.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
mode: i32, srcoff: i32, srcname: str,
disp: i32, totsize: i32) void = {
if (si == nil) { return; };
let basereg: str = "BP";
if (mode != 0) { basereg = "BX"; };
if (lit.op == tkind.TK_ELLIPSIS) {
// `..., ...` autofill — zero the entire slot first so
// unmentioned fields read as 0. Uses si.totsize (slot-padded)
// to match the inline pre-#17 pattern.
let total: i32 = si.totsize;
// unmentioned fields read as 0. Sized stores: 8/4/1. For
// non-BP modes, reload BX once before the loop (cgexpr-free
// region between iterations, so one reload is enough).
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");
};
let zi: i32 = 0;
for (zi + 8 <= total) {
for (zi + 8 <= totsize) {
emitline("\tMOVQ\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 8;
};
for (zi + 4 <= total) {
for (zi + 4 <= totsize) {
emitline("\tMOVL\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 4;
};
for (zi < total) {
for (zi < totsize) {
emitline("\tMOVB\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 1;
};
};
@@ -8723,23 +8777,29 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
// Tagged-union field: delegate to the shared
// widening writer (handles str/scalar/struct
// literal/ident payload + tagged-subset tag
// remap). Mirrors cstage cg_structlit_fill_bp
// and the pre-existing wwstage cgreturn
// structlit dispatch; cglet's pre-#17 inline
// lacked this branch but selfhost never
// tripped it (no tagged-fields-in-let-
// structlit in tree).
// remap). For non-BP modes, reload BX first so
// the widener sees a valid base reg.
if (istaggedtype(c, fi.tnode)) {
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");
};
cgwidentaggedstore(c, fi.tnode,
fieldnode.lhs, "BP",
bpoff + fi.foff, fi.fsz);
fieldnode.lhs, basereg,
disp + fi.foff, fi.fsz);
fi = nil;
} else {
// Nested struct-typed structlit value: look up
// the inner struct's metadata and recurse at the
// field's offset. Pre-#17 the cgexpr-then-store
// below would land AX = first qword and the rest
// silently stayed zero.
// field's offset. Pre-#17/#18 the cgexpr-then-
// store below would land AX = first qword and
// the rest silently stayed zero.
let nested: bool = false;
if (fieldnode.lhs != nil) {
if (fieldnode.lhs.kind == nkind.N_STRUCTLIT) {
@@ -8748,9 +8808,17 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (primsize(fi.tnode.str) == 0) {
let isi: *structinfo = structlookup(c, fi.tnode.str);
if (isi != nil) {
cgstructlitfillbp(c, isi,
// Nested fill: pick the size
// discipline matching the outer
// site — dot sites pass natural
// size, BP-rel sites pass
// totsize. Mirror it.
let inner_tot: i32 = isi.totsize;
if (mode != 0) { inner_tot = structnaturalsize(isi); };
cgstructlitfill(c, isi,
fieldnode.lhs,
bpoff + fi.foff);
mode, srcoff, srcname,
disp + fi.foff, inner_tot);
nested = true;
};
};
@@ -8762,24 +8830,39 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fi = nil;
} 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 (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((bpoff + fi.foff): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + fi.foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + fi.foff): i64, basereg);
emitline("\n");
};
fi = nil;
} else {
// Explicit {1→MOVB, 4→MOVL, else MOVQ}
// dispatch (not fieldstoreop) to match
// cstage cg_structlit_fill_bp byte-
// identically. wwstage's fieldstoreop
// would return MOVW for fsz==2 which
// cstage doesn't emit — tracked as task
// #13. Until that lands, the helper
// emits the cstage shape.
// cstage byte-identically. wwstage's
// fieldstoreop would return MOVW for
// fsz==2 which cstage doesn't emit —
// tracked as task #13.
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
@@ -8787,8 +8870,13 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((bpoff + fi.foff): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + fi.foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + fi.foff): i64, basereg);
emitline("\n");
};
fi = nil;
};
};
@@ -8802,6 +8890,14 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
};
};
// Thin wrapper preserving the BP-rel call shape used by cglet,
// cgreturn, and cgassign N_IDENT-lhs N_STRUCTLIT. Byte-identical to
// the pre-#18 cgstructlitfillbp.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (si == nil) { return; };
cgstructlitfill(c, si, lit, 0, 0, "", bpoff, si.totsize);
};
// MODULE: wcc
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
//
@@ -12643,6 +12739,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=1 (DST_PTR_LOCAL) reloads BX
// from lc.off(BP) before zero-fill and before every
// field store.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
@@ -12652,75 +12753,8 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff, ssz);
return;
};
};
@@ -12936,6 +12970,10 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=0 (DST_BP) — direct BP-rel,
// no BX reload.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
@@ -12945,66 +12983,8 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((lc.off + fi.foff + ifi.foff): i64);
emitline("(BP)\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((lc.off + fi.foff + ifi.foff): i64);
emitline("(BP)\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff, ssz);
return;
};
};
@@ -13243,87 +13223,25 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == nkind.N_TNAME
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
return;
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=2 (DST_GLOBAL) reloads BX
// via LEAQ bn(SB) before zero-fill and before every
// field store.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == nkind.N_TNAME
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff, ssz);
return;
};
};
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_IDENT
@@ -13702,6 +13620,14 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode picks the dst flavor:
// ptrroot → mode=1 (DST_PTR_LOCAL), reload BX from
// rootoff(BP).
// isglobal → mode=2 (DST_GLOBAL), reload BX via
// LEAQ rootname(SB).
// else → mode=0 (DST_BP), direct BP-rel, no reload.
if (n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& leaffi.tnode != nil
@@ -13712,131 +13638,19 @@ fn cgassign(c: *cgen, n: *node) void = {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
let zi: i32 = 0;
for (zi + 8 <= lsz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= lsz) {
emitline("\tMOVL\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < lsz) {
emitline("\tMOVB\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
} else {
let zi: i32 = 0;
for (zi + 8 <= lsz) {
emitline("\tMOVQ\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= lsz) {
emitline("\tMOVL\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < lsz) {
emitline("\tMOVB\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 1;
};
};
let dmode: i32 = 0;
let ddisp: i32 = rootoff + totaloff;
if (ptrroot) {
dmode = 1;
ddisp = totaloff;
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let fi: *fieldinfo = lsi.fields;
for (fi != nil) {
if (streq(fi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) {
mov = "MOVSS";
};
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((totaloff + fi.foff): i64, "BX");
emitline("\n");
} else {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((rootoff + totaloff + fi.foff): i64);
emitline("(BP)\n");
};
fi = nil;
} else {
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
if (fsz == 4) { op = "MOVL"; };
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((totaloff + fi.foff): i64, "BX");
emitline("\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((rootoff + totaloff + fi.foff): i64);
emitline("(BP)\n");
};
fi = nil;
};
} else {
fi = fi.finext;
};
};
};
fldn = fldn.next;
if (isglobal) {
dmode = 2;
ddisp = totaloff;
};
cgstructlitfill(c, lsi, n.rhs,
dmode, rootoff, rootname,
ddisp, lsz);
return;
};
};

View File

@@ -3838,6 +3838,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=1 (DST_PTR_LOCAL) reloads BX
// from lc.off(BP) before zero-fill and before every
// field store.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
@@ -3847,75 +3852,8 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff, ssz);
return;
};
};
@@ -4131,6 +4069,10 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=0 (DST_BP) — direct BP-rel,
// no BX reload.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
@@ -4140,66 +4082,8 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((lc.off + fi.foff + ifi.foff): i64);
emitline("(BP)\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((lc.off + fi.foff + ifi.foff): i64);
emitline("(BP)\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff, ssz);
return;
};
};
@@ -4438,87 +4322,25 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == nkind.N_TNAME
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
return;
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=2 (DST_GLOBAL) reloads BX
// via LEAQ bn(SB) before zero-fill and before every
// field store.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == nkind.N_TNAME
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff, ssz);
return;
};
};
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_IDENT
@@ -4897,6 +4719,14 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode picks the dst flavor:
// ptrroot → mode=1 (DST_PTR_LOCAL), reload BX from
// rootoff(BP).
// isglobal → mode=2 (DST_GLOBAL), reload BX via
// LEAQ rootname(SB).
// else → mode=0 (DST_BP), direct BP-rel, no reload.
if (n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& leaffi.tnode != nil
@@ -4907,131 +4737,19 @@ fn cgassign(c: *cgen, n: *node) void = {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
let zi: i32 = 0;
for (zi + 8 <= lsz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= lsz) {
emitline("\tMOVL\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < lsz) {
emitline("\tMOVB\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
} else {
let zi: i32 = 0;
for (zi + 8 <= lsz) {
emitline("\tMOVQ\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= lsz) {
emitline("\tMOVL\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < lsz) {
emitline("\tMOVB\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 1;
};
};
let dmode: i32 = 0;
let ddisp: i32 = rootoff + totaloff;
if (ptrroot) {
dmode = 1;
ddisp = totaloff;
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let fi: *fieldinfo = lsi.fields;
for (fi != nil) {
if (streq(fi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) {
mov = "MOVSS";
};
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((totaloff + fi.foff): i64, "BX");
emitline("\n");
} else {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((rootoff + totaloff + fi.foff): i64);
emitline("(BP)\n");
};
fi = nil;
} else {
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
if (fsz == 4) { op = "MOVL"; };
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((totaloff + fi.foff): i64, "BX");
emitline("\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((rootoff + totaloff + fi.foff): i64);
emitline("(BP)\n");
};
fi = nil;
};
} else {
fi = fi.finext;
};
};
};
fldn = fldn.next;
if (isglobal) {
dmode = 2;
ddisp = totaloff;
};
cgstructlitfill(c, lsi, n.rhs,
dmode, rootoff, rootname,
ddisp, lsz);
return;
};
};

View File

@@ -2784,59 +2784,113 @@ export fn dotchainresolve(c: *cgen, n: *node,
return false;
};
// cgstructlitfillbp — fill a struct-typed slot from an N_STRUCTLIT
// value into BP-relative memory at `bpoff`. Mirror of cstage cgen.c's
// cg_structlit_fill_bp. Used by cglet, cgreturn N_STRUCTLIT, and
// cgassign N_IDENT-lhs N_STRUCTLIT sites where the dst is BP-
// relative.
// cgstructlitfill — fill a struct-typed slot from an N_STRUCTLIT
// value into one of three destination flavors. Mirror of cstage
// cgen.c's cg_structlit_fill. Used by cglet, cgreturn N_STRUCTLIT,
// cgassign N_IDENT-lhs N_STRUCTLIT (BP-rel) AND cgassign N_DOT-lhs
// N_STRUCTLIT (BP-rel / via *struct local / via struct global) at
// single-dot and chained-dot sites.
//
// The inline field-walk previously did `cgexpr(field.lhs); store AX
// sized`. For struct-typed fields whose value is itself a nested
// N_STRUCTLIT, cgexpr has no whole-struct-in-register convention —
// it lands AX = first qword and the trailing bytes silently stay
// zero. Selfhost source used write-by-field as a workaround; this
// helper recurses cleanly so the workaround is no longer required.
// Destination modes:
// 0 = DST_BP — base = BP, no reload. Stores at disp+i(BP).
// srcoff/srcname unused.
// 1 = DST_PTR_LOCAL — base = BX, reloaded from srcoff(BP) before
// the ELLIPSIS zero-fill loop and before EVERY
// field store (cgexpr clobbers BX between
// fields). Stores at disp+i(BX). srcname
// unused.
// 2 = DST_GLOBAL — base = BX, reloaded via `LEAQ srcname(SB),
// BX` with the same cadence as DST_PTR_LOCAL.
// srcoff unused.
//
// Sister branches (cgassign single-dot / via_ptr / global structlit
// walks) keep their inline field-walk in v1 since their addressing
// has additional BX-reload concerns; nested-STRUCTLIT silent-zero
// still drops on those paths — separate follow-up task.
// Param semantics (locked in here so the recursion contract is
// clear):
// - `disp` is the per-recursion accumulator — grows by `fi.foff`
// as we descend into a nested struct-typed structlit field.
// - `srcoff` (DST_PTR_LOCAL) and `srcname` (DST_GLOBAL) are
// *constant* across the whole call tree — they identify the
// root dst, which doesn't change with depth.
// - `totsize` is also constant; pass the natural size for dot
// sites (structnaturalsize) and si.totsize for BP-rel sites,
// matching each site's pre-#18 zero-fill bound.
//
// Output is byte-identical to the prior inline code for any input
// that has no nested-STRUCTLIT field (the only shape selfhost source
// actually compiles today), preserving 995_self_rebuild byte-
// identity.
// Why a helper? The inline field-walk previously did
// `cgexpr(field.lhs); store AX sized`. For struct-typed fields whose
// value is itself a nested N_STRUCTLIT, cgexpr has no whole-struct-
// in-register convention — it lands AX = first qword and the
// trailing bytes silently stay zero. #17 fixed the BP-rel sites;
// #18 extends the same recursion to the four cgassign N_DOT-lhs
// structlit walks (single-dot via_ptr/global/local + chained
// depth>=2).
//
// The non-BP modes emit a redundant BX reload at the start of each
// recursive nested zero-fill / each recursive scalar store — this is
// correctness-by-construction (BX is always freshly loaded right
// before use), and the redundancy only fires on the nested-STRUCTLIT
// shapes that didn't compile before. Byte-identity for the no-
// nested case (the only shape selfhost source uses today) is
// preserved because the existing inline code's reload-before-each-
// store pattern matches the helper's per-store reload exactly.
//
// Graduation note (task #13): the scalar store currently uses the
// explicit {1→MOVB, 4→MOVL, else MOVQ} dispatch to match cstage
// byte-identically — cstage hasn't yet learned MOVW for fsz==2. Once
// #13 aligns both stages, the dispatch can switch to fieldstoreop
// which already returns MOVW where appropriate.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
mode: i32, srcoff: i32, srcname: str,
disp: i32, totsize: i32) void = {
if (si == nil) { return; };
let basereg: str = "BP";
if (mode != 0) { basereg = "BX"; };
if (lit.op == tkind.TK_ELLIPSIS) {
// `..., ...` autofill — zero the entire slot first so
// unmentioned fields read as 0. Uses si.totsize (slot-padded)
// to match the inline pre-#17 pattern.
let total: i32 = si.totsize;
// unmentioned fields read as 0. Sized stores: 8/4/1. For
// non-BP modes, reload BX once before the loop (cgexpr-free
// region between iterations, so one reload is enough).
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");
};
let zi: i32 = 0;
for (zi + 8 <= total) {
for (zi + 8 <= totsize) {
emitline("\tMOVQ\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 8;
};
for (zi + 4 <= total) {
for (zi + 4 <= totsize) {
emitline("\tMOVL\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 4;
};
for (zi < total) {
for (zi < totsize) {
emitline("\tMOVB\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 1;
};
};
@@ -2851,23 +2905,29 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
// Tagged-union field: delegate to the shared
// widening writer (handles str/scalar/struct
// literal/ident payload + tagged-subset tag
// remap). Mirrors cstage cg_structlit_fill_bp
// and the pre-existing wwstage cgreturn
// structlit dispatch; cglet's pre-#17 inline
// lacked this branch but selfhost never
// tripped it (no tagged-fields-in-let-
// structlit in tree).
// remap). For non-BP modes, reload BX first so
// the widener sees a valid base reg.
if (istaggedtype(c, fi.tnode)) {
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");
};
cgwidentaggedstore(c, fi.tnode,
fieldnode.lhs, "BP",
bpoff + fi.foff, fi.fsz);
fieldnode.lhs, basereg,
disp + fi.foff, fi.fsz);
fi = nil;
} else {
// Nested struct-typed structlit value: look up
// the inner struct's metadata and recurse at the
// field's offset. Pre-#17 the cgexpr-then-store
// below would land AX = first qword and the rest
// silently stayed zero.
// field's offset. Pre-#17/#18 the cgexpr-then-
// store below would land AX = first qword and
// the rest silently stayed zero.
let nested: bool = false;
if (fieldnode.lhs != nil) {
if (fieldnode.lhs.kind == nkind.N_STRUCTLIT) {
@@ -2876,9 +2936,17 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (primsize(fi.tnode.str) == 0) {
let isi: *structinfo = structlookup(c, fi.tnode.str);
if (isi != nil) {
cgstructlitfillbp(c, isi,
// Nested fill: pick the size
// discipline matching the outer
// site — dot sites pass natural
// size, BP-rel sites pass
// totsize. Mirror it.
let inner_tot: i32 = isi.totsize;
if (mode != 0) { inner_tot = structnaturalsize(isi); };
cgstructlitfill(c, isi,
fieldnode.lhs,
bpoff + fi.foff);
mode, srcoff, srcname,
disp + fi.foff, inner_tot);
nested = true;
};
};
@@ -2890,24 +2958,39 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fi = nil;
} 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 (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((bpoff + fi.foff): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + fi.foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + fi.foff): i64, basereg);
emitline("\n");
};
fi = nil;
} else {
// Explicit {1→MOVB, 4→MOVL, else MOVQ}
// dispatch (not fieldstoreop) to match
// cstage cg_structlit_fill_bp byte-
// identically. wwstage's fieldstoreop
// would return MOVW for fsz==2 which
// cstage doesn't emit — tracked as task
// #13. Until that lands, the helper
// emits the cstage shape.
// cstage byte-identically. wwstage's
// fieldstoreop would return MOVW for
// fsz==2 which cstage doesn't emit —
// tracked as task #13.
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
@@ -2915,8 +2998,13 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((bpoff + fi.foff): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + fi.foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + fi.foff): i64, basereg);
emitline("\n");
};
fi = nil;
};
};
@@ -2929,3 +3017,11 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fieldnode = fieldnode.next;
};
};
// Thin wrapper preserving the BP-rel call shape used by cglet,
// cgreturn, and cgassign N_IDENT-lhs N_STRUCTLIT. Byte-identical to
// the pre-#18 cgstructlitfillbp.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (si == nil) { return; };
cgstructlitfill(c, si, lit, 0, 0, "", bpoff, si.totsize);
};

View File

@@ -8656,59 +8656,113 @@ export fn dotchainresolve(c: *cgen, n: *node,
return false;
};
// cgstructlitfillbp — fill a struct-typed slot from an N_STRUCTLIT
// value into BP-relative memory at `bpoff`. Mirror of cstage cgen.c's
// cg_structlit_fill_bp. Used by cglet, cgreturn N_STRUCTLIT, and
// cgassign N_IDENT-lhs N_STRUCTLIT sites where the dst is BP-
// relative.
// cgstructlitfill — fill a struct-typed slot from an N_STRUCTLIT
// value into one of three destination flavors. Mirror of cstage
// cgen.c's cg_structlit_fill. Used by cglet, cgreturn N_STRUCTLIT,
// cgassign N_IDENT-lhs N_STRUCTLIT (BP-rel) AND cgassign N_DOT-lhs
// N_STRUCTLIT (BP-rel / via *struct local / via struct global) at
// single-dot and chained-dot sites.
//
// The inline field-walk previously did `cgexpr(field.lhs); store AX
// sized`. For struct-typed fields whose value is itself a nested
// N_STRUCTLIT, cgexpr has no whole-struct-in-register convention —
// it lands AX = first qword and the trailing bytes silently stay
// zero. Selfhost source used write-by-field as a workaround; this
// helper recurses cleanly so the workaround is no longer required.
// Destination modes:
// 0 = DST_BP — base = BP, no reload. Stores at disp+i(BP).
// srcoff/srcname unused.
// 1 = DST_PTR_LOCAL — base = BX, reloaded from srcoff(BP) before
// the ELLIPSIS zero-fill loop and before EVERY
// field store (cgexpr clobbers BX between
// fields). Stores at disp+i(BX). srcname
// unused.
// 2 = DST_GLOBAL — base = BX, reloaded via `LEAQ srcname(SB),
// BX` with the same cadence as DST_PTR_LOCAL.
// srcoff unused.
//
// Sister branches (cgassign single-dot / via_ptr / global structlit
// walks) keep their inline field-walk in v1 since their addressing
// has additional BX-reload concerns; nested-STRUCTLIT silent-zero
// still drops on those paths — separate follow-up task.
// Param semantics (locked in here so the recursion contract is
// clear):
// - `disp` is the per-recursion accumulator — grows by `fi.foff`
// as we descend into a nested struct-typed structlit field.
// - `srcoff` (DST_PTR_LOCAL) and `srcname` (DST_GLOBAL) are
// *constant* across the whole call tree — they identify the
// root dst, which doesn't change with depth.
// - `totsize` is also constant; pass the natural size for dot
// sites (structnaturalsize) and si.totsize for BP-rel sites,
// matching each site's pre-#18 zero-fill bound.
//
// Output is byte-identical to the prior inline code for any input
// that has no nested-STRUCTLIT field (the only shape selfhost source
// actually compiles today), preserving 995_self_rebuild byte-
// identity.
// Why a helper? The inline field-walk previously did
// `cgexpr(field.lhs); store AX sized`. For struct-typed fields whose
// value is itself a nested N_STRUCTLIT, cgexpr has no whole-struct-
// in-register convention — it lands AX = first qword and the
// trailing bytes silently stay zero. #17 fixed the BP-rel sites;
// #18 extends the same recursion to the four cgassign N_DOT-lhs
// structlit walks (single-dot via_ptr/global/local + chained
// depth>=2).
//
// The non-BP modes emit a redundant BX reload at the start of each
// recursive nested zero-fill / each recursive scalar store — this is
// correctness-by-construction (BX is always freshly loaded right
// before use), and the redundancy only fires on the nested-STRUCTLIT
// shapes that didn't compile before. Byte-identity for the no-
// nested case (the only shape selfhost source uses today) is
// preserved because the existing inline code's reload-before-each-
// store pattern matches the helper's per-store reload exactly.
//
// Graduation note (task #13): the scalar store currently uses the
// explicit {1→MOVB, 4→MOVL, else MOVQ} dispatch to match cstage
// byte-identically — cstage hasn't yet learned MOVW for fsz==2. Once
// #13 aligns both stages, the dispatch can switch to fieldstoreop
// which already returns MOVW where appropriate.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
mode: i32, srcoff: i32, srcname: str,
disp: i32, totsize: i32) void = {
if (si == nil) { return; };
let basereg: str = "BP";
if (mode != 0) { basereg = "BX"; };
if (lit.op == tkind.TK_ELLIPSIS) {
// `..., ...` autofill — zero the entire slot first so
// unmentioned fields read as 0. Uses si.totsize (slot-padded)
// to match the inline pre-#17 pattern.
let total: i32 = si.totsize;
// unmentioned fields read as 0. Sized stores: 8/4/1. For
// non-BP modes, reload BX once before the loop (cgexpr-free
// region between iterations, so one reload is enough).
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");
};
let zi: i32 = 0;
for (zi + 8 <= total) {
for (zi + 8 <= totsize) {
emitline("\tMOVQ\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 8;
};
for (zi + 4 <= total) {
for (zi + 4 <= totsize) {
emitline("\tMOVL\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 4;
};
for (zi < total) {
for (zi < totsize) {
emitline("\tMOVB\tAX, ");
emitoff((bpoff + zi): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + zi): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + zi): i64, basereg);
emitline("\n");
};
zi += 1;
};
};
@@ -8723,23 +8777,29 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
// Tagged-union field: delegate to the shared
// widening writer (handles str/scalar/struct
// literal/ident payload + tagged-subset tag
// remap). Mirrors cstage cg_structlit_fill_bp
// and the pre-existing wwstage cgreturn
// structlit dispatch; cglet's pre-#17 inline
// lacked this branch but selfhost never
// tripped it (no tagged-fields-in-let-
// structlit in tree).
// remap). For non-BP modes, reload BX first so
// the widener sees a valid base reg.
if (istaggedtype(c, fi.tnode)) {
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");
};
cgwidentaggedstore(c, fi.tnode,
fieldnode.lhs, "BP",
bpoff + fi.foff, fi.fsz);
fieldnode.lhs, basereg,
disp + fi.foff, fi.fsz);
fi = nil;
} else {
// Nested struct-typed structlit value: look up
// the inner struct's metadata and recurse at the
// field's offset. Pre-#17 the cgexpr-then-store
// below would land AX = first qword and the rest
// silently stayed zero.
// field's offset. Pre-#17/#18 the cgexpr-then-
// store below would land AX = first qword and
// the rest silently stayed zero.
let nested: bool = false;
if (fieldnode.lhs != nil) {
if (fieldnode.lhs.kind == nkind.N_STRUCTLIT) {
@@ -8748,9 +8808,17 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (primsize(fi.tnode.str) == 0) {
let isi: *structinfo = structlookup(c, fi.tnode.str);
if (isi != nil) {
cgstructlitfillbp(c, isi,
// Nested fill: pick the size
// discipline matching the outer
// site — dot sites pass natural
// size, BP-rel sites pass
// totsize. Mirror it.
let inner_tot: i32 = isi.totsize;
if (mode != 0) { inner_tot = structnaturalsize(isi); };
cgstructlitfill(c, isi,
fieldnode.lhs,
bpoff + fi.foff);
mode, srcoff, srcname,
disp + fi.foff, inner_tot);
nested = true;
};
};
@@ -8762,24 +8830,39 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
fi = nil;
} 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 (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((bpoff + fi.foff): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + fi.foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + fi.foff): i64, basereg);
emitline("\n");
};
fi = nil;
} else {
// Explicit {1→MOVB, 4→MOVL, else MOVQ}
// dispatch (not fieldstoreop) to match
// cstage cg_structlit_fill_bp byte-
// identically. wwstage's fieldstoreop
// would return MOVW for fsz==2 which
// cstage doesn't emit — tracked as task
// #13. Until that lands, the helper
// emits the cstage shape.
// cstage byte-identically. wwstage's
// fieldstoreop would return MOVW for
// fsz==2 which cstage doesn't emit —
// tracked as task #13.
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
@@ -8787,8 +8870,13 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((bpoff + fi.foff): i64);
emitline("(BP)\n");
if (mode == 0) {
emitoff((disp + fi.foff): i64);
emitline("(BP)\n");
} else {
emitdispreg((disp + fi.foff): i64, basereg);
emitline("\n");
};
fi = nil;
};
};
@@ -8802,6 +8890,14 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
};
};
// Thin wrapper preserving the BP-rel call shape used by cglet,
// cgreturn, and cgassign N_IDENT-lhs N_STRUCTLIT. Byte-identical to
// the pre-#18 cgstructlitfillbp.
fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
if (si == nil) { return; };
cgstructlitfill(c, si, lit, 0, 0, "", bpoff, si.totsize);
};
// MODULE: wcc
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
//
@@ -12643,6 +12739,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=1 (DST_PTR_LOCAL) reloads BX
// from lc.off(BP) before zero-fill and before every
// field store.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
@@ -12652,75 +12753,8 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
fi.foff, ssz);
return;
};
};
@@ -12936,6 +12970,10 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=0 (DST_BP) — direct BP-rel,
// no BX reload.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
@@ -12945,66 +12983,8 @@ fn cgassign(c: *cgen, n: *node) void = {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitoff((lc.off + fi.foff + zi): i64);
emitline("(BP)\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((lc.off + fi.foff + ifi.foff): i64);
emitline("(BP)\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((lc.off + fi.foff + ifi.foff): i64);
emitline("(BP)\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
lc.off + fi.foff, ssz);
return;
};
};
@@ -13243,87 +13223,25 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == nkind.N_TNAME
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
let zi: i32 = 0;
for (zi + 8 <= ssz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= ssz) {
emitline("\tMOVL\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < ssz) {
emitline("\tMOVB\tAX, ");
emitdispreg((fi.foff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let ifi: *fieldinfo = ssi.fields;
for (ifi != nil) {
if (streq(ifi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, ifi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, ifi.tnode)) {
mov = "MOVSS";
};
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
} else {
let ifsz: i32 = ifi.fsz;
let op: str = "MOVQ";
if (ifsz == 1) { op = "MOVB"; };
if (ifsz == 4) { op = "MOVL"; };
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((fi.foff + ifi.foff): i64, "BX");
emitline("\n");
ifi = nil;
};
} else {
ifi = ifi.finext;
};
};
};
fldn = fldn.next;
};
return;
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode=2 (DST_GLOBAL) reloads BX
// via LEAQ bn(SB) before zero-fill and before every
// field store.
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& fi.tnode != nil
&& fi.tnode.kind == nkind.N_TNAME
&& primsize(fi.tnode.str) == 0) {
let ssi: *structinfo = structlookup(c, fi.tnode.str);
if (ssi != nil) {
let ssz: i32 = structnaturalsize(ssi);
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
fi.foff, ssz);
return;
};
};
if (n.op == tkind.TK_ASSIGN
&& n.rhs != nil
&& n.rhs.kind == nkind.N_IDENT
@@ -13702,6 +13620,14 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #18: delegate to cgstructlitfill so a nested struct-
// typed structlit value recurses instead of dropping
// its trailing bytes. mode picks the dst flavor:
// ptrroot → mode=1 (DST_PTR_LOCAL), reload BX from
// rootoff(BP).
// isglobal → mode=2 (DST_GLOBAL), reload BX via
// LEAQ rootname(SB).
// else → mode=0 (DST_BP), direct BP-rel, no reload.
if (n.rhs != nil
&& n.rhs.kind == nkind.N_STRUCTLIT
&& leaffi.tnode != nil
@@ -13712,131 +13638,19 @@ fn cgassign(c: *cgen, n: *node) void = {
// si.totsize is slot-padded (rounded to 8);
// receive ABI needs the TYPE's natural size.
let lsz: i32 = structnaturalsize(lsi);
if (n.rhs.op == tkind.TK_ELLIPSIS) {
emitline("\tXORQ\tAX, AX\n");
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
let zi: i32 = 0;
for (zi + 8 <= lsz) {
emitline("\tMOVQ\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 8;
};
for (zi + 4 <= lsz) {
emitline("\tMOVL\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 4;
};
for (zi < lsz) {
emitline("\tMOVB\tAX, ");
emitdispreg((totaloff + zi): i64, "BX");
emitline("\n");
zi += 1;
};
} else {
let zi: i32 = 0;
for (zi + 8 <= lsz) {
emitline("\tMOVQ\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 8;
};
for (zi + 4 <= lsz) {
emitline("\tMOVL\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 4;
};
for (zi < lsz) {
emitline("\tMOVB\tAX, ");
emitoff((rootoff + totaloff + zi): i64);
emitline("(BP)\n");
zi += 1;
};
};
let dmode: i32 = 0;
let ddisp: i32 = rootoff + totaloff;
if (ptrroot) {
dmode = 1;
ddisp = totaloff;
};
let fldn: *node = n.rhs.list;
for (fldn != nil) {
if (fldn.kind == nkind.N_FIELD) {
let fnm: str = fldn.str;
let fi: *fieldinfo = lsi.fields;
for (fi != nil) {
if (streq(fi.fname, fnm)) {
cgexpr(c, fldn.lhs);
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) {
mov = "MOVSS";
};
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg((totaloff + fi.foff): i64, "BX");
emitline("\n");
} else {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((rootoff + totaloff + fi.foff): i64);
emitline("(BP)\n");
};
fi = nil;
} else {
let fsz: i32 = fi.fsz;
let op: str = "MOVQ";
if (fsz == 1) { op = "MOVB"; };
if (fsz == 4) { op = "MOVL"; };
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), BX\n");
};
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitdispreg((totaloff + fi.foff): i64, "BX");
emitline("\n");
} else {
emitline("\t");
emitline(op);
emitline("\tAX, ");
emitoff((rootoff + totaloff + fi.foff): i64);
emitline("(BP)\n");
};
fi = nil;
};
} else {
fi = fi.finext;
};
};
};
fldn = fldn.next;
if (isglobal) {
dmode = 2;
ddisp = totaloff;
};
cgstructlitfill(c, lsi, n.rhs,
dmode, rootoff, rootname,
ddisp, lsz);
return;
};
};