Files
ww/test/wcc/944_alias_idx_family_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

471 lines
16 KiB
C

/*
* 944_alias_idx_family_run — #5 alias arc F2a batch 1 (task #60 +
* the #79 init-store rider): wwstage INDEX / SLICE / FOR-RANGE /
* LITERAL-INIT over an alias-NAMED base type. The tnode-keyed cgen
* walks saw only the N_TNAME leaf — esz fell to the 1-sentinel (8 on
* the init-store side) and the base classified as a POINTER (MOVQ of
* array words + no IMULQ): m8b_idx1/range1 SEGV 139, m8b_slice1
* silent-wrong past prefix-luck (m8c_slice1big exit 2), alias-[4]u32
* literal-init MOVQ stride-8 over a stride-4 slot = saved-BP/RIP
* smash (#79, masked when esz==8). cstage reads everything off the
* chased stamped type (type_chase_named/idx_eff, post-F1), so it is
* the runtime-correct reference; the fix re-keys the ww sites off
* tichase'd stamped tinfos (cgindex/cgassign-N_INDEX/cgslice/
* cgbasecap in cgenexpr.ww, cgforrange + cgarrlitfillbp + bare-let
* classify in cgenstmt.ww, pusharg N_SLICE in cgenutil.ww) and every
* row graduates to 0/0 byte-id.
*
* row | shape | want
* ------------------+------------------------------------------+-----
* idx0_ctl | named [4]int base read/store (control) | 0
* idx1_alias | m8b_idx1: r/w via 1-level alias arr | 0
* idx2_2level | write+read via arr2 = arr (944 pin) | 0
* idx_fwdref | decl-order permuted chain + compound += | 0
* init_u32_alias | #79: type A=[4]u32 literal-init, LAST |
* | elem readback (pre-fix BP/RIP smash) | 0
* initrep_u32_alias | #79: alias `[v...]` repeat fill — bound |
* | off the chased alen, LAST elem | 0
* loopfill_1024 | uninit alias [1024]int + loop-fill, LAST |
* | elem (pins the bare-let array classify: |
* | no composite zero-fill, cs-identical) | 0
* slice0_ctl | named base slice (control) | 0
* slice1_alias | m8b_slice1 + 1-level alias ARRAY base: |
* | sl dest + store + readback | 0
* slice2_2level | sl2 = sl, len/read/store + RE-SLICE of |
* | the alias slice | 0
* slice1big | m8c: 1000 elems, values > 255, LAST-elem |
* | readback + default-hi + .cap + range cnt | 0
* slice_fwdref | decl-after-use chain over 2-level alias |
* | array base, s[1] *= 3 compound | 0
* range0_ctl | named [4]int range (control) | 0
* range1_alias | m8b_range1: sum over alias arr | 0
* range2_2level | sum over arr2 = arr | 0
* range_slicealias | for-range over an alias SLICE | 0
* m7c_global2d | global [2]row (row = [3]int): g[1][2] | 0
* gslice_alias | GLOBAL alias-slice: .len + indexed read | 0
* slicearg_alias | slice-of-alias as CALL ARG (pusharg leg) | 0
*
* Values exceed 255 so an esz=1 byte-load cannot pass by little-endian
* prefix-luck; readbacks assert the LAST element. Every row runs BOTH
* drivers and asserts cstage/wwstage asm byte-identity (alias rows are
* graduations: ww aligned UP to the cs runtime-correct side). NNN<950,
* self-contained /tmp sources, no imports (944 precedent).
*
* Deliberately NOT pinned (g-fold territory, tasks #77/#78): any
* DIRECT alias-typed global ARRAY row. Expected state at this commit,
* probe-verified UNCHANGED by the fix: `let g: arr = [...]` — ww
* link-ERR (no DATA emitted for the alias-typed global), cs 1-level
* runs 0, cs 2-level runs WRONG (silent).
*/
#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;
}
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), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "idx0_ctl",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" if (a[3] != 4000) { return 1; };\n"
" a[3] = 9999;\n"
" if (a[3] != 9999) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "idx1_alias",
"package main;\n"
"type arr = [4]int;\n"
"export fn main() i32 = {\n"
" let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" if (a[2] != 3000) { return 1; };\n"
" if (a[3] != 4000) { return 2; };\n"
" a[3] = 8888;\n"
" if (a[3] != 8888) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
{ "idx2_2level",
"package main;\n"
"type arr = [4]int;\n"
"type arr2 = arr;\n"
"export fn main() i32 = {\n"
" let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" if (a[2] != 3000) { return 1; };\n"
" a[1] = 9999;\n"
" if (a[1] != 9999) { return 2; };\n"
" if (a[3] != 4000) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
{ "idx_fwdref",
"package main;\n"
"type arr2 = arr;\n"
"type arr = [4]int;\n"
"export fn main() i32 = {\n"
" let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" if (a[3] != 4000) { return 1; };\n"
" a[3] += 500;\n"
" if (a[3] != 4500) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* #79: narrow-elem alias literal-init. Pre-fix the per-element
* store rode the esz=8 sentinel + MOVQ over a stride-4 slot —
* elements 2/3 landed at 0(BP)/+8(BP) = saved-BP/RIP smash
* (masked when esz==8). LAST element read back. */
{ "init_u32_alias",
"package main;\n"
"type A = [4]u32;\n"
"export fn main() i32 = {\n"
" let a: A = [1: u32, 2: u32, 3: u32, 4: u32];\n"
" if (a[3] != 4) { return 1; };\n"
" if (a[0] != 1) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* #79 repeat leg: the `[v...]` fill bound comes off the chased
* tinfo's alen (an alias arrtn has no length tnode). */
{ "initrep_u32_alias",
"package main;\n"
"type A = [6]u32;\n"
"export fn main() i32 = {\n"
" let a: A = [7: u32...];\n"
" if (a[5] != 7) { return 1; };\n"
" if (a[0] != 7) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* Uninit alias array + per-index loop fill. Also pins the
* bare-let classify: an alias-to-array keeps the per-index-write
* contract (no composite zero-fill — cstage-identical asm). */
{ "loopfill_1024",
"package main;\n"
"type arrk = [1024]int;\n"
"export fn main() i32 = {\n"
" let a: arrk;\n"
" let i: int = 0;\n"
" for (i < 1024) {\n"
" a[i] = i * 3 + 1000;\n"
" i += 1;\n"
" };\n"
" if (a[1023] != 1023 * 3 + 1000) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "slice0_ctl",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let s: []int = a[1:3];\n"
" if (s.len != 2) { return 1; };\n"
" if (s[1] != 3000) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* alias slice DEST over a 1-level alias ARRAY base (slice-expr
* over alias base, 1-level leg). */
{ "slice1_alias",
"package main;\n"
"type arr = [4]int;\n"
"type sl = []int;\n"
"export fn main() i32 = {\n"
" let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let s: sl = a[1:3];\n"
" if (s.len != 2) { return 1; };\n"
" if (s[0] != 2000) { return 2; };\n"
" s[1] = 7777;\n"
" if (a[2] != 7777) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
/* 2-level alias slice dest + RE-SLICE of the alias slice (the
* cgslice alias-SLICE-base leg). */
{ "slice2_2level",
"package main;\n"
"type sl = []int;\n"
"type sl2 = sl;\n"
"export fn main() i32 = {\n"
" let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let s: sl2 = a[1:3];\n"
" if (s.len != 2) { return 1; };\n"
" if (s[0] != 2000) { return 2; };\n"
" s[1] = 7777;\n"
" if (a[2] != 7777) { return 3; };\n"
" if (s[1] != 7777) { return 4; };\n"
" let r: sl = s[0:2];\n"
" if (r.len != 2) { return 5; };\n"
" if (r[1] != 7777) { return 6; };\n"
" return 0;\n"
"};\n", 0 },
/* 1000 elements, values 300..7293: an esz=1 stride/byte load
* cannot land on the right word, and the LAST-element readback
* breaks prefix-luck (m8c). Also pins alias default-hi (a[2:]),
* .cap via the chased cgbasecap leg, and range over the
* 2-level-alias sub-slice. Base array stays plain [1000]int so
* this row pins the slice legs in isolation — the alias bare-let
* + loop-fill shape is loopfill_1024's pin (the #79-rider
* bare-let array classify). */
{ "slice1big",
"package main;\n"
"type slk = []int;\n"
"type slk2 = slk;\n"
"export fn main() i32 = {\n"
" let a: [1000]int;\n"
" let i: int = 0;\n"
" for (i < 1000) {\n"
" a[i] = i * 7 + 300;\n"
" i += 1;\n"
" };\n"
" let s: slk = a[1:1000];\n"
" if (s.len != 999) { return 1; };\n"
" if (s[998] != 999 * 7 + 300) { return 2; };\n"
" s[998] = 123456;\n"
" if (a[999] != 123456) { return 3; };\n"
" let t: slk2 = a[2:];\n"
" if (t.len != 998) { return 4; };\n"
" if (t.cap != 998) { return 5; };\n"
" if (t[997] != 123456) { return 6; };\n"
" let cnt: int = 0;\n"
" for (let x .. t) { cnt = cnt + 1; if (x < 0) { return 8; }; };\n"
" if (cnt != 998) { return 7; };\n"
" return 0;\n"
"};\n", 0 },
{ "slice_fwdref",
"package main;\n"
"export fn main() i32 = {\n"
" let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let s: sl2 = a[1:3];\n"
" if (s[1] != 3000) { return 1; };\n"
" s[1] *= 3;\n"
" if (a[2] != 9000) { return 2; };\n"
" return 0;\n"
"};\n"
"type sl2 = sl;\n"
"type sl = []int;\n"
"type arr2 = arr;\n"
"type arr = [4]int;\n", 0 },
{ "range0_ctl",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]int = [1: int, 2: int, 3: int, 4: int];\n"
" let sum: int = 0;\n"
" for (let x .. a) { sum = sum + x; };\n"
" if (sum != 10) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "range1_alias",
"package main;\n"
"type arr = [4]int;\n"
"export fn main() i32 = {\n"
" let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let sum: int = 0;\n"
" for (let x .. a) { sum = sum + x; };\n"
" if (sum != 10000) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "range2_2level",
"package main;\n"
"type arr = [4]int;\n"
"type arr2 = arr;\n"
"export fn main() i32 = {\n"
" let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let sum: int = 0;\n"
" for (let x .. a) { sum = sum + x; };\n"
" if (sum != 10000) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ "range_slicealias",
"package main;\n"
"type sl = []int;\n"
"export fn main() i32 = {\n"
" let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let t: sl = a[0:4];\n"
" let sum: int = 0;\n"
" for (let x .. t) { sum = sum + x; };\n"
" if (sum != 10000) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* m7c: the BASE is a plain [2]row global — the alias sits on the
* ELEMENT (row = [3]int). elemisarrayc's node walk can't see
* through the element's N_TNAME, so g[0] loaded a VALUE where the
* sub-array ADDRESS was needed (ww SEGV on the read; DATA emit
* was already correct both stages, F0). LAST element checked. */
{ "m7c_global2d",
"package main;\n"
"type row = [3]int;\n"
"let g: [2]row = [[10: int, 20: int, 30: int], [40: int, 50: int, 60: int]];\n"
"export fn main() i32 = {\n"
" if (g[0][0] != 10) { return 1; };\n"
" if (g[1][2] != 60) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* GLOBAL alias-typed SLICE: DATA emit + .len + indexed read all
* resolve (the global ARRAY twin does NOT — see the header note;
* g-fold #77/#78). */
{ "gslice_alias",
"package main;\n"
"type sl = []int;\n"
"let g: sl = [1000: int, 2000: int, 3000: int];\n"
"export fn main() i32 = {\n"
" if (g.len != 3) { return 1; };\n"
" if (g[2] != 3000) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* slice-of-alias-array as a CALL ARG exercises the pusharg
* N_SLICE leg (cgenutil.ww), a distinct lowering from cgslice;
* plus the inferred-let receive of an alias-base slice. */
{ "slicearg_alias",
"package main;\n"
"type arr = [4]int;\n"
"type arr2 = arr;\n"
"fn sum(s: []int) int = {\n"
" let t: int = 0;\n"
" for (let x .. s) { t = t + x; };\n"
" return t;\n"
"};\n"
"export fn main() i32 = {\n"
" let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n"
" let s = a[1:3];\n"
" if (s.len != 2) { return 1; };\n"
" if (s[1] != 3000) { return 2; };\n"
" if (sum(a[0:4]) != 10000) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/aif_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/aif_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/aif_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd,
"timeout 20 %s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/aif_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/aif_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/aif_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
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[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "alias_idx_family: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("alias_idx_family: %d/%d ok\n", total, total);
return 0;
}