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

@@ -9322,6 +9322,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
else { combineop = "SARQ"; };
};
}; }; }; }; }; }; };
// all 10 compound tokens enumerated above
// (/= %= returned earlier) — a MOVQ
// combineop is the old rhs-plain-store
// legacy fallback. Rule 7.
if (syntax.streq(combineop, "MOVQ")) {
let mdc: str = "deref compound: unknown compound op (rule 7)\n";
os.write(2, mdc.ptr, mdc.len: u64);
os.exit(1);
};
emitline("\t");
emitline(combineop);
emitline("\tCX, AX\n");
@@ -12555,12 +12564,14 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (n.op == syntax.tkind.TK_STAREQ) { fop = mulf; };
if (n.op == syntax.tkind.TK_SLASHEQ) { fop = divf; };
if (fop.len == 0) {
// Unsupported (e.g., %= on float):
// fall back to plain store of rhs.
emitline("\t");
emitline(mov);
emitline("\tX0, (CX)\n");
return;
// 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.
let mfc: str = "float compound: op has no SSE lowering (rule 7)\n";
os.write(2, mfc.ptr, mfc.len: u64);
os.exit(1);
};
emitline("\t");
emitline(mov);
@@ -12722,7 +12733,6 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitline(glop);
emitline("\t(CX), BX\n");
};
let didcompound: bool = true;
if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
else { if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
else { if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }
@@ -12778,19 +12788,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
};
}
else {
// Unsupported compound: store rhs
// directly. Mirrors the local path's
// legacy fallback for unknown ops.
didcompound = false;
emitline("\tMOVQ\tAX, ");
emitsymname(c, nm);
emitline("(SB)\n");
// all 10 compound tokens enumerated
// above (rule 7).
let mgc: str = "global compound: unknown compound op (rule 7)\n";
os.write(2, mgc.ptr, mgc.len: u64);
os.exit(1);
};};};};};};};};};
if (didcompound) {
emitline("\tMOVQ\tBX, ");
emitsymname(c, nm);
emitline("(SB)\n");
};
emitline("\tMOVQ\tBX, ");
emitsymname(c, nm);
emitline("(SB)\n");
return;
};
// #10 Fold B: over-cap tuple reassign `t = f();`. t's
@@ -12978,12 +12984,14 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (n.op == syntax.tkind.TK_STAREQ) { fop = mulf; };
if (n.op == syntax.tkind.TK_SLASHEQ) { fop = divf; };
if (fop.len == 0) {
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff(off: i64);
emitline("(BP)\n");
return;
// 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.
let mfc: str = "float compound: op has no SSE lowering (rule 7)\n";
os.write(2, mfc.ptr, mfc.len: u64);
os.exit(1);
};
emitline("\t");
emitline(mov);
@@ -13156,6 +13164,18 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
emitline("\tMOVQ\tDX, BX\n");
};
};
// all 10 compound tokens enumerated above — an 11th
// stored the untouched loaded value back (silent
// no-op). Rule 7.
if (n.op != syntax.tkind.TK_PLUSEQ && n.op != syntax.tkind.TK_MINUSEQ
&& n.op != syntax.tkind.TK_STAREQ && n.op != syntax.tkind.TK_AMPEQ
&& n.op != syntax.tkind.TK_PIPEEQ && n.op != syntax.tkind.TK_CARETEQ
&& n.op != syntax.tkind.TK_LSHIFTEQ && n.op != syntax.tkind.TK_RSHIFTEQ
&& n.op != syntax.tkind.TK_SLASHEQ && n.op != syntax.tkind.TK_PERCENTEQ) {
let mlc: str = "local compound: unknown compound op (rule 7)\n";
os.write(2, mlc.ptr, mlc.len: u64);
os.exit(1);
};
emitline("\tMOVQ\tBX, ");
emitoff(off: i64);
emitline("(BP)\n");

View File

@@ -3069,6 +3069,15 @@ fn binoptype(c: *checker, e: *syntax.node) *syntax.node = {
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "arithmetic on non-numeric type");
};
// % is integer-only (harec check.c binarithm BIN_MODULO):
// floats have no SSE modulo lowering, so an admitted float %
// fell through cgen half-lowered (cs!=ww divergence).
if (op == syntax.tkind.TK_PERCENT) {
if ((ltn != nil && !intkindast(c, ltn)) ||
(rtn != nil && !intkindast(c, rtn))) {
deffolderr(c, e, "modulo on non-integer type");
};
};
return unifyarith(c, e, ltn, rtn);
};
if (op == syntax.tkind.TK_AMP || op == syntax.tkind.TK_PIPE ||
@@ -6137,6 +6146,34 @@ fn checkassign(c: *checker, n: *syntax.node) void = {
};
let ltn: *syntax.node = exprtype(c, n.lhs, nil);
let rtn: *syntax.node = exprtype(c, n.rhs, nil);
// Compound ops carry their binary op's operand class (harec
// check.c binarithm): += -= *= /= need numeric operands,
// %= modulo-integer, the bitwise/shift five integer. Without
// this `f %= x` and `s += "x"` passed and cgen's fallback
// plain-stored the rhs, silently dropping the operation.
// Mirror cstage cmd/wcc/check.c N_ASSIGN compound gate.
if (n.op != syntax.tkind.TK_ASSIGN) {
if (n.op == syntax.tkind.TK_PLUSEQ || n.op == syntax.tkind.TK_MINUSEQ ||
n.op == syntax.tkind.TK_STAREQ || n.op == syntax.tkind.TK_SLASHEQ) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
cerr("error: arithmetic on non-numeric type\n");
c.errs += 1;
};
} else { if (n.op == syntax.tkind.TK_PERCENTEQ) {
if ((ltn != nil && !intkindast(c, ltn)) ||
(rtn != nil && !intkindast(c, rtn))) {
cerr("error: modulo on non-integer type\n");
c.errs += 1;
};
} else {
if ((ltn != nil && !intkindast(c, ltn)) ||
(rtn != nil && !intkindast(c, rtn))) {
cerr("error: bitwise on non-integer type\n");
c.errs += 1;
};
}; };
};
// #120: `w = 1.0` narrows the rhs literal to the lvalue's f32. Mirror
// cstage cmd/wcc/check.c N_ASSIGN coerce_floatlit.
coercefloatlit(c, n.rhs, ltn);