w6a, wcc: add A_SARQ for signed arithmetic right-shift (#136)
Add SAR/SARQ to both assemblers' opcode tables (cstage cmd/w6a +
wwstage selfhost/cmd/w6a) — REX.W + D3 /7, parallel to SHR's D3 /5.
Encoding is the unary-on-CL form (SAR r/m64, CL), the only variant
the cgen emits today. cstage cgen + wwstage cgen sweep all 12 SHRQ
emission sites (6 per stage) so signed RSHIFT and signed RSHIFTEQ
route through SARQ (arithmetic, sign-extends MSB) instead of SHRQ
(logical, zero-fill). Pre-fix `let i: i32 = -200; i >>= 2;`
produced 0x3FFFFFCE (1073741774) instead of -50; cs==ww held because
BOTH stages emitted SHRQ, so the 990-997 byte-id gates were
gate-blind to this silent miscompile.
Sites covered (per stage 6, same shape in both):
- plain TK_RSHIFT (cgbin / N_BIN ordered binop) — derives unsignd
from operand types via type_isunsigned / nodeisunsigned, picks
SHRQ vs SARQ at emit
- chained-ptr-field compound RSHIFTEQ (cgen.c:3281-3317 area)
- N_INDEX-lhs compound RSHIFTEQ (#133-expanded N_INDEX site)
- deref-target compound RSHIFTEQ
- top-level let compound RSHIFTEQ
- IDENT-local compound RSHIFTEQ
All sites reuse the in-scope unsignd variable from the surrounding
SLASHEQ block (or derive one locally when not available). LSHIFTEQ
unchanged — SHL == SAL at the encoder, no signedness dispatch needed.
912_sar_shr_run: 5 rows. i32_neg_rshifteq (lead's repro, was wrong
1073741774 → now -50), i64_neg_rshifteq (wider type), i32_pos_
rshifteq (positive control, SARQ ≡ SHRQ on positives, no regression),
u32_rshifteq (unsigned control, still SHRQ), i32_neg_rshift_binop
(plain >> not compound, cgbin TK_RSHIFT site). Exit codes use small
absolute values with u8 wrap (-50 = 206) per Unix 8-bit exit.
Bootstrap-NEUTRAL — `grep -rE '>>=|>>\b'` in lib/+selfhost/ (excl.
combined.ww) returned zero callers of signed RSHIFT today; the only
asm shifts are on previously-broken paths. 990-997 + combined_ww_
fresh stay green. Closes the silent-misbehavior class on signed
right-shift across all 12 cgen emission paths in one fold per
rule-11. Foundation for Eisel-Lemire (strconv fold-4) big-int signed
shifts.
This commit is contained in:
@@ -3174,8 +3174,12 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
if (n.op == tkind.TK_RSHIFT) {
|
||||
// #136: signed RSHIFT → SAR (arithmetic, sign-extends MSB);
|
||||
// unsigned → SHR (logical, zero-fill). `unsignd` derived above
|
||||
// at cgbin head from nodeisunsigned(lhs) || nodeisunsigned(rhs).
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tSHRQ\tCX, AX\n");
|
||||
if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
return;
|
||||
};
|
||||
// TK_AND / TK_OR handled with short-circuit codegen at the top of
|
||||
@@ -4151,14 +4155,16 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// signed arm and MOVQ-zero/DIVQ on the unsigned
|
||||
// arm. Pre-fix the default branch silently stored
|
||||
// rhs into *p (combineop = MOVQ shape).
|
||||
// #136: lift unsignd above the SLASHEQ block so
|
||||
// RSHIFTEQ can route SHRQ vs SARQ on the same key.
|
||||
let unsignd: bool = false;
|
||||
if (pe != nil) {
|
||||
unsignd = typeisunsigned(pe.type_: *tinfo);
|
||||
};
|
||||
if (!unsignd) {
|
||||
unsignd = nodeisunsigned(c, n.rhs);
|
||||
};
|
||||
if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) {
|
||||
let unsignd: bool = false;
|
||||
if (pe != nil) {
|
||||
unsignd = typeisunsigned(pe.type_: *tinfo);
|
||||
};
|
||||
if (!unsignd) {
|
||||
unsignd = nodeisunsigned(c, n.rhs);
|
||||
};
|
||||
if (unsignd) {
|
||||
emitline("\tMOVQ\t$0, DX\n");
|
||||
emitline("\tDIVQ\tCX\n");
|
||||
@@ -4182,7 +4188,11 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
else { if (n.op == tkind.TK_PIPEEQ) { combineop = "ORQ"; }
|
||||
else { if (n.op == tkind.TK_CARETEQ) { combineop = "XORQ"; }
|
||||
else { if (n.op == tkind.TK_LSHIFTEQ) { combineop = "SHLQ"; }
|
||||
else { if (n.op == tkind.TK_RSHIFTEQ) { combineop = "SHRQ"; };
|
||||
else { if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
// #136: signed RSHIFTEQ → SARQ.
|
||||
if (unsignd) { combineop = "SHRQ"; }
|
||||
else { combineop = "SARQ"; };
|
||||
};
|
||||
}; }; }; }; }; }; };
|
||||
emitline("\t");
|
||||
emitline(combineop);
|
||||
@@ -4561,10 +4571,9 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
// #133-expanded: all 10 integer compound ops wired.
|
||||
// SLASHEQ/PERCENTEQ: CQO+IDIVQ (signed) or zero-DX+
|
||||
// DIVQ (unsigned). LSHIFTEQ/RSHIFTEQ: SHLQ/SHRQ via
|
||||
// CX. Signedness from elemtn.type_; signed RSHIFTEQ
|
||||
// uses SHRQ (cstage parity — A_SARQ not in w6a,
|
||||
// pre-existing signed-RSHIFT concern out of scope).
|
||||
// DIVQ (unsigned). LSHIFTEQ via SHLQ; RSHIFTEQ via
|
||||
// SARQ (signed) or SHRQ (unsigned) per #136.
|
||||
// Signedness from elemtn.type_.
|
||||
let unsignd_c: bool = false;
|
||||
if (elemtn != nil) {
|
||||
if (elemtn.type_ != nil) {
|
||||
@@ -4590,7 +4599,11 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) { emitline("\tSHRQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
if (unsignd_c) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (!wired) {
|
||||
let msg: str = "indexed-lvalue compound: unknown compound op (#133/rule-7)\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
@@ -5679,7 +5692,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// All 10 integer compound ops wired;
|
||||
// float/str/slice/tagged field-type
|
||||
// hard-errors LOUD. Signed RSHIFTEQ uses
|
||||
// SHRQ (cstage parity, A_SARQ absent).
|
||||
// SARQ (signed) or SHRQ (unsigned) per #136.
|
||||
if (n.op != tkind.TK_ASSIGN) {
|
||||
if (typeisstr(ft)) {
|
||||
let m: str = "chained-ptr-field compound on str element not wired (#133/rule-7)\n";
|
||||
@@ -5734,7 +5747,11 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
wired_f = true;
|
||||
};
|
||||
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired_f = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) { emitline("\tSHRQ\tCX, AX\n"); wired_f = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
if (unsignd_f) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
wired_f = true;
|
||||
};
|
||||
if (!wired_f) {
|
||||
let m: str = "chained-ptr-field compound: unknown op (#133/rule-7)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
@@ -6373,8 +6390,19 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tSHLQ\tCX, BX\n");
|
||||
}
|
||||
else { if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
// #136: signed RSHIFTEQ → SARQ.
|
||||
let unsignd_r: bool = false;
|
||||
if (lvftn != nil) {
|
||||
if (lvftn.type_ != nil) {
|
||||
unsignd_r = typeisunsigned(lvftn.type_: *tinfo);
|
||||
};
|
||||
};
|
||||
if (!unsignd_r) {
|
||||
unsignd_r = nodeisunsigned(c, n.rhs);
|
||||
};
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHRQ\tCX, BX\n");
|
||||
if (unsignd_r) { emitline("\tSHRQ\tCX, BX\n"); }
|
||||
else { emitline("\tSARQ\tCX, BX\n"); };
|
||||
}
|
||||
// Post-63332fe: /= and %= for a top-level
|
||||
// let. Same shape as the IDENT-local path:
|
||||
@@ -6658,8 +6686,21 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tSHLQ\tCX, BX\n");
|
||||
};
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
// #136: signed RSHIFTEQ → SARQ.
|
||||
let unsignd_r: bool = false;
|
||||
if (lcn != nil) {
|
||||
if (lcn.tnode != nil) {
|
||||
if (lcn.tnode.type_ != nil) {
|
||||
unsignd_r = typeisunsigned(lcn.tnode.type_: *tinfo);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!unsignd_r) {
|
||||
unsignd_r = nodeisunsigned(c, n.rhs);
|
||||
};
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tSHRQ\tCX, BX\n");
|
||||
if (unsignd_r) { emitline("\tSHRQ\tCX, BX\n"); }
|
||||
else { emitline("\tSARQ\tCX, BX\n"); };
|
||||
};
|
||||
// Post-63332fe: /= and %= for an IDENT local. Pre-fix
|
||||
// fell through with no case, so BX (still holding the
|
||||
|
||||
Reference in New Issue
Block a user