Files
ww/test/wcc/978_intdiv_signed.c
Hojun-Cho 7b9488706b parse: enforce strict-package — reject package-less files (#24a)
Flip the soft-default to a hard "missing package clause" error symmetrically in
both stages (cmd/wcc/parse.c + lib/ww/syntax/parse.ww): the first real decl of a
primary section with empty pathmod/resetmod and no seen clause is now rejected.
Closes the documented soft-default divergence (the 63-wrapper carve-out).

The gate flip can't be split from the migration it breaks, so this is one atomic
commit: ~80 test/wcc wrappers gain `package main;` via a shared wwtestpkg.h
helper, 6 data fixtures plus 17 asm-grep assertions update for the bare->main.<leaf>
root-helper mangle shift, and rt/ declares `package rt;` with @symbol pinning the
bare rt_ensure/rt_malloc linker names.

Root mangling narrows: the executable entry `main` stays bare (existing
carve-out), but root helper symbols become main.X. The #84 cluster is rewritten
to assert main.run distinct from aa.run/test.run; its cgen fix and bare machinery
are retained — still load-bearing for package-less module-reset deps. New
table-driven test 782_strict_package.c (6 rows, both stages).

Retiring //ww:module-reset is deferred to #24b: it is load-bearing (clears the
.wwi pathmod so the body's package clause asserts), not a vestige; fusing its
removal here would be a silent mismatch.

All byte-id gates green; full make test reports "all 335 tests passed".
2026-06-29 03:55:26 +09:00

459 lines
13 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.
/*
* 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 <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwid_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wwid_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwid_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
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;
}