wcc: indexed-field compound assignment wires all ten ops, both stages

arr[i].field /= %= <<= >>= silently dropped the op (load-combine-store
emitted plain assignment) in BOTH stages — gate-blind, the #133 class.
Route every compound op through the combine dispatch at the indexed-
field arm and hard-error the unhandled operand kinds (float/str/slice/
tagged), per the #133 template (3986818). The runtime-correct target is
the op's own algebra (a OP= b == a = a OP b). Review item #33.

Both stages move in one commit: the fix is a single emission contract —
splitting cstage cgen.c from selfhost cgenexpr.ww would leave the
byte-id gates red between the halves.
This commit is contained in:
2026-06-12 19:26:24 +09:00
parent 02eb867036
commit 1be0e6b5db
6 changed files with 613 additions and 25 deletions

View File

@@ -32720,8 +32720,33 @@ fn cgassign(c: *cgen, n: *node) void = {
// compound: rhs→push; compute struct
// addr→BX (deref if *T); push addr;
// load old field→AX; pop addr→BX,
// rhs→CX; combine; store. Float/str
// compound not wired.
// rhs→CX; combine; store. #33/#263:
// all 10 integer ops wired (was 6 →
// SLASHEQ/PERCENTEQ/LSHIFTEQ/RSHIFTEQ
// silently no-op'd in BOTH stages);
// float/str/slice/tagged field hard-
// errors LOUD. Mirrors cstage cgen.c
// arr[i].field compound twin.
if (istaggedtype(c, fi.tnode)) {
let m: str = "arr[i].field compound on tagged field not wired (#33/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (isstrtype(c, fi.tnode)) {
let m: str = "arr[i].field compound on str field not wired (#33/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (isslicetype(c, fi.tnode)) {
let m: str = "arr[i].field compound on slice field not wired (#33/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
if (isfloattype(c, fi.tnode)) {
let m: str = "arr[i].field compound on float field not wired (#33/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
@@ -32751,12 +32776,41 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline(", AX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tCX\n");
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
let unsignd_x: bool = false;
if (fi.tnode != nil) {
if (fi.tnode.type_ != nil) {
unsignd_x = typeisunsigned(fi.tnode.type_: *tinfo);
};
};
let wired_x: bool = false;
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_SLASHEQ) {
if (unsignd_x) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
wired_x = true;
};
if (n.op == tkind.TK_PERCENTEQ) {
if (unsignd_x) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
emitline("\tMOVQ\tDX, AX\n");
wired_x = true;
};
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired_x = true; };
if (n.op == tkind.TK_RSHIFTEQ) {
if (unsignd_x) { emitline("\tSHRQ\tCX, AX\n"); }
else { emitline("\tSARQ\tCX, AX\n"); };
wired_x = true;
};
if (!wired_x) {
let m: str = "arr[i].field compound: unknown op (#33/rule-7)\n";
os.write(2, m.ptr, m.len: u64);
os.exit(1);
};
let sop2: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop2);