cgen: fix nested aggregate field dropped in alloc-heap structlit fill (C7c)

`alloc(Outer{ x = Inner{q=10} })` dropped the nested struct-literal field:
the alloc path had its own inline fill loop with only scalar/float/str
arms, so a field whose value is itself an N_STRUCTLIT fell to the scalar
tail and stored MOVQ $0 (cgexpr leaves a whole aggregate in no register)
over the inner slot. Both stages emitted the identical wrong fill, so the
byte-id gate was blind to it.

Route alloc's fill through the existing shared structlit-fill helper (the
one the BP-relative/global/local structlit sites already use -- it handles
nested-struct recursion, N_ARRLIT, str/slice and tagged) via a new 4th
destination mode DST_PTR_SP that reloads the heap base from (SP). This
deletes alloc's divergent inline loop, the lone site lacking the recursion.
As a side effect it also fixes a latent slice-field drop in the driver's
own alloc(sepgraph{...}) (pkg.len/.cap were dropped; the consumer reads
neither -- g.n is the count SSoT). Nested-array fields are closed in-class;
a nested tuple-LITERAL field now errors loudly and symmetrically (the #49
non-addressable gap, previously dropped silently at alloc only).

Surfaced by the codegen miscompile hunt (finding C7c). Pinned by
test/lang/alloc_nested_field_test.ww (nested struct depth 1+2, nested
array, adjacent multi-nested, sibling-no-clobber; reddens on revert).
Routing preservation proven: the whole test/lang corpus is byte-identical
HEAD vs fixed except the new pin; self-compile byte-id (990-996) green.
This commit is contained in:
2026-06-27 14:16:20 +09:00
parent d152f0d744
commit a52b1aa1d9
4 changed files with 168 additions and 98 deletions

View File

@@ -5076,6 +5076,15 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node,
// 2 = DST_GLOBAL — base = BX, reloaded via `LEAQ srcname(SB),
// BX` with the same cadence as DST_PTR_LOCAL.
// srcoff unused.
// 3 = DST_PTR_SP — base = BX, reloaded via `MOVQ (SP), BX` with
// the same cadence as DST_PTR_LOCAL. dst is the
// alloc-heap base saved by cgalloc's `PUSHQ AX`
// (top-of-stack); cgexpr is stack-balanced so
// (SP) keeps pointing at it across the walk.
// srcoff/srcname unused. C7c: nested struct/
// array/tuple field VALUE in alloc(Outer{x =
// Inner{..}}) now writes the inner leaves
// instead of storing AX=0 over the inner slot.
//
// Param semantics (locked in here so the recursion contract is
// clear):
@@ -5139,6 +5148,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
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, ");
@@ -5198,6 +5210,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
cgwidentaggedstore(c, fi.tnode.type_: *syntax.tinfo,
fieldnode.lhs, basereg,
disp + fi.foff, fi.fsz);
@@ -5293,6 +5308,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
let full: i32 = cfsz / 8;
let ci: i32 = 0;
for (ci < full) {
@@ -5370,11 +5388,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
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 + fi.foff): i64, "DX");
emitline("\n");
@@ -5464,6 +5484,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
@@ -5503,6 +5526,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
let eoff: i32 = disp + fi.foff + idx * esz;
if (isfloatel) {
emitline("\t");
@@ -5556,11 +5582,13 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
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 + fi.foff != 0) {
emitline("\tADDQ\t$");
emitint((disp + fi.foff): i64);
@@ -5589,6 +5617,9 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
emitsymname(c, srcname);
emitline("(SB), BX\n");
};
if (mode == 3) {
emitline("\tMOVQ\t(SP), BX\n");
};
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };