kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9ebc085dfe3e925ecf2cf329beba8fe4d043c884
parent ad61e55784cc0159d881e48ba40fd38ad1590aa2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 12:31:31 -0700

wasm/structure: unroll nested switches to fix infinite-loop dispatch

unroll_switch_islands only reordered the outermost switch per invocation:
after recording the outer island, scan jumped to sw_i+1, skipping past
the inner switch's JUMP-to-dispatch (which sits inside the outer case
body). The inner case labels stayed as backward refs and the structurer
wrapped them in synthetic SCOPE_LOOPs — so the inner br_table targeted
loops and ran forever.

Split into unroll_switch_islands_once (returns island count) and a
wrapper that re-runs to a fixed point so nested switches all get
reordered.

Also bail the dispatch-search on WIR_SCOPE_OPEN/SCOPE_CLOSE. Without
this, a second pass mistakes a for-loop body label for a switch dispatch
label (the JUMP L_body; LABEL L_body; [switch SCOPE_LOOP open]; setup;
selector; WIR_SWITCH sequence has no terminator between the LABEL and
the WIR_SWITCH), and the resulting bogus rewrite buries the for-step
inside the switch's dispatcher dead-code region. Caught by
6_8_24_break_switch_in_loop and 6_8_25_continue_inside_switch.

Full W-path suite: 426 pass / 7 fail / 32 skip / 0 hang
(was 425 / 7 / 32 / 1).

Diffstat:
Mdoc/WASM_PARSE_CHECKLIST.md | 18++++++++++++------
Msrc/arch/wasm/structure.c | 31+++++++++++++++++++++++++++----
2 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/doc/WASM_PARSE_CHECKLIST.md b/doc/WASM_PARSE_CHECKLIST.md @@ -4,18 +4,24 @@ Status of the Wasm CGTarget against the `test/parse` C suite, path **W** (`cfree cc -O0 -target wasm32-none -c case.c` → `cfree run -e test_main case.wasm`). - Host: arm64 (native JIT for the re-lowering). Opt level 0. -- 465 cases: **425 pass · 7 fail · 0 compile-fail · 32 skip · 1 hang**. +- 465 cases: **426 pass · 7 fail · 0 compile-fail · 32 skip · 0 hang**. - The skips below match run.sh's phased-rollout regex (reported SKIP). The fails fall outside it and report as **FAIL** in the harness. - Reproduce / re-probe: `build/wasm_probe.sh [filter]`; results in `build/wasm_probe/results.tsv`, per-case logs alongside. -## ⏳ Hang (blocks `make test-parse`) +## ⏳ Hang — fixed (was 1) -- [ ] **`6_8_19_switch_nested_dup_case`** — `.wasm` compiles clean; `cfree run` - spins at ~100% CPU forever. Infinite loop is in JIT'd code (nested-switch - lowering emits a backward branch turning the switch into a loop). One stalled - parallel worker prevents the suite from completing. +- [x] **`6_8_19_switch_nested_dup_case`** — the structurer's + `unroll_switch_islands` pass only reordered the outermost switch per + invocation: after recording the outer island its `scan` advanced past the + inner switch's `JUMP L_disp`, so the inner case labels stayed backward + refs and were wrapped as synthetic `SCOPE_LOOP`s. `br_table` targeting + those loops turned the inner switch into an infinite loop. Fix + (`src/arch/wasm/structure.c`): re-run the unroller to a fixed point, and + also bail the dispatch-search loop on `WIR_SCOPE_OPEN`/`SCOPE_CLOSE` so a + later pass doesn't mistake a for-loop body label (which sits just before + `[switch SCOPE_LOOP open]`) for a switch dispatch label. ## ❌ Fail — wrong exit code (7) diff --git a/src/arch/wasm/structure.c b/src/arch/wasm/structure.c @@ -311,7 +311,13 @@ typedef struct WSIsland { static WIR* wir_append(WTarget* t, WIR** out, u32* nout, u32* cap); -static void unroll_switch_islands(WSCtx* x) { +/* One pass of switch-island detection + reordering. Returns the number of + * islands rewritten (0 = nothing to do). Nested switches need multiple passes: + * the outer JUMP-to-dispatch sits before the inner switch's JUMP-to-dispatch + * in WIR order, so the outer scan advances past the inner JUMP after recording + * the outer island. Re-scanning the rewritten WIR brings the now-exposed inner + * island into view. */ +static int unroll_switch_islands_once(WSCtx* x) { WTarget* t = x->t; Heap* h = t->c->ctx->heap; WSIsland islands[16]; @@ -334,7 +340,13 @@ static void unroll_switch_islands(WSCtx* x) { continue; } /* Find WIR_SWITCH just past dispatch label; bail on intervening - * terminator. */ + * terminator or scope boundary. The C/toy frontends emit the + * selector expression as a small straight-line sequence between + * LABEL dispatch and WIR_SWITCH, with no SCOPE_OPEN/CLOSE — those + * only appear at switch entry/exit, never around the dispatcher. + * Rejecting SCOPE_OPEN here keeps a second pass from mistaking a + * for-loop body label (which sits just before a `[switch open]`) + * for a switch dispatch label. */ u32 sw_i = UINT32_MAX; for (u32 k = dlbl->wir_index + 1u; k < t->nwir; ++k) { if (t->wir[k].op == WIR_SWITCH) { @@ -342,7 +354,8 @@ static void unroll_switch_islands(WSCtx* x) { break; } if (t->wir[k].op == WIR_JUMP || t->wir[k].op == WIR_CMP_BRANCH || - t->wir[k].op == WIR_RET || t->wir[k].op == WIR_UNREACHABLE) + t->wir[k].op == WIR_RET || t->wir[k].op == WIR_UNREACHABLE || + t->wir[k].op == WIR_SCOPE_OPEN || t->wir[k].op == WIR_SCOPE_CLOSE) break; } if (sw_i == UINT32_MAX) { @@ -359,7 +372,7 @@ static void unroll_switch_islands(WSCtx* x) { nislands++; scan = sw_i + 1u; } - if (nislands == 0) return; + if (nislands == 0) return 0; /* Build a rewritten WIR list. For each island, emit pre + (selector + * SWITCH) + (case bodies). Then everything after the last island's @@ -406,6 +419,16 @@ static void unroll_switch_islands(WSCtx* x) { Label l = t->wir[k].labels[0]; if (l != LABEL_NONE && l - 1u < t->nlabels) t->labels[l - 1u].wir_index = k; } + return (int)nislands; +} + +/* Re-run the single-pass unroller to a fixed point so nested switches all + * get reordered. Each pass reorders at most the outermost island in a chain + * of nested switches; the next pass exposes the next inner one. The loop + * terminates because every rewritten island drops one JUMP-to-dispatch from + * the WIR (and a function has finitely many). */ +static void unroll_switch_islands(WSCtx* x) { + while (unroll_switch_islands_once(x) > 0) { /* re-scan after each rewrite */ } } /* For-loop de-rotation.