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:
@@ -1347,6 +1347,55 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
mode, srcoff, name, disp + (int)foff);
|
||||
continue;
|
||||
}
|
||||
/* 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`. Mirrors #4's receive
|
||||
* shape at the N_LET / N_ASSIGN call-rhs sites; the
|
||||
* MOVW-for-tail==2 emission 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 (MOVQ/LEAQ) is safe; any other emission
|
||||
* added here will silently corrupt the return value. */
|
||||
if (fu && fu->kind == TY_STRUCT
|
||||
&& f->lhs && f->lhs->kind == N_CALL
|
||||
&& fsz <= 24
|
||||
&& (fsz % 8 == 0 || fsz % 8 == 1
|
||||
|| fsz % 8 == 2 || fsz % 8 == 4)) {
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
areg(D_BX));
|
||||
else if (mode == DST_GLOBAL)
|
||||
ins2(c, A_LEAQ, masym(c, name), areg(D_BX));
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = fsz / 8;
|
||||
int tail = fsz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(base_reg,
|
||||
disp + (int)foff + i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW : A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(base_reg,
|
||||
disp + (int)foff + full * 8));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
/* For non-BP modes, cgexpr just clobbered BX; reload it
|
||||
* before the store. */
|
||||
|
||||
Reference in New Issue
Block a user