在 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
函式可以接受函式作為引數的概念。