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:
6
Makefile
6
Makefile
@@ -334,6 +334,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_idx_compound_run \
|
||||
$(BIN)/test_dotbase_arr_run \
|
||||
$(BIN)/test_continue_run \
|
||||
$(BIN)/test_sar_shr_run \
|
||||
$(BIN)/test_f64cgen_run \
|
||||
$(BIN)/test_f64crossmod_run \
|
||||
$(BIN)/test_tuprecv_run \
|
||||
@@ -1117,6 +1118,11 @@ $(BIN)/test_continue_run: test/wcc/911_continue_run.c $(BIN)/ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_sar_shr_run: test/wcc/912_sar_shr_run.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_f64cgen_run: test/wcc/951_f64cgen_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -93,6 +93,7 @@ enum {
|
||||
A_XORQ,
|
||||
A_SHLQ,
|
||||
A_SHRQ,
|
||||
A_SARQ,
|
||||
A_CMPQ,
|
||||
|
||||
A_PUSHQ,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -735,6 +735,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -126,6 +126,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;
|
||||
|
||||
|
||||
@@ -71,6 +71,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; };
|
||||
|
||||
@@ -17786,8 +17786,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
|
||||
@@ -18763,14 +18767,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");
|
||||
@@ -18794,7 +18800,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);
|
||||
@@ -19173,10 +19183,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) {
|
||||
@@ -19202,7 +19211,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);
|
||||
@@ -20291,7 +20304,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";
|
||||
@@ -20346,7 +20359,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);
|
||||
@@ -20985,8 +21002,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:
|
||||
@@ -21270,8 +21298,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17786,8 +17786,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
|
||||
@@ -18763,14 +18767,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");
|
||||
@@ -18794,7 +18800,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);
|
||||
@@ -19173,10 +19183,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) {
|
||||
@@ -19202,7 +19211,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);
|
||||
@@ -20291,7 +20304,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";
|
||||
@@ -20346,7 +20359,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);
|
||||
@@ -20985,8 +21002,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:
|
||||
@@ -21270,8 +21298,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
|
||||
|
||||
221
test/wcc/912_sar_shr_run.c
Normal file
221
test/wcc/912_sar_shr_run.c
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 912_sar_shr_run — runtime + byte-id net for #136: signed right-shift
|
||||
* (both plain `>>` and `>>=`) must use SAR (arithmetic, sign-extends
|
||||
* MSB), not SHR (logical, zero-fill). Pre-fix BOTH stages emitted SHRQ
|
||||
* for signed RSHIFT because A_SARQ wasn't in the w6a opcode table, so
|
||||
* `let i: i32 = -200; i >>= 2;` produced i = 0x3FFFFFCE (1073741774)
|
||||
* instead of -50. cs==ww held → gate-blind. #133-expanded shipped with
|
||||
* this pre-existing concern documented (parity with cgen.c:4145 deref-
|
||||
* lvalue compound site); #136 closes it.
|
||||
*
|
||||
* Fix: add A_SARQ to w6a + w6c + w6a_ww opcode tables (encoding REX.W
|
||||
* D3 /7, parallel to SHR's D3 /5), then sweep cgen sites in both
|
||||
* stages to dispatch SARQ vs SHRQ on the operand signedness. Sites
|
||||
* covered (per-stage 6 each):
|
||||
* - plain `>>` binop (cgbin TK_RSHIFT)
|
||||
* - chained-ptr-field compound RSHIFTEQ (#133-expanded site 1)
|
||||
* - N_INDEX-lhs compound RSHIFTEQ (#133-expanded site 2)
|
||||
* - deref-target compound RSHIFTEQ (pre-existing)
|
||||
* - top-level let compound RSHIFTEQ
|
||||
* - IDENT-local compound RSHIFTEQ
|
||||
*
|
||||
* The bootstrap audit at design time showed zero current signed-RSHIFT
|
||||
* callers in lib/+selfhost/, so 990-997 byte-id stays GREEN; the only
|
||||
* shifts are on the previously-broken paths.
|
||||
*
|
||||
* Rows cover the operator + signedness × storage matrix:
|
||||
* - i32_neg_rshifteq: lead's repro pattern, -200 >>= 2 = -50 (was
|
||||
* 1073741774 pre-fix on SHRQ)
|
||||
* - i64_neg_rshifteq: same shape, i64
|
||||
* - i32_pos_rshifteq: control, SARQ and SHRQ produce same result on
|
||||
* positives; asserts no regression
|
||||
* - u32_rshifteq: unsigned control, must still use SHRQ (no change)
|
||||
* - i32_neg_rshift_binop: plain `>>` (not compound) on negative i32,
|
||||
* covers the cgen 2596 / 3176 TK_RSHIFT N_BIN site
|
||||
*
|
||||
* Each row carries (a) cstage `ww build` + run asserting the exit
|
||||
* code and (b) w6c vs w6c_ww `.s` cmp (rule-10 byte-id). Exit codes
|
||||
* use small absolute values + 256 to encode signed expectations:
|
||||
* -50 → 206 = 256-50 (Unix exit is 8-bit unsigned). i64 row clamped
|
||||
* similarly.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want_exit; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* The #136 repro: signed i32 >>=. Pre-fix produced wrong large
|
||||
* positive; post-fix produces -50. Exit code = -50 as u8 = 206. */
|
||||
{ "i32_neg_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = -200;\n"
|
||||
" i >>= 2;\n"
|
||||
" return i;\n"
|
||||
"};\n", 206 },
|
||||
/* signed i64 >>=. Same shape, wider type. Result -50 as exit u8
|
||||
* = 206. */
|
||||
{ "i64_neg_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i64 = -200i64;\n"
|
||||
" i >>= 2i64;\n"
|
||||
" return i: i32;\n"
|
||||
"};\n", 206 },
|
||||
/* Positive control: SARQ and SHRQ agree. 200 >> 2 = 50. */
|
||||
{ "i32_pos_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = 200;\n"
|
||||
" i >>= 2;\n"
|
||||
" return i;\n"
|
||||
"};\n", 50 },
|
||||
/* Unsigned control: SHRQ unchanged. 200u32 >> 2 = 50. */
|
||||
{ "u32_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: u32 = 200u32;\n"
|
||||
" i >>= 2u32;\n"
|
||||
" return i: i32;\n"
|
||||
"};\n", 50 },
|
||||
/* Plain `>>` (not compound) on signed negative: covers cgbin
|
||||
* TK_RSHIFT site (cgen.c:2596 / cgenexpr.ww:3176). */
|
||||
{ "i32_neg_rshift_binop",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = -200;\n"
|
||||
" let r: i32 = i >> 2;\n"
|
||||
" return r;\n"
|
||||
"};\n", 206 },
|
||||
{ NULL, NULL, 0 }
|
||||
};
|
||||
|
||||
static int
|
||||
slurp_eq(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb");
|
||||
FILE *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||
int rc = 0;
|
||||
for (;;) {
|
||||
int ca = fgetc(fa);
|
||||
int cb = fgetc(fb);
|
||||
if (ca != cb) { rc = -1; break; }
|
||||
if (ca == EOF) break;
|
||||
}
|
||||
fclose(fa); fclose(fb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char w6c[1100], w6c_ww[1100];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
if (access(w6c_ww, X_OK) != 0) {
|
||||
fprintf(stderr, "sarshr: w6c_ww missing — cannot run the "
|
||||
"cs==ww byte-id gate (the whole point of this test)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char src[64];
|
||||
snprintf(src, sizeof src, "/tmp/wwsar_%d_%d.ww", getpid(), i);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; continue; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char tmpdir[64];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsar_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||||
tmpdir, bin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
unlink(src); rmdir(tmpdir);
|
||||
continue;
|
||||
}
|
||||
|
||||
char outbin[128];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
unlink(outbin); rmdir(tmpdir);
|
||||
|
||||
char cs_s[64], ws_s[64];
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/wwsar_%d_%d_cs.s",
|
||||
getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "/tmp/wwsar_%d_%d_ww.s",
|
||||
getpid(), i);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c, cs_s, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
|
||||
fail++; unlink(src); continue;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c_ww, ws_s, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww failed\n",
|
||||
rows[i].label);
|
||||
fail++; unlink(src); unlink(cs_s); continue;
|
||||
}
|
||||
if (slurp_eq(cs_s, ws_s) != 0) {
|
||||
fprintf(stderr,
|
||||
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
|
||||
"byte-id violation)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d sar/shr tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("sarshr: %d/%d ok (cstage run + cs==ww byte-id)\n",
|
||||
n, n);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user