Level 1 exception handling
Level 1 of exception handling is the MVP (minimal viable proposal) for implementing exceptions in WebAssembly. As such, it doesn't include higher-level concepts and structures. These concept and structures are introduced in later levels, and either:
- Improves readability by combining concepts in Level 1 into higher-level constructs, thereby reducing code size.
- Allow performance improvements in the VM.
- Introduce additional new functionality not available in Level 1.
This document supersedes the original Exceptions Proposal.
Exception handling allows code to break control flow when an exception is thrown. The exception can be any exception known by the WebAssembly module, or it may an unknown exception that was thrown by a called imported function.
One of the problems with exception handling is that both WebAssembly and an embedder have different notions of what exceptions are, but both must be aware of the other.
It is difficult to define exceptions in WebAssembly because (in general) it doesn't have knowledge of any embedder. Further, adding such knowledge to WebAssembly would limit the ability for other embedders to support WebAssembly exceptions.
One issue is that both sides need to know if an exception was thrown by the other, because cleanup may need to be performed.
Another problem is that WebAssembly doesn't have direct access to an embedder's memory. As a result, WebAssembly defers the handling of exceptions to the host VM.
To access exceptions, WebAssembly provides instructions to check if the exception is one that WebAssembly understands. If so, the data of the WebAssembly exception is extracted and copied onto the stack, allowing succeeding instructions to process the data.
Lastly, exception lifetimes may be maintained by the embedder, so that it can collect and reuse the memory used by exceptions. This implies that an embedder needs to know where exceptions are stored, so that it can determine when an exception can be garbage collected.
However, not all embedders may have a garbage collector. For this reason, WebAssembly exceptions are designed to allow other storage management methods, such as reference counting, to perform the garbage collection in the embedder.
To do this, WebAssembly exceptions are immutable once created, to avoid cyclic data structures that cannot be garbage collected. It also means that exceptions can't be stored into linear memory. The rationale for this is twofold:
For security. Loads and stores do not guarantee that the data read was of the same type as stored. This allows spoofing of exception references that may allow a WebAssembly module to access data it should not know in the host VM.
The embedder does not know the layout of data in linear memory, so it can't find places where exception references are stored.
Hence, while an exception reference is a new first class type, this proposal disallows their usage in linear memory.
A WebAssembly exception is created when you throw it with the
instruction. Thrown exceptions are handled as follows:
They can be caught by a catch block in an enclosing try block of a function body. The caught exception is pushed onto the stack.
Throws not caught within a function body continue up the call stack, popping call frames, until an enclosing try block is found.
If the call stack is exhausted without any enclosing try blocks, the embedder defines how to handle the uncaught exception.
This proposal adds exception handling to WebAssembly. Part of this proposal is to define a new section to declare exceptions. However, rather than limiting this new section to just defining exceptions, it defines a more general format that allows the declaration of other forms of events.
In general, an event handler allows one to process an event generated by a block of code. Events suspend the current execution and look for a corresponding event handler. If found, the corresponding event handler is run. Some event handlers may send values back to the suspended instruction, allowing the originating code to resume.
Exceptions are a special case of an event in that they never resume. Similarly, a throw instruction is the suspending event of an exception. The catch block associated with a try block defines how to handle the throw.
WebAssembly events (i.e. exceptions) are defined by a new
event section of a
WebAssembly module. The event section is a list of declared events associated
with the module.
Each event has an
attribute and a
type. Currently, the attribute can only
specify that the event is an exception. In the future, additional attribute
values may be added when other events are added to WebAssembly.
The type of an event is denoted by an index to a function signature defined in
type section. The parameters of the function signature defines the list of
values associated with the exception event. The result type must be 'void'.
event tag is a value to distinguish different events, while an
is a numeric name to refer to an (imported or defined) event tag within a module
(see event index space for details).
exception is an internal construct in WebAssembly . WebAssembly exceptions
are defined in the
event and import sections of a module. Each event (with an
exception attribute) defines an
exception. The event index is also called the
exception index. Similarly, the corresponding event tag is called an
Exception indices are used by:
throwinstruction which creates a WebAssembly exception with the corresponding exception tag, and then throws it.
if_exceptinstruction queries an exception to see if the corresponding exception tag denoted by the exception index. If true it pushes the corresponding values of the exception onto the stack.
The exception reference data type
Data types are extended to have a new
except_ref type, that refers to an
exception. The representation of an exception is left to the implementation.
Try and catch blocks
A try block defines a list of instructions that may need to process exceptions
and/or clean up state when an exception is thrown. Like other higher-level
constructs, a try block begins with a
try instruction, and ends with an
instruction. That is, a try block is sequence of instructions having the
try block_type instruction* catch instruction* end
A try block ends with a
catch block that is defined by the list of
instructions after the
Try blocks, like control-flow blocks, have a block type. The block type of a try block defines the values yielded by the evaluation the try block when either no exception is thrown, or the exception is successfully caught by the catch block.
In the initial implementation, try blocks may only yield 0 or 1 values.
Throwing an exception
throw instruction takes an exception index as an immediate argument. That
index is used to identify the exception tag to use to create and throw the
The values on top of the stack must correspond to the type associated with the exception. These values are popped of the stack and are used (along with the corresponding exception tag) to create the corresponding exception. That exception is then thrown.
When an exception is thrown, the embedder searches for the nearest enclosing try block body that execution is in. That try block is called the catching try block.
If the throw appears within the body of a try block, it is the catching try block.
If a throw occurs within a function body, and it doesn't appear inside the body of a try block, the throw continues up the call stack until it is in the body of an an enclosing try block, or the call stack is flushed. If the call stack is flushed, the embedder defines how to handle uncaught exceptions. Otherwise, the found enclosing try block is the catching try block.
A throw inside the body of a catch block is never caught by the corresponding try block of the catch block, since instructions in the body of the catch block are not in the body of the try block.
Once a catching try block is found for the thrown exception, the operand stack is popped back to the size the operand stack had when the try block was entered, and then an except_ref referring to the caught exception is pushed back onto the operand stack.
If control is transferred to the body of a catch block, and the last instruction in the body is executed, control then exits the try block.
If the selected catch block does not throw an exception, it must yield the value(s) expected by the corresponding catching try block. This includes popping the caught exception.
Note that a caught exception can be rethrown using the
Rethrowing an exception
rethrow instruction takes the exception associated with the
on top of the stack, and rethrows the exception. A rethrow has the same effect
as a throw, other than an exception is not created. Rather, the referenced
exception on top of the stack is popped and then thrown.
Exception data extraction
if_except block begins with an
if_except instruction, and
has two instruction blocks,
That is, the forms of an if_except block is:
if_except block_type except_index Instruction* end if_except block_type except_index Instruction* else Instruction* end
In the first form, the instructions between the
if_except and 'end' define the
then block. In the second form, the instructions between the
else define the
then block, while the instructions between the
end define the
The conditional query of an exception checks the exception tag of exception on top of the stack. It succeeds only if the exception index of the instruction matches the corresponding exception tag. Once the query completes, the exception is popped off the stack.
If the query succeeds the values (associated with the popped exception) are extracted and pushed onto the stack, and control transfers to the instructions in the then block.
If the query fails, it either enters the else block, or transfer control to the end of the if_except block if there is no else block.
When an exception is thrown, the runtime will pop the stack across function calls until a corresponding, enclosing try block is found. It may also associate a stack trace that can be used to report uncaught exceptions. However, the details of this is left to the embedder.
Changes to the text format.
This section describes change in the instruction syntax document.
The following rules are added to instructions:
try resulttype instruction* catch instruction* end | except except_index | throw except_index | rethrow | if_except resulttype except_index then instruction* end | if_except resulttype except_index then instruction* else instruction* end
if instructions, the
instructions are structured control flow instructions, and can be
labeled. This allows branch instructions to exit try and
except_index of the
if_except instructions defines the
exception (and hence, exception tag) to create/extract from. See
exception index space for further clarification of
Changes to Modules document.
This section describes change in the Modules document.
Event index space
event index space indexes all imported and internally-defined events,
assigning monotonically-increasing indices based on the order defined in the
import and event sections. Thus, the index space starts at zero with
imported events, followed by internally-defined events in the event
The event index space defines the (module) static version of runtine event tags. For event indices that are not imported/exported, the corresponding event tag is guaranteed to be unique over all loaded modules. Events that are imported or exported alias the respective events defined elsewhere, and use the same tag.
Changes to the binary model
This section describes changes in the binary encoding design document.
An exception reference points to an exception.
varint7 indicating a
value_type is extended to include
The set of event attributes are:
Each event type has the fields:
||The attribute of the event.|
||The type index for its corresponding type signature|
A single-byte unsigned integer indicating the kind of definition being imported or defined:
Functionimport or definition
Tableimport or definition
Memoryimport or definition
Globalimport or definition
Eventimport or definition
event section is introduced and is named
event. If included, it must
appear after immediately after the global section.
section section is the named section 'event'. The event section
declares a list of event types as follows:
||count of the number of events to follow|
||The definitions of the event types|
The import section is extended to include event definitions by extending an
import_entry as follows:
||the event being imported|
The export section is extended to reference event types by
export_entry as follows:
||the index into the corresponding event index space|
The set of known values for
name_type of a name section is extended as
||Assigns names to functions|
||Assigns names to locals in functions|
||Assigns names to event types|
The event names subsection is a
name_map which assigns names to a subset of
the event indices (Used for both imports and module-defined).
Control flow operators
The control flow operators are extended to define try blocks, catch blocks, throws, and rethrows as follows:
||begins a block which can handle thrown exceptions|
||begins the catch block of the try block|
||Creates an exception defined by the exception
||Begin exception data extraction if exception on stack was created using the corresponding exception
The sig fields of
if_except operators are block
signatures which describe their use of the operand stack.