/* * 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 #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; }; 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 '.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; }