間接呼叫


在 WebAssembly 中,對於儲存在表格中的函式,沒辦法直接取得,只能間接呼叫,方式是透過 call_indirect。例如:

(module
    (type $ft (func (param i32)))
    (import "env" "log" (func $log (param i32)))
    (table $tb 1 anyfunc)
    (func $f1 (param $p i32)
        (i32.add (get_local $p) (i32.const 10))
        call $log
    )
    (elem (i32.const 0) $f1)
    (func $main
        i32.const 10
        i32.const 0
        call_indirect (type $ft)
    )
    (start $main)
)

call_indirect 必須指定函式的型態,為了要能指定型態,自行定義 type 並指定名稱,會是比較方便的做法。call_indirect 會從堆疊中取得一個數值,作為索引得知要使用函式表中哪個函式,接著會依函式型態取出對應的引數,然後執行函式。

從某些角度來看,函式表是一種匯入、匯出函式的方式,而且透過 JavaScript,函式表可以在模組之間共用,而且可以透過 Table API 來改變函式表的內容,因而就可以令模組在執行時期,也能動態地改變呼叫的實際函式。

例如,可以準備一個利率模組,其中有兩種不同的利率函式:

(module
    (func $rate1 (result f32)
        f32.const 0.015
    )
    (func $rate2 (result f32)
        f32.const 0.025
    )        
    (export "rate1" (func $rate1))
    (export "rate2" (func $rate2))
)

接著,計算利息的模組,使用間接呼叫的方式來呼叫利率函式,並乘上本金:

(module
    (type $rate (func (result f32)))
    (import "env" "tb" (table $tb 1 anyfunc))
    (func $interest (param $money f32) (result f32)
        (f32.mul
            (call_indirect (type $rate) (i32.const 0))
            (get_local $money)
        ) 
    )
    (export "interest" (func $interest))
)

在 JavaScript 的部份,建立 WebAssembly.Table 實例,並可以動態地調整利率函式:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <script>

    const rtb = new WebAssembly.Table({initial:1, element:"anyfunc"});
    const importObj = {
        env: {rtb}
    };

    Promise.all([
        WebAssembly.instantiateStreaming(fetch('rate.wasm')),
        WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj)
    ])
    .then(wasms => {
        const rate = wasms[0].instance;
        const prog = wasms[1].instance;

        rtb.set(0, rate.exports.rate1);
        console.log(prog.exports.interest(10000)); // 顯示 150

        rtb.set(0, rate.exports.rate2);
        console.log(prog.exports.interest(10000)); // 顯示 250
    });
    </script>
  </body>
</html>

函式表的另一個作用是,可實現函式指標的概念,例如:

(module
    (import "env" "log" (func $log (param f32)))
    (type $rate (func (result f32)))
    (table $rtb 2 anyfunc)
    (func $rate1 (result f32)
        f32.const 0.015
    )
    (func $rate2 (result f32)
        f32.const 0.025
    )
    (elem (i32.const 0) $rate1 $rate2)
    (func $interest (param $money f32) (param $rf i32) (result f32)
        (f32.mul
            (call_indirect (type $rate) (get_local $rf))
            (get_local $money)
        ) 
    )
    (func $main
        (local $rf i32)

        (call $interest (f32.const 10000) (get_local $rf))
        call $log  ;; 顯示 150

        (set_local $rf (i32.const 1))

        (call $interest (f32.const 10000) (get_local $rf))
        call $log  ;; 顯示 250
    )
    (start $main)
)

在這邊,函式表中儲存了兩個函式,而 $interest 函式的第二個參數,可以指定間接呼叫時的函式索引,這就像是 $interest 函式可以接受函式作為引數的概念。