B1 (63332fe) landed CQO in both stages' assemblers and switched the
binary `/` and `%` paths to it. The compound-assign sisters (`/=`,
`%=`) were six explicit workarounds across both stages, all calling
out either "fallback for TK_SLASHEQ" or just falling through with no
case at all. With CQO available, every site mechanically ports to the
same "park rhs in CX, slot value into AX, CQO/IDIVQ CX, ferry result
back" sequence.
wwstage cgenexpr.ww:5381-5403 silently no-op'd IDENT-local signed
compound div/mod — `x /= y` and `x %= y` produced no IDIV emit at
all, just a load-bearing `MOVQ BX, off(BP)` that wrote the freshly
loaded slot value back unchanged. Bootstrap byte-id passed because
no selfhost-corpus path exercises signed compound. Latent miscompile
retired alongside the workaround revert.
cstage cgen.c:3735 (top-level-let global compound) was NOT in the
initial five-site bundle and surfaced via worker probing the
wwstage:5147 fix — `let gs: i32 = 100; gs /= 7;` returned 7 (divisor)
on cstage but 14 (correct quotient) on wwstage. Rule 10 caught the
would-be Class A divergence; the sixth site bundles in.
Six sites, one family:
cmd/w6c/cgen.c:3549 deref-compound `*p OP= v`
cmd/w6c/cgen.c:3735 top-level-let `gs OP= v`
cmd/w6c/cgen.c:3765 IDENT-local `x OP= v`
selfhost/cmd/wcc/cgenexpr.ww:3338 deref-compound
selfhost/cmd/wcc/cgenexpr.ww:5147 top-level-let
selfhost/cmd/wcc/cgenexpr.ww:5381 IDENT-local (silent-no-op)
test/wcc/978_intdiv_signed.c adds 7 compound rows × 2 drivers = 14
fixtures (now 68/68): IDENT-local /= /=- /=u, deref *p /= *p %= *p
/=u, with negative-dividend, negative-divisor, and unsigned-high-
bit-set coverage. Top-level-let compound coverage is deferred per
task #18 — single-file inline drivers hit a pre-existing linker
`undefined reference to '<file>.gs'` for LEAQ name(SB) targets.
cstage:3735 and wwstage:5147 are code-review-verified for rule-10
symmetry until #18 lands.
Grep-sweep (`if (n < 0) { neg = true; n = -n; }`) returned two sites
in lib/fmt/fmt.ww i64dec and lib/strconv/strconv.ww — both mirror
ref/hare/strconv/itos.ha's pre-negate idiom for INT64_MIN safety.
Per the Hare-faithful filter, both stay.
463 lines
13 KiB
C
463 lines
13 KiB
C
/*
|
||
* 978_intdiv_signed — runtime semantics of integer `/` and `%` across
|
||
* the {i8,i16,i32,i64,u8,u16,u32,u64} × {/, %} matrix, plus the
|
||
* width-boundary minima.
|
||
*
|
||
* Class B (shared miscompile, not divergence): both cstage and
|
||
* wwstage previously emitted `MOVQ $0, DX + IDIVQ` on the signed
|
||
* arm, treating a negative dividend as a huge unsigned 128-bit
|
||
* value. Bootstrap byte-id passed throughout — both stages stomped
|
||
* the same way — so only a semantic runtime test catches it. The
|
||
* fix swaps the prep to `CQO` (sign-extend RAX into RDX:RAX) on
|
||
* the signed arm of TK_SLASH/TK_PERCENT; unsigned stays MOVQ-zero +
|
||
* DIVQ. See cmd/w6c/cgen.c TK_SLASH/TK_PERCENT and the matching
|
||
* selfhost/cmd/wcc/cgenexpr.ww branches.
|
||
*
|
||
* Unsigned rows pin the DIVQ arm against future regression — the
|
||
* cgen.c comment from #41 (high-bit-set u64 / 2) is exactly the
|
||
* shape that the unsigned-vs-signed dispatch protects.
|
||
*
|
||
* Same dual-driver runner as 640_int_cast_signed.c: each row gets
|
||
* compiled by both cstage `ww` and wwstage `ww_ww` (when built),
|
||
* with the binary exit code carrying the per-case verdict (42 = ok).
|
||
*/
|
||
#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; };
|
||
|
||
static const struct row rows[] = {
|
||
/* ---- i64 signed: negative dividend, positive divisor ---------- */
|
||
{ "i64_div_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = -100i64;\n"
|
||
" let b: i64 = 1000000000i64;\n"
|
||
" if (a / b == 0i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i64_mod_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = -100i64;\n"
|
||
" let b: i64 = 1000000000i64;\n"
|
||
" if (a % b == -100i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
/* ---- i64 signed: positive dividend, negative divisor ---------- */
|
||
{ "i64_div_neg_divisor",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = 50i64;\n"
|
||
" let b: i64 = -3i64;\n"
|
||
" if (a / b == -16i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i64_mod_neg_divisor",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = 50i64;\n"
|
||
" let b: i64 = -3i64;\n"
|
||
" if (a % b == 2i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
/* ---- i64 signed: both negative -------------------------------- */
|
||
{ "i64_div_both_negative",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = -50i64;\n"
|
||
" let b: i64 = -3i64;\n"
|
||
" if (a / b == 16i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i64_mod_both_negative",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = -50i64;\n"
|
||
" let b: i64 = -3i64;\n"
|
||
" if (a % b == -2i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
/* ---- i64 signed: both positive (sanity / regression anchor) --- */
|
||
{ "i64_div_both_positive",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = 50i64;\n"
|
||
" let b: i64 = 3i64;\n"
|
||
" if (a / b == 16i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i64_mod_both_positive",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = 50i64;\n"
|
||
" let b: i64 = 3i64;\n"
|
||
" if (a % b == 2i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
/* ---- i64 boundary: INT64_MIN ---------------------------------- *
|
||
* INT64_MIN / -1 would SIGFPE (signed overflow); /2 instead.
|
||
* The literal `-9223372036854775808i64` triggers task #17
|
||
* (wwstage NEGQ-over-imm drops digits → `MOVQ $-, AX`), so
|
||
* we spell it `(-INT64_MAX) - 1` to sidestep that orthogonal
|
||
* bug until #17 lands. */
|
||
{ "i64_div_INT64_MIN_by_two",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = (-9223372036854775807i64) - 1i64;\n"
|
||
" let b: i64 = 2i64;\n"
|
||
" if (a / b == -4611686018427387904i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i64_mod_INT64_MIN_by_two",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = (-9223372036854775807i64) - 1i64;\n"
|
||
" let b: i64 = 2i64;\n"
|
||
" if (a % b == 0i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- i32 signed ----------------------------------------------- */
|
||
{ "i32_div_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let x: i32 = -100i32;\n"
|
||
" let y: i32 = 7i32;\n"
|
||
" if (x / y == -14i32) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i32_mod_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let x: i32 = -100i32;\n"
|
||
" let y: i32 = 7i32;\n"
|
||
" if (x % y == -2i32) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i32_div_INT32_MIN_by_two",
|
||
"fn main() i32 = {\n"
|
||
" let x: i32 = -2147483648i32;\n"
|
||
" let y: i32 = 2i32;\n"
|
||
" if (x / y == -1073741824i32) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i32_mod_INT32_MIN_by_two",
|
||
"fn main() i32 = {\n"
|
||
" let x: i32 = -2147483648i32;\n"
|
||
" let y: i32 = 2i32;\n"
|
||
" if (x % y == 0i32) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- i16 signed ----------------------------------------------- */
|
||
{ "i16_div_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i16 = -100i16;\n"
|
||
" let b: i16 = 7i16;\n"
|
||
" if (a / b == -14i16) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i16_mod_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i16 = -100i16;\n"
|
||
" let b: i16 = 7i16;\n"
|
||
" if (a % b == -2i16) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i16_div_INT16_MIN_by_two",
|
||
"fn main() i32 = {\n"
|
||
" let a: i16 = -32768i16;\n"
|
||
" let b: i16 = 2i16;\n"
|
||
" if (a / b == -16384i16) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- i8 signed ------------------------------------------------ */
|
||
{ "i8_div_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i8 = -100i8;\n"
|
||
" let b: i8 = 7i8;\n"
|
||
" if (a / b == -14i8) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i8_mod_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i8 = -100i8;\n"
|
||
" let b: i8 = 7i8;\n"
|
||
" if (a % b == -2i8) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "i8_div_INT8_MIN_by_two",
|
||
"fn main() i32 = {\n"
|
||
" let a: i8 = -128i8;\n"
|
||
" let b: i8 = 2i8;\n"
|
||
" if (a / b == -64i8) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- u64 unsigned: high-bit-set / 2 must not sign-extend ----- */
|
||
{ "u64_div_high_bit_set",
|
||
"fn main() i32 = {\n"
|
||
" let a: u64 = 0x8000000000000001u64;\n"
|
||
" let b: u64 = 2u64;\n"
|
||
" if (a / b == 0x4000000000000000u64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "u64_mod_high_bit_set",
|
||
"fn main() i32 = {\n"
|
||
" let a: u64 = 0x8000000000000001u64;\n"
|
||
" let b: u64 = 2u64;\n"
|
||
" if (a % b == 1u64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- u32 unsigned: high-bit-set / 2 -------------------------- */
|
||
{ "u32_div_high_bit_set",
|
||
"fn main() i32 = {\n"
|
||
" let a: u32 = 0x80000001u32;\n"
|
||
" let b: u32 = 2u32;\n"
|
||
" if (a / b == 0x40000000u32) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "u32_mod_high_bit_set",
|
||
"fn main() i32 = {\n"
|
||
" let a: u32 = 0x80000001u32;\n"
|
||
" let b: u32 = 2u32;\n"
|
||
" if (a % b == 1u32) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- u16 unsigned: high-bit-set / 2 -------------------------- */
|
||
{ "u16_div_high_bit_set",
|
||
"fn main() i32 = {\n"
|
||
" let a: u16 = 0xFFFFu16;\n"
|
||
" let b: u16 = 2u16;\n"
|
||
" if (a / b == 0x7FFFu16) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- u8 unsigned: high-bit-set / 2 --------------------------- */
|
||
{ "u8_div_high_bit_set",
|
||
"fn main() i32 = {\n"
|
||
" let a: u8 = 0xFFu8;\n"
|
||
" let b: u8 = 2u8;\n"
|
||
" if (a / b == 0x7Fu8) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- mixed-type-flag row: i64 cast to u64 forces unsigned arm.
|
||
* `unsignd` flag fires if either operand is unsigned — pin it. */
|
||
{ "mixed_unsigned_rhs_picks_unsigned",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = 100i64;\n"
|
||
" let b: u64 = 7u64;\n"
|
||
" if ((a: u64) / b == 14u64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
|
||
/* ---- COMPOUND ASSIGN rows ------------------------------------- *
|
||
*
|
||
* The siblings of the binary `/` and `%` paths above: `x /= v`,
|
||
* `x %= v`, and `*p /= v`. Pre-#16's-B2 these emitted the worst
|
||
* shape catalog:
|
||
* - cstage cgen.c:3765 IDENT-local: silent no-op (BX held
|
||
* loaded slot value, the trailing store wrote it back
|
||
* unchanged — no IDIV emit at all).
|
||
* - wwstage cgenexpr.ww:5381-5403 IDENT-local: identical silent
|
||
* no-op shape; the load-bearing sentinel — a future regression
|
||
* here passes byte-id (no asm = no divergence) AND passes the
|
||
* selfhost corpus (no consumer of signed compound /=). The
|
||
* semantic test below is the only catch.
|
||
* - cstage cgen.c:3549 / wwstage cgenexpr.ww:3338 deref-compound
|
||
* `*p OP= v`: silent rhs-only store, value of *p clobbered with
|
||
* the divisor instead of computed quotient.
|
||
*
|
||
* Top-level-let compound coverage (cstage cgen.c:3735 / wwstage
|
||
* cgenexpr.ww:5147) is deferred per task #18 — the inline-driver
|
||
* 978 fixture can't currently exercise it because of a pre-existing
|
||
* linker `undefined reference to '<file>.gs'` for single-file
|
||
* top-level lets that flow through `LEAQ name(SB)`. The cstage:3735
|
||
* and wwstage:5147 sites are code-review-verified for rule-10
|
||
* symmetry; runtime coverage follows once #18 lands. */
|
||
{ "compound_ident_local_div_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = -100i64;\n"
|
||
" a /= 7i64;\n"
|
||
" if (a == -14i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "compound_ident_local_mod_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = -100i64;\n"
|
||
" a %= 7i64;\n"
|
||
" if (a == -2i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "compound_ident_local_div_neg_divisor",
|
||
"fn main() i32 = {\n"
|
||
" let a: i64 = 100i64;\n"
|
||
" a /= -7i64;\n"
|
||
" if (a == -14i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "compound_ident_local_div_unsigned",
|
||
"fn main() i32 = {\n"
|
||
" let a: u64 = 0x8000000000000001u64;\n"
|
||
" a /= 2u64;\n"
|
||
" if (a == 0x4000000000000000u64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "compound_deref_div_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let arr: [1]i64;\n"
|
||
" arr[0] = -50i64;\n"
|
||
" let p: *i64 = &arr[0];\n"
|
||
" *p /= 3i64;\n"
|
||
" if (arr[0] == -16i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "compound_deref_mod_neg_dividend",
|
||
"fn main() i32 = {\n"
|
||
" let arr: [1]i64;\n"
|
||
" arr[0] = -50i64;\n"
|
||
" let p: *i64 = &arr[0];\n"
|
||
" *p %= 3i64;\n"
|
||
" if (arr[0] == -2i64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
{ "compound_deref_div_unsigned",
|
||
"fn main() i32 = {\n"
|
||
" let arr: [1]u64;\n"
|
||
" arr[0] = 0x8000000000000001u64;\n"
|
||
" let p: *u64 = &arr[0];\n"
|
||
" *p /= 2u64;\n"
|
||
" if (arr[0] == 0x4000000000000000u64) { return 42; };\n"
|
||
" return 0;\n"
|
||
"};\n",
|
||
42 },
|
||
};
|
||
|
||
static int
|
||
run_driver(const char *driver, const struct row *r, int i)
|
||
{
|
||
char src[64], tmpdir[64], cmd[1024];
|
||
snprintf(src, sizeof src, "/tmp/wwid_%d_%d.ww", getpid(), i);
|
||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwid_%d_d_%d", getpid(), i);
|
||
|
||
FILE *f = fopen(src, "wb");
|
||
if (!f) return -1;
|
||
fputs(r->src, f);
|
||
fclose(f);
|
||
|
||
mkdir(tmpdir, 0755);
|
||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||
tmpdir, driver, src);
|
||
if (runwait(cmd) != 0) {
|
||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||
r->label, driver);
|
||
unlink(src); rmdir(tmpdir);
|
||
return -1;
|
||
}
|
||
|
||
const char *base = strrchr(src, '/');
|
||
base = base ? base + 1 : src;
|
||
char outbin[128];
|
||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||
char *dot = strrchr(outbin, '.');
|
||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||
int got = runwait(outbin);
|
||
|
||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||
return got;
|
||
}
|
||
|
||
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 cdrv[1024];
|
||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||
char wdrv[1024];
|
||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||
|
||
struct { const char *name; const char *path; int gated_on_existence; }
|
||
drivers[] = {
|
||
{ "cstage", cdrv, 0 },
|
||
{ "wwstage", wdrv, 1 },
|
||
{ NULL, NULL, 0 },
|
||
};
|
||
|
||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||
int total = 0, fail = 0;
|
||
for (int d = 0; drivers[d].name; d++) {
|
||
if (drivers[d].gated_on_existence
|
||
&& access(drivers[d].path, X_OK) != 0) {
|
||
fprintf(stderr, "intdiv_signed: skip %s (no %s)\n",
|
||
drivers[d].name, drivers[d].path);
|
||
continue;
|
||
}
|
||
for (int i = 0; i < n; i++) {
|
||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||
total++;
|
||
if (got != rows[i].want) {
|
||
fprintf(stderr,
|
||
"intdiv_signed[%s][%s]: exit=%d want=%d\n",
|
||
drivers[d].name, rows[i].label,
|
||
got, rows[i].want);
|
||
fail++;
|
||
}
|
||
}
|
||
}
|
||
if (fail) {
|
||
fprintf(stderr,
|
||
"intdiv_signed: %d/%d fixtures failed\n", fail, total);
|
||
return 1;
|
||
}
|
||
printf("intdiv_signed: %d/%d ok\n", total, total);
|
||
return 0;
|
||
}
|