Files
ww/test/wcc/911_continue_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

227 lines
6.8 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.
/*
* 911_continue_run — runtime + byte-id net for #138: `continue` in a
* loop with a post-step (3-clause C-style `for (init; cond; post)` OR
* Hare-range `for (let x .. xs)` with its implicit `i+=1`) must run
* the post-step BEFORE re-testing the cond / bound. Pre-fix the
* continue-target was the loop top, which SKIPPED the post-step → the
* value that triggered continue never advanced → infinite loop.
*
* Pre-fix BOTH STAGES emitted identical buggy asm (`JMP loop_top` for
* continue, post-step JMP'd over) — cs==ww byte-id held → 990-997 gate
* was BLIND to the miscompile. Found by impl-strconv-fold2 during the
* fold-3 decimal.ha port: leftshift_newdigits uses `for (let i: u32 =
* 0u32; i < n; i += 1u32) { ... else if (d.digits[i]==p5[i]) continue;
* ... }`; pre-fix that path infinite-looped at the first equal digit.
*
* Fix (both stages): allocate a dedicated `post` (3-clause) / `rpost`
* (range) label; continue jumps to that label; the label emits before
* the post-step; fall-through naturally hits it too. For 1-clause `for
* (cond)` with no post-step, the cont-target stays = loop-top
* (unchanged from pre-#138, byte-id preserved on the corpus's
* 1-clause shape).
*
* Rows cover the 3 shapes the fix targets PLUS a 1-clause regression
* row asserting the byte-id surface for the 1-clause path is
* unchanged. cstage `ww build` + run for runtime; w6c vs w6c_ww `.s`
* cmp for rule-10 byte-id.
*/
#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; };
static const struct row rows[] = {
/* The lead's repro: 3-clause for with continue. Pre-fix infinite
* loop on i=2; post-fix count=4 (i=0,1,3,4 all increment count). */
{ "for3_skip_one",
"package main;\n"
"export fn main() i32 = {\n"
" let count: i32 = 0;\n"
" for (let i: i32 = 0; i < 5; i += 1) {\n"
" if (i == 2) { continue; };\n"
" count += 1;\n"
" };\n"
" return count;\n"
"};\n", 4 },
/* Two skips: continue must advance i correctly each time. count
* over i=0,2,4 only (skip 1 and 3); 3 hits × 10 = 30. */
{ "for3_skip_two",
"package main;\n"
"export fn main() i32 = {\n"
" let c: i32 = 0;\n"
" for (let i: i32 = 0; i < 5; i += 1) {\n"
" if (i == 1) { continue; };\n"
" if (i == 3) { continue; };\n"
" c += 10;\n"
" };\n"
" return c;\n"
"};\n", 30 },
/* Hare-range form `for (let x .. xs)`. Continue must run the
* implicit `i+=1` increment. Pre-fix infinite-loop on first
* matching element; post-fix sum over non-skipped values. xs =
* [10, 20, 30, 40, 50]; skip 30; sum = 10+20+40+50 = 120. */
{ "range_skip",
"package main;\n"
"export fn main() i32 = {\n"
" let xs: [5]i32 = [10, 20, 30, 40, 50];\n"
" let sum: i32 = 0;\n"
" for (let v .. xs) {\n"
" if (v == 30) { continue; };\n"
" sum += v;\n"
" };\n"
" return sum;\n"
"};\n", 120 },
/* 1-clause `for (cond)` with continue. No post-step, so cont-
* target stays = loop-top. Pre-#138 emission must be byte-id
* preserved — this is the bootstrap shape. Manual post-step
* inside the body. count over i=0,1,3,4 (skip i=2) → 4. */
{ "for1_continue_byteid",
"package main;\n"
"export fn main() i32 = {\n"
" let count: i32 = 0;\n"
" let i: i32 = 0;\n"
" for (i < 5) {\n"
" let cur: i32 = i;\n"
" i += 1;\n"
" if (cur == 2) { continue; };\n"
" count += 1;\n"
" };\n"
" return count;\n"
"};\n", 4 },
{ NULL, NULL, 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, "continue: 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];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwcont_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[128];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwcont_%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);
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwcont_%d_%d",
tmpdir, getpid(), i);
/* Build with 5s timeout — pre-fix the buggy rows infinite-
* looped; post-fix all rows must exit cleanly under it. */
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;
}
/* timeout wrapper: pre-fix bug = infinite loop; want clean
* exit. exit 124 = timeout/hang. */
char rcmd[256];
snprintf(rcmd, sizeof rcmd, "timeout 5 %s", outbin);
int got = runwait(rcmd);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d "
"(124 = timeout / pre-fix infinite loop)\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwcont_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwcont_%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 continue tests failed\n", fail, n);
return 1;
}
printf("continue: %d/%d ok (cstage run + cs==ww byte-id)\n",
n, n);
return 0;
}