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:
2026-06-27 13:28:37 +09:00
parent 33295c41c6
commit c83a3403a4
3 changed files with 344 additions and 0 deletions

View File

@@ -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