diff --git a/Makefile b/Makefile index f4cd44ce..122abc50 100644 --- a/Makefile +++ b/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 $@ $< diff --git a/cmd/w6a/asm.c b/cmd/w6a/asm.c index 2c0a5b16..035a95cb 100644 --- a/cmd/w6a/asm.c +++ b/cmd/w6a/asm.c @@ -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); diff --git a/cmd/w6a/parse.c b/cmd/w6a/parse.c index 4d516a80..66229c80 100644 --- a/cmd/w6a/parse.c +++ b/cmd/w6a/parse.c @@ -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 }, diff --git a/cmd/w6c/6.out.h b/cmd/w6c/6.out.h index 7ca02c61..f4ad071d 100644 --- a/cmd/w6c/6.out.h +++ b/cmd/w6c/6.out.h @@ -93,6 +93,7 @@ enum { A_XORQ, A_SHLQ, A_SHRQ, + A_SARQ, A_CMPQ, A_PUSHQ, diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 865d2eaf..cb01191e 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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 diff --git a/cmd/w6c/txt.c b/cmd/w6c/txt.c index 2cfef33c..0942dae7 100644 --- a/cmd/w6c/txt.c +++ b/cmd/w6c/txt.c @@ -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"; diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index b2bbe433..cf9d6953 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -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; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 12bfd7c3..fff171ac 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -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; diff --git a/selfhost/cmd/w6a/opcodes.ww b/selfhost/cmd/w6a/opcodes.ww index a7ce82a8..615b6887 100644 --- a/selfhost/cmd/w6a/opcodes.ww +++ b/selfhost/cmd/w6a/opcodes.ww @@ -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; diff --git a/selfhost/cmd/w6a/parse.ww b/selfhost/cmd/w6a/parse.ww index 145b9ea4..7207147e 100644 --- a/selfhost/cmd/w6a/parse.ww +++ b/selfhost/cmd/w6a/parse.ww @@ -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; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 555078c0..fbf0e47a 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 710cb982..6a46dbd2 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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 diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 5dbb5d3d..2c2a85a6 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 diff --git a/test/wcc/912_sar_shr_run.c b/test/wcc/912_sar_shr_run.c new file mode 100644 index 00000000..6a898794 --- /dev/null +++ b/test/wcc/912_sar_shr_run.c @@ -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 +#include +#include +#include +#include +#include + +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; +}