Files
ww/test/wcc/921_amp_def_global_run.c
Hojun-Cho ce3a25a0b4 test: contain sepwork scratch per-driver tmpdir, fix /tmp+in-repo leak (#8)
The wcc test drivers ran `ww build <bare-/tmp src>` with no -o, so the
compiler's <stem>.sepwork scratch landed beside the source and was never
cleaned: unbounded /tmp growth (2195 stale dirs observed) that fills tmpfs
and fabricates phantom test failures + silent harness aborts, and for
in-repo fixture builds leaked .sepwork into the tracked tree.

Each leaking build now writes its source + output inside a per-invocation
tmpdir, passes -o <tmpdir>/<stem> so the .sepwork lands inside it, and
rm -rf's the tmpdir on every exit path -- including fopen-fail and the
expected-fail reject builds (scratch is mkdir'd before the build can fail).
`ww run` and explicit-`-o`/byte-id helpers are left as-is; the 990/993
byte-id comparison logic is byte-for-byte unchanged.

Two items filed separately (this commit holds the no-Makefile / no-main.c
rail):
- #13: a stale <src>.s byte-id readback (749) silently no-ops since
  separate-compile emits .s to <ostem>.sepwork/__root.s; documented inline.
- #14: build-system Makefile recipes build selfhost/cmd/*/main.ww with no
  -o and leak main.sepwork in-tree (bounded, gitignored; own commit).

One concern -- sepwork leak hygiene -- across 228 drivers; uniform
transform applied per-file and two-round reviewed. make test: all 402
passed, zero net-new /tmp scratch, zero test-driven in-repo .sepwork.
2026-06-22 23:29:39 +09:00

288 lines
9.2 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.
/*
* 921_amp_def_global_run — address-of a module-level global (#149) +
* scalar-def address-of (#147 consolidation). Regression net for the
* cgaddr def-symbol / module-qualified gap: A.2 (0ed0b39) + A.3
* (9e3bc4e) widened the VALUE-LOAD path for struct/array defs but the
* ADDRESS-OF twin (cgexpr N_UN TK_AMP / cgun) never handled def-symbols
* nor module-qualified globals, so `&def` / `&mod.global` emitted an
* uninitialised AX (PUSHQ AX / POPQ DI / CALL — no LEAQ) → garbage
* pointer, despite the DATA symbol existing. cs+ww were BYTE-IDENTICAL
* on the bug (gate-blind), so each row carries BOTH dimensions:
* (a) `ww build` + run, asserting the exit code == the value reached
* THROUGH the pointer (a garbage pointer yields a wrong value, not
* a false exit-0 — the probe lesson).
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
*
* Two operand shapes, one class (cgaddr-doesn't-handle-defs):
* Shape 1 `&G` — N_IDENT, G a top-level def (struct/array/scalar)
* Shape 2 `&mod.G` — N_DOT, mod a module qualifier (kind-agnostic:
* cross-module let / def / scalar / fn)
* The fold-4 / γ-cleanup driver is the cross-module struct-def row
* (`&mod.def_struct`, the `&math::f64info` shape).
*
* Non-addressable defs (str def inlined; computed-rhs float like
* `def NAN = 0.0/0.0` — #147, no DATA symbol) are NOT silently dropped:
* `&them` is a LOUD build error in both stages (rule-7). Those rows set
* want_build_fail.
*
* NOTE: array-element reads THROUGH a `*[N]T` pointer param have a
* pre-existing cs!=ww divergence (narrow MOVSXD/MOVL + esz-stride),
* unrelated to #149 — so the def-array row reads element 0 via a
* `*[N]T -> *T` cast + plain deref, which isolates the `&A` LEAQ.
*
* Single-file multi-package form (like 953_f64crossmod_run): `package
* myf; ... package main; import myf; ...` so w6c/w6c_ww see the cross-
* module reference without -I plumbing.
*/
#include <stdio.h>
#include <stdlib.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;
int want_build_fail; /* 1: w6c must reject (loud rule-7 error) */
};
static const struct row rows[] = {
/* Shape 1 — same-pkg `&def`. */
{ "def_struct",
"package main;\n"
"type pt = struct { x: i32, y: i32 };\n"
"def P: pt = pt{x=7, y=11};\n"
"fn rd(p: *pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&P); };\n", 18, 0 },
{ "def_array",
"package main;\n"
"def A: [3]i64 = [18i64, 11i64, 0i64];\n"
"fn rd(p: *[3]i64) i64 = { let q: *i64 = p: *i64; return *q; };\n"
"export fn main() i32 = { return rd(&A): i32; };\n", 18, 0 },
{ "def_scalar_int",
"package main;\n"
"def N: i32 = 42;\n"
"fn rd(p: *i32) i32 = { return *p; };\n"
"export fn main() i32 = { return rd(&N); };\n", 42, 0 },
{ "def_scalar_float",
"package main;\n"
"def D: f64 = 0.5;\n"
"fn rd(p: *f64) f64 = { return *p; };\n"
"export fn main() i32 = { return (rd(&D) * 100.0): i32; };\n", 50, 0 },
/* Shape 2 — cross-pkg `&mod.G` (kind-agnostic). */
{ "xmod_def_struct", /* the &math::f64info / fold-4 shape */
"package myf;\n"
"export type pt = struct { x: i32, y: i32 };\n"
"export def P: pt = pt{x=7, y=11};\n"
"package main;\n"
"import myf;\n"
"fn rd(p: *myf.pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&myf.P); };\n", 18, 0 },
{ "xmod_let_struct",
"package myf;\n"
"export type pt = struct { x: i32, y: i32 };\n"
"export let L: pt = pt{x=7, y=11};\n"
"package main;\n"
"import myf;\n"
"fn rd(p: *myf.pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&myf.L); };\n", 18, 0 },
{ "xmod_scalar_let",
"package myf;\n"
"export let S: i32 = 42;\n"
"package main;\n"
"import myf;\n"
"fn rd(p: *i32) i32 = { return *p; };\n"
"export fn main() i32 = { return rd(&myf.S); };\n", 42, 0 },
{ "xmod_func", /* Shape 2 TY_FN leaf → LEAQ fn(SB) */
"package myf;\n"
"export fn helper() i32 = { return 42; };\n"
"package main;\n"
"import myf;\n"
"fn take(p: *fn() i32) i32 = { return 7; };\n"
"export fn main() i32 = { return take(&myf.helper); };\n", 7, 0 },
/* Regressions — paths the fix must NOT disturb. */
{ "reg_let_struct", /* &let_struct worked same-pkg pre-#149 */
"package main;\n"
"type pt = struct { x: i32, y: i32 };\n"
"let L: pt = pt{x=7, y=11};\n"
"fn rd(p: *pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&L); };\n", 18, 0 },
{ "reg_amp_arr_idx", /* &local_arr[i] */
"package main;\n"
"fn rd(p: *i64) i64 = { return *p; };\n"
"export fn main() i32 = {\n"
" let a: [3]i64 = [5i64, 18i64, 9i64];\n"
" let p: *i64 = &a[1];\n"
" return rd(p): i32; };\n", 18, 0 },
/* rule-7 LOUD: `&non-addressable def` (no DATA symbol). */
{ "fail_str_def",
"package main;\n"
"def MSG: str = \"hello\";\n"
"fn rd(p: *str) i32 = { return 7; };\n"
"export fn main() i32 = { return rd(&MSG); };\n", 0, 1 },
{ "fail_computed_float", /* #147 `def NAN = 0.0/0.0` shape */
"package main;\n"
"def D: f64 = 1.0 / 4.0;\n"
"fn rd(p: *f64) f64 = { return *p; };\n"
"export fn main() i32 = { return (rd(&D) * 100.0): i32; };\n", 0, 1 },
{ NULL, NULL, 0, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
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 w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "amp_def_global: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64], rmcmd[80];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwamp_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwamp_%d_%d.ww",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
if (rows[i].want_build_fail) {
/* rule-7 LOUD: both stages must REJECT (`&` of a non-
* addressable def has no DATA symbol). Build to .s and
* assert non-zero exit on each stage. */
char cmd[2048], dump[128];
snprintf(dump, sizeof dump, "%s/wwamp_%d_%d.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, dump, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c ACCEPTED &non-"
"addressable-def (want loud reject)\n",
rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, dump, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww ACCEPTED &non-"
"addressable-def (want loud reject)\n",
rows[i].label);
fail++;
}
runwait(rmcmd);
continue;
}
/* (a) cstage build + run; assert exit == value reached
* through the pointer. */
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwamp_%d_%d",
tmpdir, getpid(), i);
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate. */
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwamp_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwamp_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d amp-def-global tests failed\n", fail, n);
return 1;
}
printf("amp_def_global: %d/%d ok (cstage run + cs==ww byte-id)\n",
n, n);
return 0;
}