try_table: Wasm exception handling instruction
The try_table exception handling instruction enables you to test a block of code to see whether it throws an exception, handling the exception with a catch clause if so.
Try it
(module
;; Import error tag and console.log
(tag $my_error (import "env" "my_error") (param i32))
(import "env" "log" (func $log (param i32)))
(func $try_and_catch (param $value i32)
(block $handler (result i32)
;; In try_table block, catch thrown exception
(try_table (catch $my_error $handler)
(call $might_throw (local.get $value))
)
(return)
)
;; Log value returned by handler block
call $log
)
;; Function that throws an error of type $my_error
;; when its parameter is less than 0
(func $might_throw (param $value i32)
(local.get $value)
(i32.const 0)
(i32.lt_s)
(if
(then
;; Throw exception with payload of 42
(i32.const 42)
(throw $my_error)
)
)
)
(export "try_and_catch" (func $try_and_catch))
)
// Define error tag in JS
const myErrorTag = new WebAssembly.Tag({ parameters: ["i32"] });
// Import error tag and console.log into the module
const env = {
my_error: myErrorTag, // import the tag into the module
log: console.log,
};
WebAssembly.instantiateStreaming(fetch("{%wasm-url%}"), { env }).then(
// Negative value causes function to throw
(result) => result.instance.exports.try_and_catch(-1),
);
Syntax
try_table blocktype catch* instruction*
try_table-
The
try_tableinstruction. blocktypeOptional-
Specifies one or more parameters that will be passed into the
try_tableblock and provided as a result value after the block has run. catch*-
One or more
catchclauses, each representing criteria for catching exceptions, and specifying ablockto branch to as a result. Each clause can be one of the following: instruction*-
Zero or more instructions to execute inside the try block.
Type
[param*] -> [result*]
Binary encoding
| Instruction | Binary format |
|---|---|
try_table |
0x1f bt:blocktype n:u32 (ct:catch)^n instruction* 0x0b |
A basic try_table with a single catch clause:
(try_table (catch $my_error $handler)
;; instructions ...
)
Would be encoded like so:
0x1f 0x40 0x01 0x00 0x00 0x00 ...instructions binary... 0x0b
Description
A try_table instruction, when combined with catch clauses, creates the Wasm equivalent of a JavaScript try...catch statement. The instructions inside the try_table block are run, and if an exception is thrown that is caught by the available catch clauses, the code branches to the specified outer block, and the values produced by the catch clause are pushed onto the stack.
The different catch clauses behave as follows:
catch-
If an exception with a matching tag is thrown, branch to the specified
block, pushing the payload values onto the stack. catch_all-
If any exception is thrown, branch to the specified
block, pushing nothing onto the stack. catch_ref-
If an exception with a matching tag is thrown, branch to the specified
block, pushing the payload values and anexnrefvalue representing the exception onto the stack. catch_all_ref-
If any exception is thrown, branch to the specified
block, pushing anexnrefvalue representing the exception onto the stack.
Each catch clause that branches to an outer block must produce values matching that block's result type when a thrown exception is caught.
Blocktype parameters
The optional blocktype parameters will be passed into the try_table block and provided as a result value after the block has run. The value can be specified before the try_table block, or inside. For example:
;; Push an i32
i32.const 42
;; pops an i32 as the param
try_table (param i32)
;; The single i32 const 42 is still on the stack
end
Or:
try_table (result i32)
;; Push an i32
i32.const 42
;; The end of the block pops the results
end
;; The result i32 is now available to be used here
Or you can use any combination of these structures.
Examples
>Handling multiple exceptions
This example shows how to handle multiple exceptions in a single try_table structure.
JavaScript
In our script, we start by grabbing a reference to a <p> element that we will output results to. We then define two different error tags to represent a type error and a range error using the WebAssembly.Tag() constructor.
const output = document.querySelector("p");
const typeErrorTag = new WebAssembly.Tag({ parameters: ["i32"] });
const rangeErrorTag = new WebAssembly.Tag({ parameters: ["i32", "i32"] });
Next, we compile and instantiate our Wasm module using the WebAssembly.instantiateStreaming() method, importing the two error tags and a function to log results to the <p> element.
We invoke the exported Wasm try_multiple() function available on the WebAssembly Instance exports object multiple times, passing it two different parameters to throw different exceptions, and then finally one value that doesn't throw an exception.
async function init() {
const { instance } = await WebAssembly.instantiateStreaming(
fetch("{%wasm-url%}"),
{
env: {
type_error: typeErrorTag,
range_error: rangeErrorTag,
log: (code) => {
output.textContent += `Error code: ${code} | `;
},
},
},
);
instance.exports.try_multiple(-1); // Throws type_error, logs 10
instance.exports.try_multiple(101); // Throws range_error, logs 99
instance.exports.try_multiple(50); // Doesn't throw
}
init();
Wasm
In our Wasm module, we first import our two error tags and log function. We then create a function called $try_multiple that has two nested blocks to handle $type_errors and $range_errors, respectively. In the middle of the blocks is a try_table structure containing two catch clauses, one to catch each type of error. We then call the $might_throw function defined later on to see whether it throws any exceptions:
- If a
$type_erroris thrown, we branch to the$on_type_errorblockand log the payload value. - If a
$range_erroris thrown, we branch to the$on_range_errorblock, drop the first of the two payload values and log the second one, then return out of theblock. - If no exception is thrown, we just return out of the
block.
The $might_throw function itself takes a single parameter, and checks its value. If the value is less than 0, it throws a $type_error with error code 10. If the value is greater than 100, it throws a $range_error with code 99.
(module
(tag $type_error (import "env" "type_error") (param i32))
(tag $range_error (import "env" "range_error") (param i32 i32)) ;; carries two values
(import "env" "log" (func $log (param i32)))
(func $try_multiple (param $value i32)
(block $on_type_error (result i32)
(block $on_range_error (result i32 i32)
(try_table
(catch $type_error $on_type_error)
(catch $range_error $on_range_error)
(call $might_throw (local.get $value))
)
(return) ;; no exception thrown
)
;; $range_error was caught — stack has i32 i32
(drop) ;; drop second payload value
(call $log) ;; log first payload value
(return)
)
;; $type_error was caught — stack has i32
(call $log) ;; log the payload
)
(func $might_throw (param $value i32)
(local.get $value)
(i32.const 0)
(i32.lt_s)
(if
(then
(i32.const 10)
(throw $type_error)
)
)
(local.get $value)
(i32.const 100)
(i32.gt_s)
(if
(then
(i32.const 99)
(i32.const 100)
(throw $range_error)
)
)
)
(export "try_multiple" (func $try_multiple))
)
Result
We call the try_multiple() function three times. The first time throws a $type_error, so error code 10 is logged. The second throws a $range_error, so error code 99 is logged. The third does not throw an exception.