cgen: store all eightbytes when an in-cap aggregate call returns into an array element (#31-G)
`arr[i] = mk()` where mk returns an in-cap (<=24B) struct/tuple/array left the result in the #4 cgreturn registers (AX/DX/CX), but the N_ASSIGN-into-N_INDEX path had no arm for an N_CALL rhs, so it fell to the scalar store tail: only member 0 was written and the index scale clobbered CX. Both stages emitted byte-identical wrong code (the documented-but-silent #31-G gap), so the byte-id gate was blind to it. Add an in-cap N_CALL-rhs arm: materialise the return into a frame scratch first (keeping the CALL at the frame's natural 16B alignment), resolve &arr[i], then word-copy the full eightbyte count + sub-8 tail -- mirroring the #4 receive shape and the #270-1b copy. The eightbyte count derives from the element size in the type table. Over-cap returns (#234), non-call rhs (#270-1b) and tuple literals (#121) are unaffected; the sibling field/deref shapes stay loud (#24). Surfaced by the codegen miscompile hunt (finding C2c). Pinned by test/lang/idx_aggret_recv_test.ww (10 value-asserting rows: 2/3-eightbyte structs+tuples, array elem, sub-8 tail, const/runtime index, all four base shapes; reddens on revert).
This commit is contained in:
101
cmd/w6c/cgen.c
101
cmd/w6c/cgen.c
@@ -6731,6 +6731,107 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cg_sret_dest_off = 0;
|
||||
break;
|
||||
}
|
||||
/* C2c / #31-G: an IN-CAP aggregate-returning CALL into an
|
||||
* indexed element `a[i] = mk()`. The #234 arm above only
|
||||
* fires for an OVER-cap (sret/memory) return; the #270-1b
|
||||
* arm below excludes N_CALL (it copies from a source
|
||||
* ADDRESS, which a call result has none). An in-cap (≤24B)
|
||||
* struct/array/tuple return leaves AX/DX/CX per the #4
|
||||
* cgreturn ABI but fell through to the 1-word scalar store
|
||||
* (AX only) — dropping DX/CX (a silent field-drop, the
|
||||
* documented-but-unfixed #31-G). Materialise the return into
|
||||
* a frame scratch (the AX/DX/CX receive shape at the N_LET
|
||||
* call-rhs site, cgen.c:3434), THEN compute &a[i] and word-
|
||||
* copy scratch → dest. Scratch-first (not a dest spill across
|
||||
* the call) keeps the call at the frame's natural alignment.
|
||||
* In-cap only (cg_sret_retsize==0). */
|
||||
if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN
|
||||
&& esubu && (esubu->kind == TY_STRUCT
|
||||
|| esubu->kind == TY_ARRAY
|
||||
|| esubu->kind == TY_TUPLE)
|
||||
&& esz > 8
|
||||
&& n->rhs->kind == N_CALL
|
||||
&& cg_sret_retsize(esub) == 0) {
|
||||
int scr = cg_tagscr_slot(c, &locals, esz);
|
||||
cgexpr(c, n->rhs, locals); /* call → AX/DX/CX */
|
||||
/* AX/DX/CX → scratch (mirror cgen.c:3434 receive). */
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = esz / 8;
|
||||
int tail = esz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(D_BP, scr + i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW : A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(D_BP, scr + full * 8));
|
||||
}
|
||||
/* dest &a[i] → BX (mirror #121 / #270-1b resolve) */
|
||||
cgexpr(c, n->lhs->rhs, locals); /* idx → AX */
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
||||
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (base->kind == N_IDENT) {
|
||||
int off = localfind(locals, base->str);
|
||||
int isglobal = (off == 0)
|
||||
&& let_islet(base->str);
|
||||
if (isglobal && is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
else if (isglobal)
|
||||
ins2(c, A_MOVQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
else if (is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off), areg(D_BX));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off), areg(D_BX));
|
||||
} else if (cg_dotbase_addr(c, base, D_BX, locals)) {
|
||||
/* N_DOT array-field base resolved inline. */
|
||||
} else {
|
||||
cgexpr(c, base, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
||||
}
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||
/* word-copy scratch → dest (tail-aware, mirror
|
||||
* #270-1b copy). */
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
}
|
||||
if (k + 4 <= esz) {
|
||||
ins2(c, A_MOVL, amem(D_BP, scr + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= esz) {
|
||||
ins2(c, A_MOVW, amem(D_BP, scr + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= esz) {
|
||||
ins2(c, A_MOVB, amem(D_BP, scr + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* #121 (write-face of leg-b): a tuple-LITERAL rhs into an
|
||||
* indexed element `a[i] = (3,4)`. A literal has no source
|
||||
* ADDRESS, so the ident/dot/deref copy arm below can't reach
|
||||
|
||||
Reference in New Issue
Block a user