Loops: loop and br_if
A loop in WebAssembly does not iterate automatically. It is simply a labeled construct whose beginning is a branch target. You create iteration by placing a br_if at the bottom of the body that branches back to the loop label when the condition is still true.
The loop construct
Section titled “The loop construct”(loop $label ...) marks the beginning of a loop. A br $label inside the body jumps back to the start — the opposite direction from br in a block. To exit the loop you either let execution reach the closing end (fall-through) or branch to an outer label.
(module (func (export "countDown") (param $n i32) (loop $again ;; body here local.get $n i32.const 1 i32.sub local.tee $n ;; decrement $n and keep value on stack i32.const 0 i32.gt_s br_if $again))) ;; repeat while $n > 0Loop flow
Section titled “Loop flow”flowchart LR A["init locals"] --> B["loop $again (start)"] B --> C["body: update acc, increment i"] C --> D["i <= n?"] D -->|yes| B D -->|no| E["exit: return acc"]
Counted loop example: sumTo
Section titled “Counted loop example: sumTo”The function below sums the integers from 1 to n using a loop. Local $i is the counter; local $acc accumulates the total.
(module (func (export "sumTo") (param $n i32) (result i32) (local $i i32) (local $acc i32) i32.const 1 local.set $i ;; i = 1 i32.const 0 local.set $acc ;; acc = 0 (loop $again local.get $i local.get $acc i32.add local.set $acc ;; acc += i local.get $i i32.const 1 i32.add local.set $i ;; i++ local.get $i local.get $n i32.le_s br_if $again) ;; repeat while i <= n local.get $acc)) ;; return accRun it
Section titled “Run it”Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
loop + br_if (structured back-edge) | Validatable at compile time and safer than an arbitrary backward goto | You have to write the exit condition yourself every time — there’s no built-in for/while for the engine to manage |
| No arbitrary jumps between loops | The loop’s boundaries are unambiguous to the engine, which simplifies compiling/optimizing it | A complex CFG from native code has to go through a relooper-style pass before it can map onto loop/br |
Common mistakes
Section titled “Common mistakes”- Assuming
br_ifinside aloopbehaves likebreak— it actually jumps back to the start of theloop(closer tocontinue), not an exit from the loop - Forgetting a correct exit condition, so
br_if $againis always true and the loop never ends (Wasm has no automatic infinite-loop guard) - An off-by-one in the exit condition — e.g. using
i <= nwherei < nwas intended — causing the loop to run one iteration too few or too many, as in thesumToexample
💡 Real-world example
Because a Wasm
loop’s back-edge is structured and explicit, an engine’s JIT can identify loop boundaries directly at compile time — without first having to reconstruct them from an arbitrary jump graph, the way a native-code JIT sometimes must.