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.
222 lines
6.6 KiB
C
222 lines
6.6 KiB
C
/*
|
||
* 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;
|
||
}
|