區塊與分支


WebAssembly 提供 block 指令,它可以建立一個區塊,在區塊中看不到進入區塊前於堆疊中置放的數值,可以想像成在既有的堆疊頂端,建立一個新的堆疊起點,在其中進行指令操作。

因此底下會出錯:

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

因為區塊中看不到先前堆疊中已經置入的數值,區塊中在新堆疊起點之後,只置入了一個數值,而 i32.add 必須堆疊中有兩個數值,必須是底下才是正確的:

(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)
)

區塊若沒有定義結果型態,執行完區塊後,堆疊必須為空,否則會發生堆疊不為空的錯誤;區塊可以使用 result 定義回傳值,執行完區塊後堆疊可以留下一個數值,這個數值在離開區塊之後,會置入先前堆疊,可以繼續其他指令操作。例如:

(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 ;; 顯示 6
    )
    (start $main)
)

在區塊中可以使用 br 分支(Branch)指令跳出區塊,br 可以接上數字,若數字為 n,表示流程分支至往外第 n 層區塊的 end 之後開始執行,也就是在分支時,只能分支至外層。例如:

(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)
)

其中 br 0 表示流程分支至往外第 0 層的 end,也就是目前區塊的 end 之後,因此顯示 2、1,如果將 br 0 改為 br 1,表示流程分支至往外第 1 層的 end,這時就只會顯示 1。

就撰寫上,使用數字並不方便,區塊也可以命名,br 時就可以指定名稱:

(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)
)

同樣地,以上會顯示 2、1,若改為 br $B0 則會顯示 1。

在分支指令方面,有個 br_if 可以進行條件分支,br_if 會從堆疊中取出一個數值,若為 0 不做什麼事,若不為 0 就依指定進行分支,因此,可以用來實現一個 unless(也就是 if 的相反):

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        block $UNLESS_BLOCK
            block $THEN
                block $UNLESS
                    i32.const 0 ;; unless 條件不成立
                    br_if $THEN
                end
                ;; unless 條件不成立執行的部份
                i32.const 10
                call $log               
                br $UNLESS_BLOCK
            end
            ;; unless 條件成立執行的部份
            i32.const 20
            call $log
        end
    )
    (start $main)
)

在上面的範例中,因為 i32.const 0br_if 不進行分支,繼續執行流程到 br $UNLESS_BLOCK,分支至 $UNLESS_BLOCKend 之後,因此不會執行到 $THENend 之後的部份,也就是結果顯示 10;若是改為 i32.const 1,結果就會顯示 20。

在多個區塊形成巢狀的情況下,也許會有複雜的分支流程,這時可以使用 br_table,它會從堆疊中取出一個數值,依數值決定要分支至哪個被列出的區塊。例如:

(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  ;; 依 $n 的值分支
                end
                i32.const 2
                call $log
            end
            i32.const 1
            call $log
        end
        i32.const 0
        call $log
    )
    (start $main)
)

$n 若為 0,就會分支至第一個列出的 $B2,若為 1 就分支至 $B1,依此類推。

若要進行條件判斷,WebAssembly 提供 if..else..endloop..end 條件分支,不必如上自行使用 block 來兜,實際上,if..else..endloop..end(以及 func)都會建立區塊,而 block 可以輔助 if..else..endloop..end 等,實現更多樣化的條件分支。