cgen: store the full register into padded scratch for a 3/5/6/7-byte aggregate tail (#10)

The in-cap aggregate-receive materialise emitted a single narrow tail MOV that fell to MOVB for a 3/5/6/7-byte sub-8 tail, storing one byte while the scratch->dest copy read the full tail from uninitialised scratch — silently dropping members at the C2c whole-element arm (arr[i]=mk()) and loud-stopping at the #11 field arm. The scratch slot is ceil-8 padded (local_alloc/localadd round to 8) and the copy reads only tsz bytes, so flipping the tail default MOVB->MOVQ stores the full register harmlessly into the slot's own pad (in-bounds for in-cap <=24B); 1/2/4-byte tails stay byte-identical. Both stages symmetric. Removes the now-redundant #11 sub-8-tail loud-stop (keeps the float #165 and over-cap #234 loud-stops). The same narrow-tail materialise recurs at 6 other cstage sites (task #14).

Retires the obsolete idx_dot_aggret_subtail_loud //ww:error fixture (both stages now compile the case) and converts it to a positive cstage run-test; the struct-field shape is byte-id-divergent only via the pre-existing #9 frame-size bug, so the value pin uses array-field shapes. Value-asserting, reddens under each stage's independent revert.
This commit is contained in:
2026-06-27 23:44:41 +09:00
parent 5b33ea3f7d
commit b3f4990979
5 changed files with 145 additions and 71 deletions

View File

@@ -5866,29 +5866,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
"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)");
/* The sub-8 tail materialise stores the FULL
* 8-byte register (MOVQ) into a ceil-8-padded
* scratch (cg_tagscr_slot -> local_alloc rounds
* to 8): the over-stored high bytes land in the
* pad and the scratch->dest copy reads only fsz
* bytes, so every in-cap tail (incl. 3/5/6/7) is
* exact without an immediate-shift cascade (w6a
* has no SHRQ $imm). Shares the C2c whole-element
* materialise (:7008). #10; mirrors wwstage
* cgenexpr.ww. */
int scr11 = cg_tagscr_slot(c,
&locals, fsz);
cgexpr(c, n->rhs, locals);
@@ -5903,10 +5890,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
amem(D_BP, scr11
+ i11 * 8));
if (tail11 > 0) {
int op11 = (tail11 == 4)
? A_MOVL : (tail11
int op11 = (tail11 == 1)
? A_MOVB : (tail11
== 2) ? A_MOVW
: A_MOVB;
: (tail11 == 4)
? A_MOVL : A_MOVQ;
ins2(c, op11,
areg(regs11[full11]),
amem(D_BP, scr11
@@ -7015,8 +7003,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
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;
int op = (tail == 1) ? A_MOVB
: (tail == 2) ? A_MOVW
: (tail == 4) ? A_MOVL : A_MOVQ;
ins2(c, op, areg(regs[full]),
amem(D_BP, scr + full * 8));
}