In Hello Module, you've known that Wat can use type
to define the Type section; however, the Type section is often generated automatically according to the content of Wat. For example, given a simple function.
(module
(func $main)
)
After using wasm2wat
, you have a wat file.
(module
(type (;0;) (func))
(func (;0;) (type 0)))
Functions with the same signature will have the same type. If every function has a type, is it a value? Can it be stored?
You cannot push a function onto the stack or set to a variable. Use table
to define a table. You can store function references in a table. In the MVP, a module can only define a table.
(module
(table $tb 2 anyfunc)
...
)
In (table $tb 2 anyfunc)
, the 2 is the initial size of the table meaning it will store two references. An optional number can be put after the initial size to limit the maximum size. anyfunc
means “any function signature” and is the only valid element type in the MVP. More element types might be added in the future.
You can use elem
to store function references in a table. For example:
(module
(import "env" "log" (func $log (param i32)))
(table $tb 2 anyfunc)
(func $f1 (param $p i32)
(i32.add (get_local $p) (i32.const 10))
call $log
)
(func $f2 (param $p i32)
(i32.add (get_local $p) (i32.const 20))
call $log
)
(elem (i32.const 0) $f1 $f2)
...
)
(i32.const 0)
is the offset from the index 0 so $f1
would be stored at index 0 and $f2
is at 1. Exporting a table is allowed if you want. For example:
(module
(import "env" "log" (func $log (param i32)))
(table $tb 2 anyfunc)
(func $f1 (param $p i32)
(i32.add (get_local $p) (i32.const 10))
call $log
)
(func $f2 (param $p i32)
(i32.add (get_local $p) (i32.const 20))
call $log
)
(elem (i32.const 0) $f1 $f2)
(export "tb" (table $tb))
)
The exported table is an instance of WebAssembly.Table
. You can pass an index to the get
method to get the function reference.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
const importObj = {
env: {
log(n) {
console.log(n);
}
}
};
WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj)
.then(prog => {
const f1 = prog.instance.exports.tb.get(0);
const f2 = prog.instance.exports.tb.get(1);
f1(10);
f2(10);
});
</script>
</body>
</html>
You can create a WebAssembly.Table
instance, store function references and then import the table into WebAssembly modules. Only WebAssembly functions can be stored in the table.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
const tb = new WebAssembly.Table({initial:2, element:"anyfunc"});
const importObj = {
env: {
log(n) {
console.log(n);
},
tb : tb
}
};
WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj)
.then(_ => {
let f1 = tb.get(0);
let f2 = tb.get(1);
f1(10);
f2(10);
tb.set(0, f2); // only WebAssembly functions can be stored in the table
tb.set(1, f1);
f1 = tb.get(0);
f2 = tb.get(1);
f1(10);
f2(10);
});
</script>
</body>
</html>
In the above script, you create WebAssembly.Table
and import to the module. After initializing the module which stores function in the table, you invoke functions in JavaScript. The module is shown below.
(module
(import "env" "log" (func $log (param i32)))
(import "env" "tb" (table $tb 2 anyfunc))
(func $f1 (param $p i32)
(i32.add (get_local $p) (i32.const 10))
call $log
)
(func $f2 (param $p i32)
(i32.add (get_local $p) (i32.const 20))
call $log
)
(elem (i32.const 0) $f1 $f2)
)
Since storing functions in WebAssembly.Table
is allowed and multiple instances can share the same table, run-time dynamic linking of multiple modules is possible.
As for how to invoke functions stored in a table, I'll talk in the later document.