Files
ww/test/wcc/928_match_nonident_idx_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

291 lines
9.1 KiB
C

/*
* 928_match_nonident_idx_run — `match` on an N_INDEX scrutinee whose
* BASE is not an ident (#48).
*
* Pre-#48 wwstage matchscrutt's N_INDEX arm required ibase.kind ==
* N_IDENT; an index over a struct field (`match (h.xs[i])` = N_INDEX
* over N_DOT) returned nil → cgmatch dispatched with scrutt = nil:
* every case arm's variant index clamped to 0 (CMPQ $0) and the
* @match_spill slot fell to the 16B default. SILENT cs≠ww miscompile
* (cstage N_MATCH reads the checker-stamped s->type for every
* scrutinee shape, cmd/w6c/cgen.c:7510); regex fold-2a's
* `match (re.insts[i])` is the consumer that surfaced it. The fix
* resolves the element type from the stamped .type_ (the #45/#67
* stamped-carrier pattern), plus the load-half twin: cgindex's
* generic-fallback tagged-element load was the only arm missing the
* `slot > 24` R8 word (cgen.c:9106-9117), so a >24B-slot element via
* a non-ident base under-read the cursor.
*
* Rows pin: the discovering repro shape (field-base slice index, all
* three variants matched both ways), the regex shape (56B-slot
* inst-like union over r.insts[i]; payload reads stay within the 32B
* cursor — words past R8 are #43's deferred residual), a chained-dot
* base (o.in_.xs[i] — depth independence), and the ident
* scrutinee / ident-base array + slice index controls (byte-id at
* master per the #48 scoping probes; cs is untouched by the fix, so
* the per-row cs==ww cmp pins them unchanged). A call-BASE index
* (`mk()[0]`) is NOT covered: cgindex's element classification (esz +
* tagged) skips N_CALL bases — pre-existing sibling, filed separately.
* Per row: w6c vs w6c_ww byte-id (rule 10 — cstage is the
* runtime-correct reference) + runtime via both drivers.
*/
#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;
};
#define TU_TYPES \
"type va = void;\n" \
"type vb = bool;\n" \
"type vc = size;\n" \
"type tu = (va | vb | vc);\n"
static const struct row rows[] = {
/* The discovering repro: slice field of a struct, indexed.
* Pre-#48 wwstage dispatched the vb element to the FIRST arm
* regardless of pattern (tag compared against 0) — exit 1. The
* third match pins arm-order independence (variant 1 matched
* from a non-first arm). */
{ "field_slice_idx",
TU_TYPES
"type holder = struct { xs: []tu, n: size };\n"
"export fn main() i32 = {\n"
" let sl: []tu = [(true: vb), ((7: size): vc)];\n"
" let h: holder;\n"
" h.xs = sl;\n"
" h.n = 2;\n"
" match (h.xs[0]) {\n"
" case let b: vb => { if (!(b: bool)) { return 2; }; };\n"
" case => return 1;\n"
" };\n"
" match (h.xs[1]) {\n"
" case let s: vc => { if ((s: size) != 7) { return 4; }; };\n"
" case => return 3;\n"
" };\n"
" match (h.xs[0]) {\n"
" case va => return 5;\n"
" case let b: vb => { if (!(b: bool)) { return 6; }; };\n"
" case vc => return 7;\n"
" };\n"
" return 0;\n"
"};\n",
0 },
/* The regex fold-2a shape: 56B-slot inst-like union (8B tag +
* 48B widest payload) matched over a struct-field slice index.
* The big variant drives slot/spill sizing past 24B (the R8
* word both in cgindex's fallback load and cgmatch's spill);
* matched payloads read word 1 only — within the 32B cursor. */
{ "regex_inst_56b",
"type big = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n"
"type ilit = struct { r: i64 };\n"
"type imatch = void;\n"
"type inst = (big | ilit | imatch);\n"
"type re = struct { insts: []inst, n: i64 };\n"
"export fn main() i32 = {\n"
" let sl: []inst;\n"
" let l: ilit = ilit { r = 65 };\n"
" let v: inst = l;\n"
" append(sl, v);\n"
" let m: inst = void: imatch;\n"
" append(sl, m);\n"
" let r: re;\n"
" r.insts = sl;\n"
" r.n = 2;\n"
" match (r.insts[0]) {\n"
" case let x: ilit => { if (x.r != 65) { return 2; }; };\n"
" case => return 1;\n"
" };\n"
" match (r.insts[1]) {\n"
" case imatch => { };\n"
" case => return 3;\n"
" };\n"
" match (r.insts[0]) {\n"
" case big => return 4;\n"
" case let x: ilit => { if (x.r != 65) { return 5; }; };\n"
" case imatch => return 6;\n"
" };\n"
" return 0;\n"
"};\n",
0 },
/* Dot-DEPTH: the index base is a CHAINED dot (o.in_.xs[i] =
* N_INDEX over N_DOT over N_DOT) — pins that the stamped-carrier
* resolve is depth-independent (any non-ident base shape, not
* just one dot level). */
{ "dot_depth_idx",
TU_TYPES
"type inner = struct { xs: []tu, k: size };\n"
"type outer = struct { in_: inner, n: size };\n"
"export fn main() i32 = {\n"
" let sl: []tu = [(true: vb), ((9: size): vc)];\n"
" let o: outer;\n"
" o.in_.xs = sl;\n"
" o.in_.k = 1;\n"
" o.n = 2;\n"
" match (o.in_.xs[0]) {\n"
" case let b: vb => { if (!(b: bool)) { return 2; }; };\n"
" case => return 1;\n"
" };\n"
" match (o.in_.xs[1]) {\n"
" case va => return 3;\n"
" case let s: vc => { if ((s: size) != 9) { return 4; }; };\n"
" case vb => return 5;\n"
" };\n"
" return 0;\n"
"};\n",
0 },
/* Controls: ident scrutinee + ident-base array and slice index
* — the pre-existing matchscrutt/cgmatch paths the fix must not
* disturb (asm byte-id at master per the scoping probes). */
{ "ident_and_identbase_controls",
TU_TYPES
"export fn main() i32 = {\n"
" let v: tu = (true: vb);\n"
" match (v) {\n"
" case let b: vb => { if (!(b: bool)) { return 2; }; };\n"
" case => return 1;\n"
" };\n"
" let arr: [2]tu = [(true: vb), ((7: size): vc)];\n"
" match (arr[1]) {\n"
" case let s: vc => { if ((s: size) != 7) { return 4; }; };\n"
" case => return 3;\n"
" };\n"
" let sl: []tu = [(true: vb), ((7: size): vc)];\n"
" match (sl[0]) {\n"
" case let b: vb => { if (!(b: bool)) { return 6; }; };\n"
" case => return 5;\n"
" };\n"
" return 0;\n"
"};\n",
0 },
};
static const char *g_bin;
static int
compile_s(const char *tool, const char *src, const char *outpath)
{
char cmd[1024];
snprintf(cmd, sizeof cmd, "%s/%s %s > %s 2>&1",
g_bin, tool, src, outpath);
return runwait(cmd);
}
static int
file_eq(const char *a, const char *b)
{
char cmd[1024];
snprintf(cmd, sizeof cmd, "cmp -s %s %s", a, b);
return runwait(cmd) == 0;
}
static int
run_driver(const char *driver, const char *tmpdir, const char *src,
const char *label)
{
char outbin[256], cmd[1024];
snprintf(outbin, sizeof outbin, "%s/%s_bin", tmpdir, driver);
snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s >/dev/null 2>&1",
g_bin, driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
label, driver);
return -1;
}
return runwait(outbin);
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
static char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
g_bin = bin;
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
const struct row *r = &rows[i];
char tmpdir[128], src[160], cs_s[160], ww_s[160], rmcmd[192];
snprintf(tmpdir, sizeof tmpdir, "/tmp/matchnidx_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/matchnidx_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/matchnidx_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ww_s, sizeof ww_s, "%s/matchnidx_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return 1; }
fputs("package main;\n\n", f);
fputs(r->src, f);
fclose(f);
total++;
int cs_rc = compile_s("w6c", src, cs_s);
int ww_rc = compile_s("w6c_ww", src, ww_s);
if (cs_rc != 0 || ww_rc != 0) {
fprintf(stderr, "FAIL row[%s]: compile rc cs=%d "
"ww=%d\n", r->label, cs_rc, ww_rc);
fail++;
runwait(rmcmd);
continue;
}
if (!file_eq(cs_s, ww_s)) {
fprintf(stderr, "FAIL row[%s]: cs != ww .s\n",
r->label);
fail++;
}
int got_cs = run_driver("ww", tmpdir, src, r->label);
if (got_cs != r->want) {
fprintf(stderr, "FAIL row[%s] cstage: want %d "
"got %d\n", r->label, r->want, got_cs);
fail++;
}
char wwdrv[600];
snprintf(wwdrv, sizeof wwdrv, "%s/ww_ww", g_bin);
if (access(wwdrv, X_OK) == 0) {
int got_ww = run_driver("ww_ww", tmpdir, src,
r->label);
if (got_ww != r->want) {
fprintf(stderr, "FAIL row[%s] wwstage: "
"want %d got %d\n",
r->label, r->want, got_ww);
fail++;
}
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "match_nonident_idx_run: %d/%d rows failed\n",
fail, total);
return 1;
}
printf("match_nonident_idx_run: %d rows ok\n", total);
return 0;
}