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.
This commit is contained in:
@@ -7812,8 +7812,15 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
char *end = mklabel(c, "rend");
|
||||
char *natural_exit = end;
|
||||
if (n->els) natural_exit = mklabel(c, "relseloop");
|
||||
/* #138 (range form): `continue` must run the implicit `i+=1`
|
||||
* post-step before re-testing the loop bound. Pre-fix the
|
||||
* cont-target was `loop` (top), skipping the ADDQ $1, ioff
|
||||
* below the body — infinite loop on the value that triggered
|
||||
* continue. Dedicated `rpost` label; bootstrap-NEUTRAL (no
|
||||
* range-form continue callers in lib/ or selfhost/). */
|
||||
char *rpost = mklabel(c, "rpost");
|
||||
if (nloops < LOOP_MAX) {
|
||||
loop_cont[nloops] = loop;
|
||||
loop_cont[nloops] = rpost;
|
||||
loop_brk[nloops] = end;
|
||||
nloops++;
|
||||
}
|
||||
@@ -7842,6 +7849,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, binds[b].off));
|
||||
}
|
||||
cgstmt(c, n->body, locals, frame);
|
||||
label(c, rpost);
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, ioff));
|
||||
ins1(c, A_JMP, abranch(loop));
|
||||
if (n->els) {
|
||||
@@ -7860,6 +7868,14 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
* the else block sits between them. */
|
||||
char *natural_exit = end;
|
||||
if (n->els) natural_exit = mklabel(c, "elseloop");
|
||||
/* #138: `continue` in a 3-clause `for (init; cond; post)` must
|
||||
* run the post-step before re-testing cond. Pre-fix the
|
||||
* continue-target was `loop` (top), which SKIPPED post → state
|
||||
* never advanced → infinite loop. Allocate a dedicated `post`
|
||||
* label only when there IS a post-step (`n->rhs`); else keep
|
||||
* continue → loop-top, byte-id with 1-clause for. */
|
||||
char *cont_target = loop;
|
||||
if (n->rhs) cont_target = mklabel(c, "post");
|
||||
if (n->lhs) cgstmt(c, n->lhs, locals, frame);
|
||||
label(c, loop);
|
||||
if (n->cond) {
|
||||
@@ -7868,13 +7884,16 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins1(c, A_JE, abranch(natural_exit));
|
||||
}
|
||||
if (nloops < LOOP_MAX) {
|
||||
loop_cont[nloops] = loop;
|
||||
loop_cont[nloops] = cont_target;
|
||||
loop_brk[nloops] = end;
|
||||
nloops++;
|
||||
}
|
||||
cgstmt(c, n->body, locals, frame);
|
||||
if (nloops > 0) nloops--;
|
||||
if (n->rhs) cgexpr(c, n->rhs, *locals);
|
||||
if (n->rhs) {
|
||||
label(c, cont_target);
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
}
|
||||
ins1(c, A_JMP, abranch(loop));
|
||||
if (n->els) {
|
||||
label(c, natural_exit);
|
||||
|
||||
Reference in New Issue
Block a user