wcc: modulo is integer-only; compound ops carry their operand class

Hare's rule (harec check.c binarithm): % and the bitwise/shift five
are integer-only; + - * / need numeric operands. ww grouped % with
the numeric ops, and compound assigns never op-checked at all, so
`a % b` on floats compiled half-lowered (live cs!=ww divergence),
`a %= 2.0` plain-stored the rhs (op silently dropped, both stages),
and `s += "cd"` garbled str headers. Gate both at the checker, both
stages; the cgen float-compound fallbacks and the three unknown-
compound legacy defaults (deref/global/local) demote to rule-7 hard
stops. 34 compound-on-tagged/str/slice fixtures re-pin from the old
cgen "not wired" stops to the earlier checker diagnostics; 3 new
reject fixtures pin the closed shapes.
This commit is contained in:
2026-08-09 00:32:41 +09:00
parent f0029227b5
commit f191e6e0e2
42 changed files with 206 additions and 93 deletions

View File

@@ -7041,25 +7041,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
case TK_SLASHEQ: fop = divop; break;
default: break;
}
/* Non-SSE compound on a float lvalue is checker-
* rejected (modulo/bitwise integer-only); a survivor
* here means a checker gap — loud, never the old
* plain-store of rhs that dropped the op. */
if (fop < 0)
fatal("float compound: op has no SSE "
"lowering (rule 7)");
if (off != 0) {
if (fop < 0) {
/* Unsupported compound (e.g., %= on float):
* fall back to plain store of rhs. */
ins2(c, mvop, areg(D_X0),
amem(D_BP, off));
break;
}
ins2(c, mvop, amem(D_BP, off), areg(D_X1));
ins2(c, fop, areg(D_X0), areg(D_X1));
ins2(c, mvop, areg(D_X1), amem(D_BP, off));
} else {
ins2(c, A_LEAQ, masym(c, n->lhs->str),
areg(D_CX));
if (fop < 0) {
ins2(c, mvop, areg(D_X0),
amem(D_CX, 0));
break;
}
ins2(c, mvop, amem(D_CX, 0), areg(D_X1));
ins2(c, fop, areg(D_X0), areg(D_X1));
ins2(c, mvop, areg(D_X1), amem(D_CX, 0));
@@ -7921,10 +7916,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
default:
/* unknown compound: legacy fallback —
* store rhs only. */
ins2(c, A_MOVQ, areg(D_CX), areg(D_AX));
break;
/* all 10 compound tokens enumerated
* above — an 11th means a parser/
* checker gap, never a plain store
* of rhs (rule 7). */
fatal("deref compound: unknown "
"compound op (rule 7)");
}
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
break;
@@ -8364,7 +8361,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, glop, amem(D_CX, 0),
areg(D_BX));
}
int did_compound = 1;
switch (n->op) {
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
case TK_MINUSEQ: ins2(c, A_SUBQ, areg(D_AX), areg(D_BX)); break;
@@ -8410,16 +8406,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
default:
/* unknown compound: legacy fallback —
* store rhs only. */
did_compound = 0;
ins2(c, A_MOVQ, areg(D_AX),
masym(c, n->lhs->str));
break;
/* all 10 compound tokens enumerated
* above (rule 7). */
fatal("global compound: unknown "
"compound op (rule 7)");
}
if (did_compound)
ins2(c, A_MOVQ, areg(D_BX),
masym(c, n->lhs->str));
ins2(c, A_MOVQ, areg(D_BX),
masym(c, n->lhs->str));
break;
}
cgexpr(c, n->rhs, locals);
@@ -8490,12 +8483,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
default:
/* unknown: just store rhs (legacy fallback) */
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
goto skip_assign_store;
/* all 10 compound tokens enumerated above
* (rule 7). */
fatal("local compound: unknown compound op "
"(rule 7)");
}
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off));
skip_assign_store: ;
}
/* C1 residual (task #22): a non-DOT lvalue no arm above
* matched still falls out SILENT here — the known member is