Files
ww/test/wcc/911_continue_run.c
Hojun-Cho d960971c6e wcc: continue runs post-step in 3-clause for and range form (#138)
`for (init; cond; post) { ... continue; ... }` and `for (let i .. xs)
{ ... continue; ... }` now emit a `post` (3-clause) or `rpost` (range)
label between the body and the JMP back to the cond-test. `continue`
jumps to that label, runs the post-step, then re-tests the loop
condition — mirrors C/Go/Hare semantics. Pre-fix both stages emitted
`JMP loop_top` for continue, SKIPPING the post-step → the value that
triggered continue never advanced → silent infinite loop on the first
matching iteration. Found by impl-strconv-fold2 during the fold-3
decimal.ha port: `leftshift_newdigits`'s `for (... i+=1) { ... else
if (d.digits[i]==p5[i]) continue; ... }` would infinite-loop at the
first equal digit.

BOTH stages were identically buggy → 990-997 cs==ww byte-id held →
gate-blind. Bootstrap audit (`grep -rE 'for \(let .*\.\.' lib/
selfhost/`) confirmed zero existing callers with continue in either
the 3-clause or range form; bootstrap-NEUTRAL.

Sites: cmd/w6c/cgen.c N_FOR + N_FORRANGE; selfhost/cmd/wcc/
cgenstmt.ww cgfor + cgforrange. 1-clause `for (cond)` byte-id
preserved (cont_target stays = loop_top when n.rhs == nil). Rule-11
carve-out: 3-clause and range share the lowered structure; fixing
one without the other would leave the same silent miscompile in
N_FORRANGE — one-class closure on the continue-skips-post bug, same
precedent as #133-expanded.

911_continue_run: 4 rows. for3_skip_one (lead's repro, was infinite
loop, now 4), for3_skip_two (nested continues, 30), range_skip
(Hare-range continue, was infinite loop, now 120), for1_continue_
byteid (1-clause regression assertion — bootstrap shape unchanged).
Pre-existing parser-side divergences (cstage silently drops post in
the never-used 2-clause `for (cond; post)`; wwstage doesn't support
infinite `for {}`) deferred to #139 — not in decimal.ha, no shared
class with the cgen continue-skips-post.
2026-05-27 00:12:39 +09:00

229 lines
6.9 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 <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[] = {
/* 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 src[64];
snprintf(src, sizeof src, "/tmp/wwcont_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwcont_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
/* 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, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
unlink(src); rmdir(tmpdir);
continue;
}
char outbin[128];
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
/* 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++;
}
unlink(outbin); rmdir(tmpdir);
char cs_s[64], ws_s[64];
snprintf(cs_s, sizeof cs_s, "/tmp/wwcont_%d_%d_cs.s",
getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/wwcont_%d_%d_ww.s",
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++; unlink(src); 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++; unlink(src); unlink(cs_s); 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++;
}
unlink(src); unlink(cs_s); unlink(ws_s);
}
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;
}