wcc/ww: nodeisunsigned resolves module-global idents via the stamp
A bare module-global unsigned operand got signed IDIV/SAR/Jcc — the predicate's ident arm only consulted the local table, so globals fell through to signed (review finding #25). Read the stamp for the global arm; corpus emission is unmoved (no bootstrap code div/shift/cmps a bare unsigned global). 989_gunsigned_run pins cs==ww (red 3/8 pre-fix).
This commit is contained in:
190
test/wcc/989_gunsigned_run.c
Normal file
190
test/wcc/989_gunsigned_run.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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 <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[] = {
|
||||
/* (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;
|
||||
}
|
||||
Reference in New Issue
Block a user