cgen: store all eightbytes when an in-cap aggregate call returns into a field of an indexed element (#11)

The arr[i].f=mk() assign arm had no aggregate-field sub-arm, so a by-value aggregate field receive fell to the scalar default (one MOVQ, dropping DX/CX) — silent on BOTH stages (byte-id blind). Add a dual-site symmetric in-cap N_CALL arm mirroring C2c (c83a340): scratch-first materialise AX/DX/CX, then word-copy to (fi.foff+k*8) within &arr[i], sizing from the natural field size fi.fsz (not slotsize). Rule-7 LOUD-STOP for the three cases the in-cap GP path cannot transport: over-cap sret (#11c/#234), a float-bearing field whose eightbyte classifies SSE (#11/#165), and a 3/5/6/7-byte sub-8 tail the single narrow tail MOV cannot express (the general cascade tail is the shared C2c/#11 follow-up, task #10). Value-asserting pins (poison-seeded, redden under each stage's independent revert) plus cfail pins for the three loud-stops.

Contained to the indexed base + in-cap call rhs; arr[i].f=src (#11b) and over-cap (#11c) are separate.
This commit is contained in:
2026-06-27 18:49:03 +09:00
parent b8d55a729e
commit a0e330b283
7 changed files with 522 additions and 0 deletions

View File

@@ -5810,6 +5810,178 @@ cgexpr(Cg *c, Node *n, Local *locals)
amem(D_BX, foff + 8));
break;
}
/* #11: an in-cap aggregate-returning CALL into
* an AGGREGATE field of an indexed element
* `arr[i].f = mk()`. The scalar default below
* stores only AX (eb0), dropping DX/CX — a SILENT
* both-stage field-drop, the field-of-indexed twin
* of C2c's whole-element arr[i]=mk() arm
* (cgen.c :6764). Scratch-first materialise of the
* AX/DX/CX return (not a PUSHQ spill — keeps the
* CALL at the frame's 16B alignment and survives an
* idx that itself contains a call), reuse the scalar
* arm's &arr[i]->BX address computation verbatim,
* then word-copy scratch to foff(BX). In-cap only
* (cg_sret_retsize==0); over-cap sret-into-field
* LOUD-STOPS (#11c/#234, task #8) and a float-
* bearing aggregate LOUD-STOPS (#165/#171 — a pure-
* float return eightbyte rides X0/X1 which the GP
* AX/DX/CX cursor cannot read). Mirrors wwstage
* cgenexpr.ww. */
if (n->op == TK_ASSIGN && n->rhs
&& n->rhs->kind == N_CALL) {
if (cg_sret_retsize(ft) > 0)
fatal("#11c/#234: over-cap "
"(sret) aggregate field "
"receive arr[i].f=mk() "
"unwired (cs!=ww; task #8)");
/* struct_float_class mirrors the
* return-side SSE routing (#171a); a
* field typed DIRECTLY as a tuple misses
* it yet the bare-tuple return routes
* floats to tuple_sse_seq — guard it too
* so neither stage silently stores X0
* garbage through the GP cursor. */
int sclass11[2];
int sse11 = struct_float_class(ft,
sclass11) > 0;
if (!sse11) {
Type *tu11 =
type_chase_named(ft);
if (tu11 && tu11->kind
== TY_TUPLE)
for (Tparam *p11 =
tu11->params; p11;
p11 = p11->next) {
int f32_11;
if (fld_isfloat(
p11->type,
&f32_11))
sse11 = 1;
}
}
if (sse11)
fatal("#11/#165: float-bearing "
"aggregate field receive "
"arr[i].f=mk() unwired (SSE "
"return eightbyte; #171)");
if (fsz > 8) {
/* A 3/5/6/7-byte sub-8 tail cannot be
* materialised by the single narrow MOV
* below — it stores ONE byte while the
* copy reads the full tail, dropping the
* rest from uninitialised scratch (a
* silent both-stage drop; empirically a
* 14B 7xi16 field loses f/g). The whole-
* element C2c sibling (:6764) shares this
* single-tail gap; until a general
* register->scratch tail (shift cascade)
* lands across BOTH sites, LOUD-STOP
* rather than silently drop — rule 7, the
* sibling of the over-cap/float stops
* above. Tails 0/1/2/4 are exact and flow
* through. Mirrors wwstage cgenexpr.ww. */
int tl11 = fsz % 8;
if (tl11 == 3 || tl11 == 5
|| tl11 == 6 || tl11 == 7)
fatal("#11: aggregate field receive "
"arr[i].f=mk() with a 3/5/6/7-byte "
"sub-8 tail unwired (materialise "
"single-MOV under-stores; "
"C2c-shared)");
int scr11 = cg_tagscr_slot(c,
&locals, fsz);
cgexpr(c, n->rhs, locals);
int regs11[3] = { D_AX, D_DX,
D_CX };
int full11 = fsz / 8;
int tail11 = fsz % 8;
for (int i11 = 0;
i11 < full11; i11++)
ins2(c, A_MOVQ,
areg(regs11[i11]),
amem(D_BP, scr11
+ i11 * 8));
if (tail11 > 0) {
int op11 = (tail11 == 4)
? A_MOVL : (tail11
== 2) ? A_MOVW
: A_MOVB;
ins2(c, op11,
areg(regs11[full11]),
amem(D_BP, scr11
+ full11 * 8));
}
cgexpr(c, idx, locals);
if (esz > 1) {
ins2(c, A_MOVQ,
aimm(esz),
areg(D_CX));
ins2(c, A_IMULQ,
areg(D_CX),
areg(D_AX));
}
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));
ins2(c, A_ADDQ,
areg(D_AX), areg(D_BX));
if (viaptr)
ins2(c, A_MOVQ,
amem(D_BX, 0),
areg(D_BX));
int k11 = 0;
for (; k11 + 8 <= fsz;
k11 += 8) {
ins2(c, A_MOVQ,
amem(D_BP, scr11
+ k11), areg(D_AX));
ins2(c, A_MOVQ,
areg(D_AX),
amem(D_BX,
foff + k11));
}
if (k11 + 4 <= fsz) {
ins2(c, A_MOVL,
amem(D_BP, scr11
+ k11), areg(D_AX));
ins2(c, A_MOVL,
areg(D_AX),
amem(D_BX,
foff + k11));
k11 += 4;
}
if (k11 + 2 <= fsz) {
ins2(c, A_MOVW,
amem(D_BP, scr11
+ k11), areg(D_AX));
ins2(c, A_MOVW,
areg(D_AX),
amem(D_BX,
foff + k11));
k11 += 2;
}
if (k11 + 1 <= fsz) {
ins2(c, A_MOVB,
amem(D_BP, scr11
+ k11), areg(D_AX));
ins2(c, A_MOVB,
areg(D_AX),
amem(D_BX,
foff + k11));
k11 += 1;
}
break;
}
/* fsz<=8 in-cap aggregate returns
* wholly in AX; the scalar default's
* single store is the correct receive. */
}
if (n->op == TK_ASSIGN) {
cgexpr(c, n->rhs, locals);
ins1(c, A_PUSHQ,