Files
ww/test/wcc/912_sar_shr_run.c
Hojun-Cho 13441c5e2e 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.
2026-05-27 01:29:24 +09:00

222 lines
6.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}