Control Flow — Overview
WebAssembly has no goto, no raw jump addresses, and no unstructured branches. Every transfer of control happens inside a labeled construct, and br targets that label — not a memory address or a line number. This design makes Wasm programs safe to validate in a single linear pass and easy for browsers to compile efficiently.
Structured constructs
Section titled “Structured constructs”Wasm provides exactly three control-flow constructs:
block— a forward-only group.br $labelexits the block, jumping to the instruction immediately after the closingend. Think of it as a labeled break.loop— a backward-branch target.br $labeljumps to the beginning of the loop, repeating it. Exiting a loop requires branching to an outer label or falling off the end.if— a conditional branch. Pops ani32from the stack; if non-zero it executes thethenarm, otherwise the optionalelsearm.
All three constructs can carry a result type, making them expressions that leave a value on the stack.
Branch targets and direction
Section titled “Branch targets and direction”The key insight is that br in Wasm is not a jump to an address. It is a jump relative to the nesting structure. A br $b inside a block $b exits that block; the same br $l inside a loop $l repeats that loop.
flowchart LR A["block $b\n ...body...\n br $b"] --> B["jumps to END\nof block"] C["loop $l\n ...body...\n br_if $l"] --> D["jumps to START\nof loop"]
br_if is the conditional variant — it pops an i32 condition and only branches if it is non-zero.
What this module covers
Section titled “What this module covers”This module walks through each construct in depth:
blockandbr— using labeled breaks to exit early from a sequence of instructions.loopandbr_if— writing counted and conditional loops.if / else— conditional execution with optional result values.
Early exit with block and br
Section titled “Early exit with block and br”The simplest use of block is an early-exit pattern. The function below pushes 42, immediately branches out of the block, and the i32.const 99 after the branch is never reached.
(module (func (export "earlyExit") (result i32) (block $b (result i32) i32.const 42 br $b i32.const 99) ;; never reached ))The block has a result type of i32. When br $b fires, the value 42 is already on the stack and it becomes the result of the block expression — and therefore the return value of the function.