cstage+selfhost+test: fix nested call-rhs silent zero in structlit fill (3rd of family)
Sister bug to #17 / #18. The structlit-fill helper handled nested N_STRUCTLIT field values but a struct-typed field whose VALUE is an N_CALL (call returning a struct, #4 cgreturn ABI) fell through to the cgexpr-then-AX-store path — landing AX=first qword and silently dropping DX/CX. For 16B/24B inner returns the trailing 8B/16B stayed zero (whatever was in the destination slot beforehand). Fix: a new N_CALL+struct branch in cg_structlit_fill / cgstructlitfill, placed between the nested-N_STRUCTLIT recursion and the scalar cgexpr fallthrough. Emits cgexpr -> BX reload (non-BP modes only) -> MOVQ AX/DX/CX x full + sized tail (MOVL/MOVW/MOVB) per #4's receive shape. INVARIANT (commented inline both stages): between cgexpr(N_CALL) and the AX/DX/CX stores below, no instruction may touch AX/DX/CX. Only the BX reload (MOVQ srcoff(BP),BX or LEAQ name(SB),BX) is safe. Sized-tail dispatch is {1->MOVB, 2->MOVW, 4->MOVL, else MOVQ}. Unlike the scalar fallthrough — which still uses the {1/4/else MOVQ} shape to stay byte-identical with cstage pending #13 — the new branch is correctness-by-construction: MOVW for tail==2 only fires on call-rhs shapes that didn't compile before, and both stages emit it symmetrically (705's 10B inner row pins this). Guard `fsz <= 24 && fsz%8 in {0,1,2,4}` mirrors #4's cgreturn ABI: >24B falls through (sret deferred), and fsz%8 in {3,5,6,7} would need shift-store — also unsupported by #4. Filed as task #21 (covers both cgreturn and call-rhs's identical gap). Two #15 sidesteps, both documented inline: 1. wwstage's fi.fsz for an inner-struct field is slot-padded (8-rounded), not natural — using it would emit 2x MOVQ where cstage emits MOVQ+MOVL for a 12B inner. The new wwstage branch uses structnaturalsize(csi) to recover the natural size, matching cstage's fl->type->size (check.c hands the helper natural sizes). This sidesteps #15 without touching its scope. 2. The outer struct's totsize diverges across stages when maxalign<8 (wwstage rounds to 8 universally; cstage to maxalign). The 705 test rows pin `x: i64` on the outer to force outer maxalign=8, keeping BP offsets stable across stages. Test-side sidestep only; also #15 territory. Files: - cmd/w6c/cgen.c cg_structlit_fill extended - selfhost/cmd/wcc/cgenutil.ww cgstructlitfill mirror - selfhost/cmd/{w6c,wwdump}/main.combined.ww auto-regen - test/wcc/705_nested_call_rhs.c 8 rows, table-driven; pins cstage exit + wwstage exit + .s byte-identity. Tail widths 0/4/2/1, dst modes DST_BP + DST_PTR_LOCAL, shallow + 3-deep. - Makefile 705 wiring Test: 65/65 PASS. 994_w6c_ww + 995_self_rebuild PASS (byte-identity holds — load-bearing).
This commit is contained in:
@@ -8826,8 +8826,123 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
};
|
||||
};
|
||||
};
|
||||
// Nested struct-typed CALL value (#20). cgexpr
|
||||
// leaves AX=bytes[0..7], DX=bytes[8..15], CX=
|
||||
// bytes[16..23] per #4's cgreturn ABI. Pre-#20
|
||||
// the cgexpr-then-AX-store fallthrough below
|
||||
// silently dropped past the first qword for any
|
||||
// fsz > 8 (only AX got stored).
|
||||
//
|
||||
// Sized stores: MOVQ for full 8B chunks plus a
|
||||
// sized tail (MOVL/MOVW/MOVB) by `tail = fsz%8`.
|
||||
// Mirror of cstage cg_structlit_fill's #20 branch.
|
||||
// MOVW-for-tail==2 only fires on shapes that
|
||||
// didn't compile before, so no #13 byte-identity
|
||||
// concern.
|
||||
//
|
||||
// Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` matches
|
||||
// #4's cgreturn ABI: >24B falls through (sret
|
||||
// deferred); fsz%8 ∈ {3,5,6,7} would need shift-
|
||||
// store and is also unsupported by #4 — falls
|
||||
// through to the existing AX-only wrongness
|
||||
// (consistent, tracked as follow-up).
|
||||
//
|
||||
// INVARIANT: between cgexpr(N_CALL) and the
|
||||
// AX/DX/CX stores below, NO instruction may touch
|
||||
// AX/DX/CX. The BX reload is safe; any other
|
||||
// emission added here will silently corrupt the
|
||||
// return value.
|
||||
let callwhole: bool = false;
|
||||
if (!nested) {
|
||||
if (fieldnode.lhs != nil) {
|
||||
if (fieldnode.lhs.kind == nkind.N_CALL) {
|
||||
if (fi.tnode != nil) {
|
||||
if (fi.tnode.kind == nkind.N_TNAME) {
|
||||
if (primsize(fi.tnode.str) == 0) {
|
||||
let csi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (csi != nil) {
|
||||
// Use the inner struct's
|
||||
// NATURAL size (no 8B slot
|
||||
// rounding) so MOVL/MOVW/
|
||||
// MOVB tail dispatch matches
|
||||
// cstage's fl->type->size
|
||||
// (which is natural per
|
||||
// check.c). fi.fsz here is
|
||||
// wwstage's slot-padded
|
||||
// totsize — using it would
|
||||
// emit 2× MOVQ where cstage
|
||||
// emits MOVQ+MOVL for a
|
||||
// 12B inner, etc. (task #15
|
||||
// territory; sidestepped
|
||||
// locally.)
|
||||
let cfsz: i32 = structnaturalsize(csi);
|
||||
let crem: i32 = cfsz - (cfsz / 8) * 8;
|
||||
if (cfsz <= 24) {
|
||||
if (crem == 0 || crem == 1
|
||||
|| crem == 2 || crem == 4) {
|
||||
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");
|
||||
};
|
||||
let full: i32 = cfsz / 8;
|
||||
let ci: i32 = 0;
|
||||
for (ci < full) {
|
||||
let r: str = "AX";
|
||||
if (ci == 1) { r = "DX"; };
|
||||
if (ci == 2) { r = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(r);
|
||||
emitline(", ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + fi.foff + ci * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + fi.foff + ci * 8): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
ci += 1;
|
||||
};
|
||||
if (crem > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (crem == 4) { top = "MOVL"; };
|
||||
if (crem == 2) { top = "MOVW"; };
|
||||
let tr: str = "AX";
|
||||
if (full == 1) { tr = "DX"; };
|
||||
if (full == 2) { tr = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(tr);
|
||||
emitline(", ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + fi.foff + full * 8): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
callwhole = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (nested) {
|
||||
fi = nil;
|
||||
} else if (callwhole) {
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
|
||||
@@ -2954,8 +2954,123 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
};
|
||||
};
|
||||
};
|
||||
// Nested struct-typed CALL value (#20). cgexpr
|
||||
// leaves AX=bytes[0..7], DX=bytes[8..15], CX=
|
||||
// bytes[16..23] per #4's cgreturn ABI. Pre-#20
|
||||
// the cgexpr-then-AX-store fallthrough below
|
||||
// silently dropped past the first qword for any
|
||||
// fsz > 8 (only AX got stored).
|
||||
//
|
||||
// Sized stores: MOVQ for full 8B chunks plus a
|
||||
// sized tail (MOVL/MOVW/MOVB) by `tail = fsz%8`.
|
||||
// Mirror of cstage cg_structlit_fill's #20 branch.
|
||||
// MOVW-for-tail==2 only fires on shapes that
|
||||
// didn't compile before, so no #13 byte-identity
|
||||
// concern.
|
||||
//
|
||||
// Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` matches
|
||||
// #4's cgreturn ABI: >24B falls through (sret
|
||||
// deferred); fsz%8 ∈ {3,5,6,7} would need shift-
|
||||
// store and is also unsupported by #4 — falls
|
||||
// through to the existing AX-only wrongness
|
||||
// (consistent, tracked as follow-up).
|
||||
//
|
||||
// INVARIANT: between cgexpr(N_CALL) and the
|
||||
// AX/DX/CX stores below, NO instruction may touch
|
||||
// AX/DX/CX. The BX reload is safe; any other
|
||||
// emission added here will silently corrupt the
|
||||
// return value.
|
||||
let callwhole: bool = false;
|
||||
if (!nested) {
|
||||
if (fieldnode.lhs != nil) {
|
||||
if (fieldnode.lhs.kind == nkind.N_CALL) {
|
||||
if (fi.tnode != nil) {
|
||||
if (fi.tnode.kind == nkind.N_TNAME) {
|
||||
if (primsize(fi.tnode.str) == 0) {
|
||||
let csi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (csi != nil) {
|
||||
// Use the inner struct's
|
||||
// NATURAL size (no 8B slot
|
||||
// rounding) so MOVL/MOVW/
|
||||
// MOVB tail dispatch matches
|
||||
// cstage's fl->type->size
|
||||
// (which is natural per
|
||||
// check.c). fi.fsz here is
|
||||
// wwstage's slot-padded
|
||||
// totsize — using it would
|
||||
// emit 2× MOVQ where cstage
|
||||
// emits MOVQ+MOVL for a
|
||||
// 12B inner, etc. (task #15
|
||||
// territory; sidestepped
|
||||
// locally.)
|
||||
let cfsz: i32 = structnaturalsize(csi);
|
||||
let crem: i32 = cfsz - (cfsz / 8) * 8;
|
||||
if (cfsz <= 24) {
|
||||
if (crem == 0 || crem == 1
|
||||
|| crem == 2 || crem == 4) {
|
||||
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");
|
||||
};
|
||||
let full: i32 = cfsz / 8;
|
||||
let ci: i32 = 0;
|
||||
for (ci < full) {
|
||||
let r: str = "AX";
|
||||
if (ci == 1) { r = "DX"; };
|
||||
if (ci == 2) { r = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(r);
|
||||
emitline(", ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + fi.foff + ci * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + fi.foff + ci * 8): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
ci += 1;
|
||||
};
|
||||
if (crem > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (crem == 4) { top = "MOVL"; };
|
||||
if (crem == 2) { top = "MOVW"; };
|
||||
let tr: str = "AX";
|
||||
if (full == 1) { tr = "DX"; };
|
||||
if (full == 2) { tr = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(tr);
|
||||
emitline(", ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + fi.foff + full * 8): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
callwhole = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (nested) {
|
||||
fi = nil;
|
||||
} else if (callwhole) {
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
|
||||
@@ -8826,8 +8826,123 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
};
|
||||
};
|
||||
};
|
||||
// Nested struct-typed CALL value (#20). cgexpr
|
||||
// leaves AX=bytes[0..7], DX=bytes[8..15], CX=
|
||||
// bytes[16..23] per #4's cgreturn ABI. Pre-#20
|
||||
// the cgexpr-then-AX-store fallthrough below
|
||||
// silently dropped past the first qword for any
|
||||
// fsz > 8 (only AX got stored).
|
||||
//
|
||||
// Sized stores: MOVQ for full 8B chunks plus a
|
||||
// sized tail (MOVL/MOVW/MOVB) by `tail = fsz%8`.
|
||||
// Mirror of cstage cg_structlit_fill's #20 branch.
|
||||
// MOVW-for-tail==2 only fires on shapes that
|
||||
// didn't compile before, so no #13 byte-identity
|
||||
// concern.
|
||||
//
|
||||
// Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` matches
|
||||
// #4's cgreturn ABI: >24B falls through (sret
|
||||
// deferred); fsz%8 ∈ {3,5,6,7} would need shift-
|
||||
// store and is also unsupported by #4 — falls
|
||||
// through to the existing AX-only wrongness
|
||||
// (consistent, tracked as follow-up).
|
||||
//
|
||||
// INVARIANT: between cgexpr(N_CALL) and the
|
||||
// AX/DX/CX stores below, NO instruction may touch
|
||||
// AX/DX/CX. The BX reload is safe; any other
|
||||
// emission added here will silently corrupt the
|
||||
// return value.
|
||||
let callwhole: bool = false;
|
||||
if (!nested) {
|
||||
if (fieldnode.lhs != nil) {
|
||||
if (fieldnode.lhs.kind == nkind.N_CALL) {
|
||||
if (fi.tnode != nil) {
|
||||
if (fi.tnode.kind == nkind.N_TNAME) {
|
||||
if (primsize(fi.tnode.str) == 0) {
|
||||
let csi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (csi != nil) {
|
||||
// Use the inner struct's
|
||||
// NATURAL size (no 8B slot
|
||||
// rounding) so MOVL/MOVW/
|
||||
// MOVB tail dispatch matches
|
||||
// cstage's fl->type->size
|
||||
// (which is natural per
|
||||
// check.c). fi.fsz here is
|
||||
// wwstage's slot-padded
|
||||
// totsize — using it would
|
||||
// emit 2× MOVQ where cstage
|
||||
// emits MOVQ+MOVL for a
|
||||
// 12B inner, etc. (task #15
|
||||
// territory; sidestepped
|
||||
// locally.)
|
||||
let cfsz: i32 = structnaturalsize(csi);
|
||||
let crem: i32 = cfsz - (cfsz / 8) * 8;
|
||||
if (cfsz <= 24) {
|
||||
if (crem == 0 || crem == 1
|
||||
|| crem == 2 || crem == 4) {
|
||||
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");
|
||||
};
|
||||
let full: i32 = cfsz / 8;
|
||||
let ci: i32 = 0;
|
||||
for (ci < full) {
|
||||
let r: str = "AX";
|
||||
if (ci == 1) { r = "DX"; };
|
||||
if (ci == 2) { r = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(r);
|
||||
emitline(", ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + fi.foff + ci * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + fi.foff + ci * 8): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
ci += 1;
|
||||
};
|
||||
if (crem > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (crem == 4) { top = "MOVL"; };
|
||||
if (crem == 2) { top = "MOVW"; };
|
||||
let tr: str = "AX";
|
||||
if (full == 1) { tr = "DX"; };
|
||||
if (full == 2) { tr = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(tr);
|
||||
emitline(", ");
|
||||
if (mode == 0) {
|
||||
emitoff((disp + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitdispreg((disp + fi.foff + full * 8): i64, basereg);
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
callwhole = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (nested) {
|
||||
fi = nil;
|
||||
} else if (callwhole) {
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
|
||||
Reference in New Issue
Block a user