/* * 989_gunsigned_run — F7-c5 (#25): a module-GLOBAL unsigned ident on the * divide / shift / relational path must pick the UNSIGNED opcode. * * THE BUG (cat-A silent miscompile, gate-blind): nodeisunsigned * (selfhost/cmd/wcc/cgenutil.ww) read the LOCAL's declared tnode and * returned `false` (signed) for a module-global ident (localfindnode→nil * → fell through to `return false`). So a `u64` global counter fed to * `/ % >> >= >` got the signed opcode — IDIVQ/CQO, SARQ, JG/JGE — instead * of the unsigned DIVQ, SHRQ, JA/JAE cstage emits (type_isunsigned reads * the stamped n->type, cmd/w6c/cgen.c:2541). For a high-bit-set u64 * global the two diverge at runtime (cs≠ww — the cat-A signature). * THE FIX: the N_IDENT arm collapses onto the checker-stamped n.type_ * (the same stamp the N_DOT/N_CAST/N_INDEX/N_CALL arms already read), * aligning wwstage UP — the global's unsigned stamp now flows. * * CLASS-M: unlike c1-c4 this changes emission on a shape the bootstrap * corpus DOES hit (module-global unsigned counters on the divide/shift * path), so the self-compile .s MOVES; the bind verifies every move is * toward cstage (IDIV→DIV) and runtime-correct. * * Rows (cstage `ww` + gated wwstage `ww_ww`; rule-10 + absolute value): * row | shape | want * -----------------+-----------------------------+------ * global_ushr | g:u64=1<<63; g >> 1 | 0 [bug: SAR] * global_udiv | g:u64=1<<63; g / (1<<62) | 2 [bug: IDIV] * global_ucmp | g:u64=1<<63; g > 1 | 7 [bug: JG] * signed_global_ctl| s:i64=-8; s >> 1 | 252 (control: a SIGNED * | global must STAY SARQ — c5 must not over-convert) */ #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_exit; }; static const struct row rows[] = { /* (1) #25 — u64 global >> 1: unsigned SHRQ (cs) vs signed SARQ (bug). * 0x8000000000000000 >> 1 == 0x4000000000000000 unsigned → return 0. */ { "global_ushr", "package main;\n" "let g: u64 = 0;\n" "export fn main() int = {\n" " g = 9223372036854775808u64;\n" " let r: u64 = g >> 1;\n" " if (r == 4611686018427387904u64) { return 0; };\n" " return 1;\n" "};\n", 0 }, /* (2) #25 — u64 global / (1<<62): unsigned DIVQ (cs) vs signed IDIVQ. * (1<<63) / (1<<62) == 2 unsigned; signed reads g as negative. */ { "global_udiv", "package main;\n" "let g: u64 = 0;\n" "export fn main() int = {\n" " g = 9223372036854775808u64;\n" " let r: u64 = g / 4611686018427387904u64;\n" " return r: int;\n" "};\n", 2 }, /* (3) #25 — u64 global > 1: JA (cs) vs JG (bug). 1<<63 > 1 is true * unsigned, false signed (1<<63 is negative as i64). */ { "global_ucmp", "package main;\n" "let g: u64 = 0;\n" "export fn main() int = {\n" " g = 9223372036854775808u64;\n" " if (g > 1u64) { return 7; };\n" " return 9;\n" "};\n", 7 }, /* (4) control — a SIGNED i64 global must keep the signed shift (SARQ): * -8 >> 1 == -4 (exit byte 252). Pins that c5 reads the stamp, not a * blanket "global → unsigned" — no over-conversion. */ { "signed_global_ctl", "package main;\n" "let s: i64 = 0;\n" "export fn main() int = {\n" " s = -8;\n" " let r: i64 = s >> 1;\n" " return r: int;\n" "};\n", 252 }, }; /* run_build — build+run `src` via `driver`; returns the binary's exit * code, or -1 on a build failure. */ static int run_build(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/gun_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/gun_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -2; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, src); int brc = runwait(cmd); 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 = -1; if (brc == 0) got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return brc == 0 ? got : -1; } 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], wdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *drv; int gated; } 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 && access(drivers[d].drv, X_OK) != 0) { fprintf(stderr, "gunsigned_run: skip %s (no %s)\n", drivers[d].name, drivers[d].drv); continue; } for (int i = 0; i < n; i++) { total++; int got = run_build(drivers[d].drv, &rows[i], i); if (got != rows[i].want_exit) { fprintf(stderr, "gunsigned_run[%s][%s]: exit=%d " "want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want_exit); fail++; } } } if (fail) { fprintf(stderr, "gunsigned_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("gunsigned_run: %d/%d ok\n", total, total); return 0; }