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:
2026-05-27 01:29:24 +09:00
parent 90d31c5b41
commit 13441c5e2e
14 changed files with 471 additions and 81 deletions

View File

@@ -2832,6 +2832,10 @@ def A_JNZ: i32 = 58;
// A_SYSCALL/A_DATAW/A_DATAR/A_CQO numbers stay put. Jump on
// parity (PF=1): UCOMISD unordered (#97).
def A_JP: i32 = 67;
// #136: arithmetic right-shift, sign-extends MSB. SHR injects
// zeros and is wrong for signed operands; cgen routes signed
// `>>` / `>>=` through SAR after this opcode landed.
def A_SARQ: i32 = 68;
def A_SYSCALL: i32 = 59;
@@ -3068,6 +3072,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "XORQ")) { return A_XORQ; };
if (streqlit(p, n, "SHLQ")) { return A_SHLQ; };
if (streqlit(p, n, "SHRQ")) { return A_SHRQ; };
if (streqlit(p, n, "SARQ")) { return A_SARQ; };
if (streqlit(p, n, "CMPQ")) { return A_CMPQ; };
if (streqlit(p, n, "PUSHQ")) { return A_PUSHQ; };
if (streqlit(p, n, "POPQ")) { return A_POPQ; };
@@ -4320,6 +4325,9 @@ export fn encode(a: *asm_) i32 = {
};
if (op == A_SHLQ) { encodeunary(a, 211u8, 4, p.to.atype); p = p.link; continue; }; // 0xD3
if (op == A_SHRQ) { encodeunary(a, 211u8, 5, p.to.atype); p = p.link; continue; };
// #136: SAR r/m64, CL — REX.W + D3 /7 (arithmetic right
// shift, sign-extends MSB; cstage twin cmd/w6a/asm.c).
if (op == A_SARQ) { encodeunary(a, 211u8, 7, p.to.atype); p = p.link; continue; };
if (op == A_CMPQ) {
let ft: i32 = p.from.atype;
let tt: i32 = p.to.atype;