cgen: copy all eightbytes when a non-call aggregate assigns into a field of an indexed element (#11b)

The arr[i].f=src legacy assign block enumerated scalar field-type arms then fell to a 1-word scalar default, so a non-call aggregate source (ident/dot/index) cgexpr'd only its first word into AX and stored one eightbyte — silent on BOTH stages (byte-id blind). The non-indexed bases (local/deref/chained/global) reach the general assign resolver's canonical aggargsrcaddr+aggcopy; the indexed arm short-circuited before it. Route the indexed base through the block's own proven &arr[i] spine into the same aggargsrcaddr+aggcopy emitters (DRY — no third copy), dual-site symmetric. Unlike #11's in-cap arm, the source is a memory address so aggcopy is a pure memcpy: float bits and the sub-8 tail transport verbatim, no loud-stop needed. Did not fall through to the general resolver because its cgplaceaddr N_INDEX arm rejects a *[N]S (TY_PTR) base (latent resolver gap, filed separately).

Contained to the indexed base + non-call aggregate-field rhs; value-asserting pins redden under each stage's independent revert.
This commit is contained in:
2026-06-27 19:40:04 +09:00
parent 737126ed69
commit 3719ff1c64
4 changed files with 302 additions and 0 deletions

View File

@@ -5982,6 +5982,57 @@ cgexpr(Cg *c, Node *n, Local *locals)
* wholly in AX; the scalar default's
* single store is the correct receive. */
}
/* #11b: a NON-call AGGREGATE source into an
* aggregate field of an indexed element
* `arr[i].f = src` (src an ident / .g / index).
* The scalar default below loads only the
* source's FIRST word into AX and stores ONE
* word — dropping the rest (a SILENT both-stage
* member drop, the non-call twin of the #11
* in-cap CALL arm above; byte-id blind). Unlike
* #11's GP AX/DX/CX cursor the source is a
* MEMORY address, so the shared mem-to-mem
* cg_aggcopy transports EVERY byte: a sub-8
* tail and float bits copy verbatim, so NO
* tail/float/over-cap loud-stop is needed here
* (those #11 stops were register-cursor
* artefacts). Reuse the block's own &arr[i]
* spine (proven for [N]S / *[N]S / []S by the
* sibling arms) -> BX + foff, then funnel
* through aggarg_srcaddr (src -> SI) +
* cg_aggcopy — the ONE copy emitter the non-
* indexed bases use (DRY). fsz natural
* (ft->size). Mirrors wwstage cgenexpr.ww. */
if (n->op == TK_ASSIGN && n->rhs
&& n->rhs->kind != N_CALL && fu
&& (fu->kind == TY_STRUCT || fu->kind == TY_ARRAY
|| fu->kind == TY_TUPLE) && fsz > 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));
if (foff != 0)
ins2(c, A_ADDQ, aimm(foff), areg(D_BX));
/* spill dest across the source-address
* resolution (the #270-1b order:
* aggarg_srcaddr clobbers BX). */
ins1(c, A_PUSHQ, areg(D_BX));
if (!aggarg_srcaddr(c, n->rhs, D_SI, locals))
fatal("#11b: aggregate field receive "
"arr[i].f=src - source shape unwired "
"(rule-7)");
ins1(c, A_POPQ, areg(D_BX));
cg_aggcopy(c, fsz);
break;
}
if (n->op == TK_ASSIGN) {
cgexpr(c, n->rhs, locals);
ins1(c, A_PUSHQ,