Block and branch instructions


WebAssembly offers a block instruction for creating a block construct. After entering a block, you cannot see existing values of the stack. You may imagine that a new partition (or a new stack) is built on top of the stack. You'll push values and carry out instructions on the basis of the partition.

So, the following code would cause an error:

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        i32.const 1
        block
            i32.const 2
            i32.add
            call $log
        end
    )
    (start $main)
)

Because you cannot see those values pushed before entering the block, you only push one value to the new partition but i32.add has to pop two values. Pushing two values after entering the block would solve the problem.

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        i32.const 1
        block
            i32.const 2
            i32.const 3
            i32.add
            call $log
        end
        call $log
    )
    (start $main)
)

The above block doesn't use result to define the result type. You have to empty the new stack before leaving the block. If not, an error happens. If you define the result type of a block, a value can be left on the stack. The value can be popped by the instruction after leaving the block. For example:

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        i32.const 1
        block (result i32)
            i32.const 2
            i32.const 3
            i32.add
        end
        i32.add
        call $log ;; print 6
    )
    (start $main)
)

The br instruction can branch to a given level or label in an enclosing construct. You provide br a number. If the number is n, the control flow would branch to the end of the n-th outer block. That is, branches may only reference levels or labels defined by a construct in which they are enclosed. For example:

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        block
            block
                br 0
                i32.const 3
                call $log
            end
            i32.const 2
            call $log
        end
        i32.const 1
        call $log
    )
    (start $main)
)

The br 0 instruction branches the control flow to the end of the 0th outer block. The code after the end of the current block will be executed so the console will print 2 and 1. If you change br 0 to br 1, it will branch the control flow to the end of the first outer block so only 1 is printed.

Using numbers is not convenient. You can place a label when declaring a block so that br can branch to a given label.

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        block $B0
            block $B1
                br $B1
                i32.const 3
                call $log
            end
            i32.const 2
            call $log
        end
        i32.const 1
        call $log
    )
    (start $main)
)

Similarly, the code will print 2 and 1. If you change br $B1 to br $B0, 1 is printed.

The br_if instruction can conditionally branch to a given label in an enclosing construct. It pops one value from the stack, do nothing if the value is 0. If not, branch to a given level or label. Let's implement a unless construct (the inverse of if) by using br_if.

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        block $UNLESS_BLOCK
            block $THEN
                block $UNLESS
                    i32.const 0 ;; unless false
                    br_if $THEN
                end
                ;; executed unless false
                i32.const 10
                call $log               
                br $UNLESS_BLOCK
            end
            ;; executed unless true
            i32.const 20
            call $log
        end
    )
    (start $main)
)

Because of i32.const 0, br_if doesn't branch and br $UNLESS_BLOCK is executed. The control flow branch to the end of $UNLESS_BLOCK so the code after the end of block $THEN won't be executed. The console will print 10. If you change i32.const 0 to i32.const 1, the console prints 20.

If you have a list of branching conditions, br_table may be feasible. It pops a number from the stack, uses the number as an index and jumps to the label in an enclosing construct. For example:

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        (local $n i32)
        (set_local $n (i32.const 0))
        block $B0
            block $B1
                block $B2
                    get_local $n
                    br_table $B2 $B1 $B0  ;; branch according to $n 
                end
                i32.const 2
                call $log
            end
            i32.const 1
            call $log
        end
        i32.const 0
        call $log
    )
    (start $main)
)

If $n is 0, br_table branches to $B2 and 1 would branch to $B1, etc.

WebAssembly provides if..else..end and loop..end so using block to implement conditionally branching is not necessary. In fact, if..else..end, loop..end and func all build blocks. block can be an auxiliary instruction when using if..else..end and loop..end when defining more diversified branches.