|
| 1 | +# Layer 1 exception handling |
| 2 | + |
| 3 | +Layer 1 of exception handling is the MVP (minimal viable proposal) for |
| 4 | +implementing exceptions in WebAssembly. As such, it doesn't include higher-level |
| 5 | +concepts and structures. These concept and structures are introduced in later |
| 6 | +layers, and either: |
| 7 | + |
| 8 | +1. Improves readability by combining concepts in layer 1 into higher-level |
| 9 | + constructs, thereby reducing code size. |
| 10 | + |
| 11 | +2. Allow performance improvements in the VM. |
| 12 | + |
| 13 | +3. Introduce additional new functionality not available in layer 1. |
| 14 | + |
| 15 | +## Overview |
| 16 | + |
| 17 | +Exception handling allows code to break control flow when an exception is |
| 18 | +thrown. The exeception can be any exception known by the WebAssembly module, or |
| 19 | +it may an unknown exception that was thrown by a called imported function. |
| 20 | + |
| 21 | +One of the problems with exception handling is that both WebAssembly and the |
| 22 | +host VM probably have different notions of what exceptions are, but both must be |
| 23 | +aware of the other. |
| 24 | + |
| 25 | +It is difficult to define exceptions in WebAssembly because (in general) |
| 26 | +it doesn't have knowledge of the host VM. Further, adding such knowledge to |
| 27 | +WebAssembly would limit the ability for other host VMs to support WebAssembly |
| 28 | +exceptions. |
| 29 | + |
| 30 | +One issue is that both sides need to know if an exception was thrown by the |
| 31 | +other, because cleanup may need to be performed. |
| 32 | + |
| 33 | +Another problem is that WebAssembly doesn't have direct access to the host VM's |
| 34 | +memory. As a result, WebAssembly defers the handling of exceptions to the host |
| 35 | +VM. |
| 36 | + |
| 37 | +To access exceptions, WebAssembly provides instructions to check if the |
| 38 | +exception is one that WebAssembly understands. If so, the data of the |
| 39 | +WebAssembly exceptions's data is extracted and copied onto the stack, allowing |
| 40 | +succeeding instructions to process the data. |
| 41 | + |
| 42 | +Lastly, exception lifetimes must be maintained by the host VM, so that it can |
| 43 | +collect and reuse the memory used by exceptions. This implies that the host must |
| 44 | +know where exceptions are stored, so that it can determine when an exception can |
| 45 | +be garbage collected. |
| 46 | + |
| 47 | +This also implies that the host VM must provide a garbage collector for |
| 48 | +exceptions. For host VMs that have garbage collection (such as JavaScript), |
| 49 | +this is not a problem. |
| 50 | + |
| 51 | +However, not all host VMs may have a garbage collector. For this reason, |
| 52 | +WebAssembly exceptions are designed to allow the use of reference counters to |
| 53 | +perform the the garbage collection in the host VM. |
| 54 | + |
| 55 | +To do this, WebAssembly exceptions are immutable once created, to avoid cyclic |
| 56 | +data structures that can't be garbage collected. It also means that exceptions |
| 57 | +can't be stored into linear memory. The rationale for this is twofold: |
| 58 | + |
| 59 | +* For security. Loads |
| 60 | + and stores do not guarantee that the data read was of the same type as |
| 61 | + stored. This allows spoofing of exception references that may allow a |
| 62 | + WebAssembly module to access data it should not know in the host VM. |
| 63 | + |
| 64 | +* The host VM does not know the layout of data in linear memory, so it can't |
| 65 | + find places where exception references are stored. |
| 66 | + |
| 67 | +Hence, while an exception reference is a new first class type, this proposal |
| 68 | +disallows their usage in linear memory. |
| 69 | + |
| 70 | +A WebAssembly exception is created by the `except` instruction. Once an |
| 71 | +exception is created, you can throw it with the `throw` instruction. Thrown |
| 72 | +exceptions are handled as follows: |
| 73 | + |
| 74 | +1. They can be caught by a catch block in an enclosing try block of a function |
| 75 | + body. The caught exception is pushed onto the stack. |
| 76 | + |
| 77 | +1. Throws not caught within a function body continue up the call stack, popping |
| 78 | + call frames, until an enclosing try block is found. |
| 79 | + |
| 80 | +1. If the call stack is exhausted without any enclosing try blocks, the host VM |
| 81 | + defines how to handle the uncaught exception. |
| 82 | + |
| 83 | +### Exceptions |
| 84 | + |
| 85 | +An `exception` is an internal construct in WebAssembly that is maintained by the |
| 86 | +host. WebAssembly exceptions (as opposed to host exceptions) are defined by a |
| 87 | +new `exception section` of a WebAssembly module. The exception section is a list |
| 88 | +of exception types, from which exceptions can be created. |
| 89 | + |
| 90 | +Each exception type has a `type signature`. The type signature defines the list |
| 91 | +of values associated with the exception. |
| 92 | + |
| 93 | +Within the module, exception types are identified by an index into the |
| 94 | +[exception index space](#exception-index-space). This index is referred to as |
| 95 | +the `exception tag`. The `tagged exception type` is the corresponding |
| 96 | +exception type refered to by the exception tag. |
| 97 | + |
| 98 | +Exception types can be imported and exported by adding the appropriate entries |
| 99 | +to the import and export sections of the module. All imported/exported exception |
| 100 | +types must be named to reconcile exception tags between modules. |
| 101 | + |
| 102 | +Exception tags are used by: |
| 103 | + |
| 104 | +1. The `except` instruction which creates a WebAssembly instance of the |
| 105 | + corresponding tagged exception type, and pushes a reference to it onto the |
| 106 | + stack. |
| 107 | + |
| 108 | +2. The `if_except` instruction that queries an exception to see if it is an |
| 109 | + instance of the corresponding tagged exception class, and if true it pushes |
| 110 | + the corresponding values of the exception onto the stack. |
| 111 | + |
| 112 | +### The exception refernce data type |
| 113 | + |
| 114 | +Data types are extended to have a new `except_ref` type. The representation of |
| 115 | +an exception type is left to the host VM, but its size must be fixed. The actual |
| 116 | +number of bytes it takes to represent any `except_ref` is left to the host VM. |
| 117 | + |
| 118 | +### Try and catch blocks |
| 119 | + |
| 120 | +A _try block_ defines a list of instructions that may need to process exceptions |
| 121 | +and/or clean up state when an exception is thrown. Like other higher-level |
| 122 | +constructs, a try block begins with a `try` instruction, and ends with an `end` |
| 123 | +instruction. That is, a try block is sequence of instructions having the |
| 124 | +following form: |
| 125 | + |
| 126 | +``` |
| 127 | +try block_type |
| 128 | + instruction* |
| 129 | +catch |
| 130 | + instruction* |
| 131 | +end |
| 132 | +``` |
| 133 | + |
| 134 | +A try block ends with a `catch block` that is defined by the list of |
| 135 | +instructions after the `catch` instruction. |
| 136 | + |
| 137 | +Try blocks, like control-flow blocks, have a _block type_. The block type of a |
| 138 | +try block defines the values yielded by the evaluation the try block when either |
| 139 | +no exception is thrown, or the exception is successfully caught by the catch |
| 140 | +block. |
| 141 | + |
| 142 | +In the initial implementation, try blocks may only yield 0 or 1 values. |
| 143 | + |
| 144 | +### Exception creation |
| 145 | + |
| 146 | +A `except` instruction has a single immediate argument, an exception tag. The |
| 147 | +corresponding tagged exception type is used to define the data fields of the |
| 148 | +created exception. The values for the data fields must be on top of the operand |
| 149 | +stack, and must correspond to the exception's type signature. These values are |
| 150 | +popped off the stack and an instance of the exception is then created. A |
| 151 | +reference to the created exception is then pushed onto the stack. |
| 152 | + |
| 153 | +### Throws |
| 154 | + |
| 155 | +The `throw` throws the exception on top of the stack. The exception is popped |
| 156 | +off the top of the stack before throwing. |
| 157 | + |
| 158 | +When an exception is thrown, the host VM searches for nearest enclosing try |
| 159 | +block body that execution is in. That try block is called the _catching_ try |
| 160 | +block. |
| 161 | + |
| 162 | +If the throw appears within the body of a try block, it is the catching try |
| 163 | +block. |
| 164 | + |
| 165 | +If a throw occurs within a function body, and it doesn't appear inside the body |
| 166 | +of a try block, the throw continues up the call stack until it is in the body of |
| 167 | +an an enclosing try block, or the call stack is flushed. If the call stack is |
| 168 | +flushed, the host VM defines how to handle uncaught exceptions. Otherwise, the |
| 169 | +found enclosing try block is the catching try block. |
| 170 | + |
| 171 | +A throw inside the body of a catch block is never caught by the corresponding |
| 172 | +try block of the catch block, since instructions in the body of the catch block |
| 173 | +are not in the body of the try block. |
| 174 | + |
| 175 | +Once a catching try block is found for the throw, the operand stack is popped back |
| 176 | +to the size the operand stack had when the try block was entered, and then |
| 177 | +the caught exception is pushed onto the stack. |
| 178 | + |
| 179 | +If control is transferred to the body of a catch block, and the last instruction |
| 180 | +in the body is executed, control then exits the try block. |
| 181 | + |
| 182 | +If the selected catch block does not throw an exception, it must yield the |
| 183 | +value(s) expected by the corresponding catching try block. This includes popping |
| 184 | +the caught exception. |
| 185 | + |
| 186 | +Note that a caught exception can be rethrown using the `throw` instruction. |
| 187 | + |
| 188 | +### Exception data extraction |
| 189 | + |
| 190 | +The `if_except block` defines a conditional query of the exception on top of the |
| 191 | +stack. The exception is not popped when queried. The if_except block has two |
| 192 | +subblocks, the `then` and `else` subblocks, like that of an `if` block. The then |
| 193 | +block is a sequence of instructions following the `if_except` instruction. The |
| 194 | +else block is optional, and if it appears, it begins with the `else` |
| 195 | +instruction. The scope of the if_except block is from the `if_except` |
| 196 | +instruction to the corresponding `end` instruction. |
| 197 | + |
| 198 | +That is, the forms of an if_except block is: |
| 199 | + |
| 200 | +``` |
| 201 | +if_except block_type except_index |
| 202 | + Instruction* |
| 203 | +end |
| 204 | +
|
| 205 | +if_except block_type except_index |
| 206 | + Instruction* |
| 207 | +else |
| 208 | + Instructions* |
| 209 | +end |
| 210 | +``` |
| 211 | + |
| 212 | +The conditional query of an exception succeeds when the exception on the top of |
| 213 | +the stack is an instance of the corresponding tagged exception type (defined by |
| 214 | +`except_index`). |
| 215 | + |
| 216 | +If the query succeeds, the data values (associated with the type signature of |
| 217 | +the exception class) are extracted and pushed onto the stack, and control |
| 218 | +transfers to the instructions in the then block. |
| 219 | + |
| 220 | +If the query fails, it either enters the else block, or transfer control to the |
| 221 | +end of the if_except block if there is no else block. |
| 222 | + |
| 223 | +### Debugging |
| 224 | + |
| 225 | +Earlier discussion implied that when an exception is thrown, the runtime will |
| 226 | +pop the operand stack across function calls until a corresponding, enclosing try |
| 227 | +block is found. The implementation may actually not do this. Rather, it may |
| 228 | +first search up the call stack to see if there is an enclosing try. If none are |
| 229 | +found, it could terminate the thread at the point of the throw. This would |
| 230 | +allow better debugging capability, since the corresponding call stack is still |
| 231 | +there to query. |
| 232 | + |
| 233 | +## Changes to the text format. |
| 234 | + |
| 235 | +This section describes change in the |
| 236 | +[instruction syntax document](https://github.com/WebAssembly/spec/blob/master/document/core/instructions.rst). |
| 237 | + |
| 238 | +### New instructions |
| 239 | + |
| 240 | +The following rules are added to *instructions*: |
| 241 | + |
| 242 | +``` |
| 243 | + try resulttype instructions* catch instructions* end | |
| 244 | + except except_index | |
| 245 | + throw | |
| 246 | + if_except resulttype except_index then Instructions* end | |
| 247 | + if_except resulttype except_index then Instructions* else Instructions* end |
| 248 | +``` |
| 249 | + |
| 250 | +Like the `block`, `loop`, and `if` instructions, the `try` and `if_except` |
| 251 | +instructions are *structured* control flow instructions, and can be |
| 252 | +labeled. This allows branch instructions to exit try and `if_except` blocks. |
| 253 | + |
| 254 | +The `except_index` of the `except` and `if_except` instructions defines the |
| 255 | +exception type to create/extract form. See [exception index |
| 256 | +space](#exception-index-space) for further clarification of exception tags. |
| 257 | + |
| 258 | +## Changes to Modules document. |
| 259 | + |
| 260 | +This section describes change in the |
| 261 | +[Modules document](https://github.com/WebAssembly/design/blob/master/Modules.md). |
| 262 | + |
| 263 | +### Exception index space |
| 264 | + |
| 265 | +The _exception index space_ indexes all imported and internally-defined |
| 266 | +exceptions, assigning monotonically-increasing indices based on the order |
| 267 | +defined in the import and exception sections. Thus, the index space starts at |
| 268 | +zero with imported exceptions followed by internally-defined exceptions in |
| 269 | +the [exception section](#exception-section). |
| 270 | + |
| 271 | +## Changes to the binary model |
| 272 | + |
| 273 | +This section describes changes in |
| 274 | +the |
| 275 | +[binary encoding design document](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md). |
| 276 | + |
| 277 | + |
| 278 | +### Data Types |
| 279 | + |
| 280 | +#### except_ref |
| 281 | + |
| 282 | +An exception reference pointing to an instance of an exception. The size |
| 283 | +is fixed, but unknown in WebAssembly (the host defines the size in bytes). |
| 284 | + |
| 285 | +### Language Types |
| 286 | + |
| 287 | +| Opcode | Type constructor | |
| 288 | +|--------|------------------| |
| 289 | +| -0x41 | `except_ref` | |
| 290 | + |
| 291 | +#### value_type |
| 292 | + |
| 293 | +A `varint7` indicating a a `value type` is extended to include `except_ref` as |
| 294 | +encoded above. |
| 295 | + |
| 296 | +#### Other Types |
| 297 | + |
| 298 | +##### except_type |
| 299 | + |
| 300 | +An exception is described by its exception type signature, which corresponds to |
| 301 | +the data fields of the exception. |
| 302 | + |
| 303 | +| Field | Type | Description | |
| 304 | +|-------|------|-------------| |
| 305 | +| `count` | `varuint32` | The number of types in the signature | |
| 306 | +| `type` | `value_type*` | The type of each element in the signature | |
| 307 | + |
| 308 | + |
| 309 | +##### external_kind |
| 310 | + |
| 311 | +A single-byte unsigned integer indicating the kind of definition being imported |
| 312 | +or defined: |
| 313 | + |
| 314 | +* `0` indicating a `Function` [import](Modules.md#imports) or [definition](Modules.md#function-and-code-sections) |
| 315 | +* `1` indicating a `Table` [import](Modules.md#imports) or [definition](Modules.md#table-section) |
| 316 | +* `2` indicating a `Memory` [import](Modules.md#imports) or [definition](Modules.md#linear-memory-section) |
| 317 | +* `3` indicating a `Global` [import](Modules.md#imports) or [definition](Modules.md#global-section) |
| 318 | +* `4` indicating an `Exception` [import](#import-section) or [definition](#exception-sectio) |
| 319 | + |
| 320 | +### Module structure |
| 321 | + |
| 322 | +#### High-level structure |
| 323 | + |
| 324 | +A new `exception` section is introduced and is named `exception`. If included, |
| 325 | +it must appear between the `Export` and `Start` sections of the module. |
| 326 | + |
| 327 | + |
| 328 | +##### Exception section |
| 329 | + |
| 330 | +The `exception` section is the named section 'exception'. The exception section |
| 331 | +declares exception types using exception type signatures. |
| 332 | + |
| 333 | +| Field | Type | Description | |
| 334 | +|-------|------|-------------| |
| 335 | +| count | `varuint32` | count of the number of exceptions to follow | |
| 336 | +| sig | `except_type*` | The type signature of the data fields for each exception | |
| 337 | + |
| 338 | + |
| 339 | +##### Import section |
| 340 | + |
| 341 | +The import section is extended to include exception types by extending an |
| 342 | +`import_entry` as follows: |
| 343 | + |
| 344 | +If the `kind` is `Exception`: |
| 345 | + |
| 346 | +| Field | Type | Description | |
| 347 | +|-------|------|-------------| |
| 348 | +| `sig` | `except_type` | the type signature of the exception | |
| 349 | + |
| 350 | +##### Export section |
| 351 | + |
| 352 | +The export section is extended to include exception types by extending an |
| 353 | +`export_entry` as follows: |
| 354 | + |
| 355 | +If the `kind` is `Exception`, then the `index` is into the corresponding |
| 356 | +exception in the [exception index space](#exception-index-space). |
| 357 | + |
| 358 | + |
| 359 | +##### Name section |
| 360 | + |
| 361 | +The set of known values for `name_type` of a name section is extended as |
| 362 | +follows: |
| 363 | + |
| 364 | +| Name Type | Code | Description | |
| 365 | +| --------- | ---- | ----------- | |
| 366 | +| [Function](#function-names) | `1` | Assigns names to functions | |
| 367 | +| [Local](#local-names) | `2` | Assigns names to locals in functions | |
| 368 | +| [Exception](#exception-names) | `3` | Assigns names to exception types | |
| 369 | + |
| 370 | +###### Exception names |
| 371 | + |
| 372 | +The exception names subsection is a `name_map` which assigns names to a subset |
| 373 | +of the _exception_ indices from the exception section. (Used for both imports |
| 374 | +and module-defined). |
| 375 | + |
| 376 | +### Control flow operators |
| 377 | + |
| 378 | +The control flow operators are extended to define try blocks, catch blocks, |
| 379 | +throws, and rethrows as follows: |
| 380 | + |
| 381 | +| Name | Opcode | Immediates | Description | |
| 382 | +| ---- | ---- | ---- | ---- | |
| 383 | +| `try` | `0x06` | sig : `block_type` | begins a block which can handle thrown exceptions | |
| 384 | +| `catch` | `0x07` | | begins the catch block of the try block | |
| 385 | +| `throw` | `0x08` | |Throws the exception on top of the stack | |
| 386 | +| `except` | `0x09` | tag : varuint32 | Creates an exception defined by the exception tag and pushes reference on stack | |
| 387 | +| `if_except` | `0x0a` | sig : `block_type` , tag : `varuint32` | Begin exception data extraction if exception on stack was created using the corresponding exception tag | |
| 388 | + |
| 389 | +The *sig* fields of `block`, `if`, `try` and `if_except` operators are block |
| 390 | +signatures which describe their use of the operand stack. |
0 commit comments