Skip to content
This repository was archived by the owner on Aug 12, 2026. It is now read-only.

Commit 4b6173d

Browse files
committed
loop/recur lands with its corpus lines: a block, a br, and a stack swap
0022 D's shape verbatim: loop is locals set before a (loop ...) label, recur a br back to it. Rebinding is simultaneous — every recur argument is evaluated onto the value stack under the current bindings, then popped into the loop locals in reverse; the loop-recur-simultaneous entry exists to fail if that ever becomes a sequential local.set. Nested loops get distinct labels and the inner recur binds to the inner loop (loop-nested). fn-recur stays out of the slice and says so. Claude-Session: https://claude.ai/code/session_01XF5Hfq4Ca2N2XYEzWQQuHt
1 parent b73388c commit 4b6173d

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

‎corpus/s3.edn‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@
2727
{:id "if-false" :forms [(if false 1 2)]}
2828
{:id "if-zero-truthy" :forms [(if 0 1 2)]}
2929
{:id "if-untaken-arm" :forms [(if true 3 (quot 1 0))]}
30+
{:id "loop-countdown" :forms [(loop [i 10 acc 0]
31+
(if (< 0 i) (recur (- i 1) (+ acc i)) acc))]}
32+
{:id "loop-fib-iter" :forms [(loop [n 10 a 0 b 1]
33+
(if (< 0 n) (recur (- n 1) b (+ a b)) a))]}
34+
;; recur rebinds simultaneously: a sequential-assignment bug makes the
35+
;; swap collapse (a=b, then b=that same value) and this entry catch it.
36+
{:id "loop-recur-simultaneous"
37+
:forms [(loop [a 1 b 2 n 3]
38+
(if (< 0 n) (recur b a (- n 1)) (- (* a 10) b)))]}
39+
{:id "loop-nested" :forms [(loop [i 3 total 0]
40+
(if (< 0 i)
41+
(recur (- i 1)
42+
(+ total (loop [j i s 0]
43+
(if (< 0 j) (recur (- j 1) (+ s j)) s))))
44+
total))]}
3045
{:id "let-shadow" :forms [(let [x 1] (let [x (+ x 1)] (* x 10)))]}
3146
{:id "let-sequential" :forms [(let [x 2 y (+ x 3)] (* x y))]}
3247
{:id "top-level-forms" :forms [(+ 1 1) (+ 2 2)]}

‎src/cljwit/emit.clj‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,37 @@
4040
:nil "(ref.null eq)"
4141
(out-of-slice! (str "literal of type " type) ast)))
4242

43+
(defn- emit-loop
44+
"`0022` D: a block and a br. The loop's locals are set before entry;
45+
`recur` re-enters via `br` to the loop label."
46+
[ctx {:keys [bindings body loop-id]}]
47+
(let [[ctx' sets]
48+
(reduce (fn [[c acc] {:keys [name init]}]
49+
(let [wl (fresh-local! c)]
50+
[(assoc-in c [:locals name] wl)
51+
(conj acc (format "(local.set %s %s)" wl (emit-expr c init)))]))
52+
[ctx []] bindings)
53+
label (str "$loop" (swap! (:counter ctx) inc))
54+
ctx'' (assoc-in ctx' [:loops loop-id]
55+
{:label label
56+
:locals (mapv #(get-in ctx' [:locals (:name %)]) bindings)})]
57+
(format "(block (result (ref null eq)) %s (loop %s (result (ref null eq)) %s))"
58+
(str/join " " sets) label (emit-expr ctx'' body))))
59+
60+
(defn- emit-recur
61+
"Rebinding is simultaneous: every argument is evaluated onto the value
62+
stack under the *current* bindings, then popped into the loop locals in
63+
reverse — a sequential `local.set` would let later arguments observe
64+
earlier rebinds."
65+
[ctx {:keys [exprs loop-id] :as ast}]
66+
(let [{:keys [label locals]} (get-in ctx [:loops loop-id])]
67+
(when-not label
68+
(out-of-slice! "recur outside an enclosing loop (fn recur is a later unit)" ast))
69+
(format "(block (result (ref null eq)) %s %s (br %s))"
70+
(str/join " " (map #(emit-expr ctx %) exprs))
71+
(str/join " " (map #(format "(local.set %s)" %) (reverse locals)))
72+
label)))
73+
4374
(defn- emit-let [ctx {:keys [bindings body]}]
4475
(let [[ctx' sets]
4576
(reduce (fn [[c acc] {:keys [name init]}]
@@ -100,6 +131,8 @@
100131
(emit-expr ctx (:then ast))
101132
(emit-expr ctx (:else ast)))
102133
:let (emit-let ctx ast)
134+
:loop (emit-loop ctx ast)
135+
:recur (emit-recur ctx ast)
103136
:local (if-let [wl (get-in ctx [:locals (:name ast)])]
104137
(format "(local.get %s)" wl)
105138
(out-of-slice! (str "unresolved local " (:name ast)) ast))

0 commit comments

Comments
 (0)