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

@@ -762,6 +762,12 @@ a_encode(Asm *a)
encode_unary(a, 0xD3, 4, p->to.type); break;
case A_SHRQ:
encode_unary(a, 0xD3, 5, p->to.type); break;
case A_SARQ:
/* SAR r/m64, CL: REX.W + D3 /7 (arithmetic right shift,
* sign-extends MSB — distinct from SHR D3 /5 which
* injects zeros). Same encoding shape; only modrm.reg
* digit differs. #136. */
encode_unary(a, 0xD3, 7, p->to.type); break;
case A_CMPQ:
if (p->from.type == D_CONST && p->to.type >= D_AX && p->to.type <= D_R15)
encode_ri_imm32(a, 0x81, 7, p->to.type, (i32)p->from.offset);

View File

@@ -107,7 +107,7 @@ opcode_lookup(const char *m)
{ "NEGQ", A_NEGQ },{ "NOTQ", A_NOTQ },
{ "ANDQ", A_ANDQ },{ "ORQ", A_ORQ },
{ "XORQ", A_XORQ },
{ "SHLQ", A_SHLQ },{ "SHRQ", A_SHRQ },
{ "SHLQ", A_SHLQ },{ "SHRQ", A_SHRQ },{ "SARQ", A_SARQ },
{ "CMPQ", A_CMPQ },
{ "PUSHQ",A_PUSHQ},{ "POPQ", A_POPQ },
{ "LEAQ", A_LEAQ },

View File

@@ -93,6 +93,7 @@ enum {
A_XORQ,
A_SHLQ,
A_SHRQ,
A_SARQ,
A_CMPQ,
A_PUSHQ,

View File

@@ -2586,12 +2586,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
case TK_AMP: ins2(c, A_ANDQ, areg(D_BX), areg(D_AX)); break;
case TK_PIPE: ins2(c, A_ORQ, areg(D_BX), areg(D_AX)); break;
case TK_CARET: ins2(c, A_XORQ, areg(D_BX), areg(D_AX)); break;
case TK_LSHIFT: case TK_RSHIFT:
/* shift amount must be in CL */
case TK_LSHIFT: case TK_RSHIFT: {
/* shift amount must be in CL. #136: signed RSHIFT uses
* SAR (arithmetic, sign-extends MSB); unsigned uses SHR
* (logical, zero-fill). LSHIFT is signedness-agnostic
* (SHL == SAL at the encoder). */
int unsignd = (n->lhs && type_isunsigned(n->lhs->type))
|| (n->rhs && type_isunsigned(n->rhs->type));
int rop = unsignd ? A_SHRQ : A_SARQ;
ins2(c, A_MOVQ, areg(D_BX), areg(D_CX));
ins2(c, n->op == TK_LSHIFT ? A_SHLQ : A_SHRQ,
ins2(c, n->op == TK_LSHIFT ? A_SHLQ : rop,
areg(D_CX), areg(D_AX));
break;
}
case TK_EQ: case TK_NEQ: case TK_LT: case TK_LE:
case TK_GT: case TK_GE: {
/* For ordered comparisons on unsigned operands we must
@@ -3343,15 +3350,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
* combine; store. #133-expanded: all 10 integer
* compound ops wired; SLASHEQ/PERCENTEQ via
* CQO+IDIV (signed) or zero-DX+DIV (unsigned);
* LSHIFTEQ/RSHIFTEQ via SHLQ/SHRQ on CX. Float /
* str / slice / tagged element compound hard-
* errors LOUD (rule-7 — replaces prior silent
* fall-through-to-default-break). Note: A_SARQ
* isn't in w6a today; signed RSHIFTEQ uses SHRQ
* for parity with the pre-existing deref-lvalue
* compound site (TK_RSHIFTEQ→A_SHRQ below);
* signed-RSHIFT-on-negatives separate concern,
* filed as #136, out of scope for #133. */
* LSHIFTEQ via SHLQ on CX; RSHIFTEQ via SARQ
* (signed) or SHRQ (unsigned) on CX per #136.
* Float / str / slice / tagged element compound
* hard-errors LOUD (rule-7). */
{
int compound_isf32 = 0;
if (fld_isfloat(ft, &compound_isf32))
@@ -3429,8 +3431,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_AX));
break;
case TK_RSHIFTEQ:
ins2(c, A_SHRQ, areg(D_CX),
areg(D_AX));
ins2(c, unsignd ? A_SHRQ : A_SARQ,
areg(D_CX), areg(D_AX));
break;
default:
fatal("chained-ptr-field compound: "
@@ -3992,11 +3994,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
* #133-expanded: all 10 integer compound ops wired;
* float/str/slice/tagged element compound HARD-ERRORS
* loud (rule-7, replaces prior silent fall-through).
* Signed RSHIFTEQ uses SHRQ — parity with the pre-
* existing deref-lvalue compound site (TK_RSHIFTEQ→
* A_SHRQ below). A_SARQ not in w6a; pre-existing
* signed-RSHIFT-on-negatives is filed as #136, out of
* scope for #133. */
* #136: signed RSHIFTEQ now uses A_SARQ (arithmetic
* shift). */
if ((is_arr || is_sl || is_ptr) && n->op != TK_ASSIGN) {
if (elem_is_str)
fatal("indexed-lvalue compound on "
@@ -4099,8 +4098,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_AX));
break;
case TK_RSHIFTEQ:
ins2(c, A_SHRQ, areg(D_CX),
areg(D_AX));
ins2(c, unsignd_c ? A_SHRQ : A_SARQ,
areg(D_CX), areg(D_AX));
break;
default:
fatal("indexed-lvalue compound: "
@@ -4213,7 +4212,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
case TK_PIPEEQ: ins2(c, A_ORQ, areg(D_CX), areg(D_AX)); break;
case TK_CARETEQ: ins2(c, A_XORQ, areg(D_CX), areg(D_AX)); break;
case TK_LSHIFTEQ: ins2(c, A_SHLQ, areg(D_CX), areg(D_AX)); break;
case TK_RSHIFTEQ: ins2(c, A_SHRQ, areg(D_CX), areg(D_AX)); break;
case TK_RSHIFTEQ: {
/* #136: signed RSHIFTEQ → SARQ. */
int unsignd = (vt && type_isunsigned(vt))
|| (n->rhs && type_isunsigned(n->rhs->type));
ins2(c, unsignd ? A_SHRQ : A_SARQ,
areg(D_CX), areg(D_AX));
break;
}
case TK_SLASHEQ:
case TK_PERCENTEQ: {
int unsignd = (vt && type_isunsigned(vt))
@@ -4383,10 +4389,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
ins2(c, A_SHLQ, areg(D_CX), areg(D_BX));
break;
case TK_RSHIFTEQ:
case TK_RSHIFTEQ: {
/* #136: signed RSHIFTEQ → SARQ. */
int unsignd_r = (n->lhs && type_isunsigned(n->lhs->type))
|| (n->rhs && type_isunsigned(n->rhs->type));
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
ins2(c, A_SHRQ, areg(D_CX), areg(D_BX));
ins2(c, unsignd_r ? A_SHRQ : A_SARQ,
areg(D_CX), areg(D_BX));
break;
}
case TK_SLASHEQ:
case TK_PERCENTEQ: {
/* Sister site of the IDENT-local path
@@ -4458,10 +4469,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
ins2(c, A_SHLQ, areg(D_CX), areg(D_BX));
break;
case TK_RSHIFTEQ:
case TK_RSHIFTEQ: {
/* #136: signed RSHIFTEQ → SARQ. */
int unsignd_r = (n->lhs && type_isunsigned(n->lhs->type))
|| (n->rhs && type_isunsigned(n->rhs->type));
ins2(c, A_MOVQ, areg(D_AX), areg(D_CX));
ins2(c, A_SHRQ, areg(D_CX), areg(D_BX));
ins2(c, unsignd_r ? A_SHRQ : A_SARQ,
areg(D_CX), areg(D_BX));
break;
}
case TK_SLASHEQ:
case TK_PERCENTEQ: {
/* IDIV/DIV needs dividend in RDX:RAX, divisor

View File

@@ -69,6 +69,7 @@ anames(int op)
case A_XORQ: return "XORQ";
case A_SHLQ: return "SHLQ";
case A_SHRQ: return "SHRQ";
case A_SARQ: return "SARQ";
case A_CMPQ: return "CMPQ";
case A_PUSHQ: return "PUSHQ";
case A_POPQ: return "POPQ";