wcc: single-dot field compound assignment routes through one combine helper
s.f *= v silently became s.f = v (and the other non-+=/-= ops dropped likewise) in BOTH stages across five lvalue sub-arms: via-ptr field, direct local field, str/slice pseudo-field, and the two global-field forms. Funnel all five through a shared combine dispatch (cgdotfieldcombine / cg_dotfield_combine) emitting the load-OP-store sequence at field width, hard-erroring the unhandled kinds — close-by- construction so no arm stays on the old PLUSEQ-only path (the #133 BUS-routing lesson; #227 sites A/B are the closed siblings). The refactor routes the corpus's existing +=/-= sites through the same helper output-identically (byte-id held). Review item #34. Both stages move in one commit: one emission contract; splitting the halves would leave the byte-id gates red in between.
This commit is contained in:
105
cmd/w6c/cgen.c
105
cmd/w6c/cgen.c
@@ -3905,6 +3905,89 @@ cg_arrlit_fill_bp(Cg *c, Local **locals, Type *lu, Node *arrlit, int off)
|
||||
}
|
||||
}
|
||||
|
||||
/* cg_dotfield_combine — single-dot field compound combine. The old
|
||||
* field value is in BX, the rhs in AX; the result is left in AX.
|
||||
* PLUSEQ/MINUSEQ preserve the pre-#34 emission (byte-id); the other 8
|
||||
* ops were silently DROPPED (the arm fell through to a plain store of
|
||||
* the rhs -> `s.f = rhs`, #34/#263). SLASHEQ/PERCENTEQ/LSHIFTEQ/RSHIFTEQ
|
||||
* need the lhs in AX and the divisor/count in CX, so swap (rhs AX->CX,
|
||||
* old BX->AX) first. Signed RSHIFTEQ uses SARQ, unsigned SHRQ (#136).
|
||||
* Mirrors wwstage cgdotfieldcombine — both stages emit identical asm. */
|
||||
static void
|
||||
cg_dotfield_combine(Cg *c, int op, int unsignd)
|
||||
{
|
||||
switch (op) {
|
||||
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_MINUSEQ:
|
||||
ins2(c, A_SUBQ, areg(D_AX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
break;
|
||||
case TK_STAREQ: ins2(c, A_IMULQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_AMPEQ: ins2(c, A_ANDQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_PIPEEQ: ins2(c, A_ORQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_CARETEQ: ins2(c, A_XORQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_SLASHEQ:
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
if (unsignd) {
|
||||
ins2(c, A_MOVQ, aimm(0), areg(D_DX));
|
||||
ins1(c, A_DIVQ, areg(D_CX));
|
||||
} else {
|
||||
ins0(c, A_CQO);
|
||||
ins1(c, A_IDIVQ, areg(D_CX));
|
||||
}
|
||||
break;
|
||||
case TK_PERCENTEQ:
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
if (unsignd) {
|
||||
ins2(c, A_MOVQ, aimm(0), areg(D_DX));
|
||||
ins1(c, A_DIVQ, areg(D_CX));
|
||||
} else {
|
||||
ins0(c, A_CQO);
|
||||
ins1(c, A_IDIVQ, areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||
break;
|
||||
case TK_LSHIFTEQ:
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
ins2(c, A_SHLQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
case TK_RSHIFTEQ:
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
ins2(c, unsignd ? A_SHRQ : A_SARQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
default:
|
||||
fatal("single-dot field compound: unknown op tk=%d "
|
||||
"(#34/rule-7)", op);
|
||||
}
|
||||
}
|
||||
|
||||
/* cg_dotfield_hardstop — loud-stop a single-dot field compound on a
|
||||
* non-integer field (float/str/slice/tagged): the combine arm only
|
||||
* speaks integer ABI; pre-#34 these silently became `s.f = rhs`.
|
||||
* Mirrors the wwstage cgdotfieldhardstop gate. (#34/rule-7) */
|
||||
static void
|
||||
cg_dotfield_hardstop(Type *ft)
|
||||
{
|
||||
int isf32 = 0;
|
||||
Type *u = type_chase_named(ft);
|
||||
if (u && u->kind == TY_TAGGED)
|
||||
fatal("single-dot field compound on tagged field not wired "
|
||||
"(#34/rule-7)");
|
||||
if (u && u->kind == TY_STR)
|
||||
fatal("single-dot field compound on str field not wired "
|
||||
"(#34/rule-7)");
|
||||
if (u && u->kind == TY_SLICE)
|
||||
fatal("single-dot field compound on slice field not wired "
|
||||
"(#34/rule-7)");
|
||||
if (fld_isfloat(ft, &isf32))
|
||||
fatal("single-dot field compound on float field not wired "
|
||||
"(#34/rule-7)");
|
||||
}
|
||||
|
||||
static void
|
||||
cgexpr(Cg *c, Node *n, Local *locals)
|
||||
{
|
||||
@@ -4952,16 +5035,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (n->op != TK_ASSIGN) {
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_MINUSEQ:
|
||||
/* old in BX, rhs in AX; want AX = old-rhs.
|
||||
* SUBQ src,dst is dst -= src in Plan 9. */
|
||||
ins2(c, A_SUBQ, areg(D_AX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
cg_dotfield_combine(c, n->op, 0);
|
||||
}
|
||||
if (via_ptr) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
||||
@@ -5226,15 +5300,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cgexpr(c, n->rhs, locals); /* AX = rhs */
|
||||
if (n->op != TK_ASSIGN) {
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); break;
|
||||
case TK_MINUSEQ:
|
||||
/* old in BX, rhs in AX; want AX=old-rhs */
|
||||
ins2(c, A_SUBQ, areg(D_AX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_AX));
|
||||
break;
|
||||
default: break; /* others rare */
|
||||
}
|
||||
cg_dotfield_hardstop(f->type);
|
||||
cg_dotfield_combine(c, n->op, type_isunsigned(f->type));
|
||||
}
|
||||
/* f64/f32 field, plain `=`: cgexpr left the value in
|
||||
* X0, not AX. Route the store via MOVSD/MOVSS.
|
||||
|
||||
Reference in New Issue
Block a user