From 2b94fb06e457a5906f62298eed28d9ae0b89a91a Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 6 Oct 2020 16:40:07 -0700 Subject: [PATCH 01/17] Update explainer for the new spec This updates the explainer text according to the new spec we agreed in the 09-15-2020 CG meeting. Some of the text is taken from [the first version](https://github.com/WebAssembly/exception-handling/blob/master/proposals/old/Exceptions-v1.md) of the proposal. I renamed `catch_br` to `delegate` based on the perceived consensus in #133. There are some minor issues that need clarification, some of which is currently being discussed in the repo: - `rethrow`'s immmediate (or label) argument. I made it match the status in the first proposal for now. I believe keeping it makes it more versatile and lets us handle more languages. - It is not clear whether `rethrow` and `delegate`'s label (=immediate) argument should count non-try block-like structures, such as blocks and loops. It is currently being discussed for `delegate`. - `delegate`'s opcode should be decided. We exhausted all control-flow opcodes that begin with `0x0`. --- proposals/Exceptions.md | 365 +++++++++++++++++++++------------------- 1 file changed, 188 insertions(+), 177 deletions(-) diff --git a/proposals/Exceptions.md b/proposals/Exceptions.md index d09cce1e..c9671e58 100644 --- a/proposals/Exceptions.md +++ b/proposals/Exceptions.md @@ -1,29 +1,8 @@ # Exception handling -There were two alternative proposals -([1st](https://github.com/WebAssembly/exception-handling/blob/master/proposals/old/Exceptions.md) -and -[2nd](https://github.com/WebAssembly/exception-handling/blob/master/proposals/old/Level-1.md)) -for the design of exception handling and we -[decided](https://github.com/WebAssembly/meetings/blob/master/2018/TPAC.md#exception-handling-ben-titzer) -on the second proposal, which uses first-class exception types, mainly based on -the reasoning that it is more expressive and also more extendible to other kinds -of events. - -This proposal requires the following proposals as prerequisites. - -- The [reference types proposal](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), - since the [`exnref`](#the-exception-reference-data-type) type should be - represented as a subtype of `anyref`. - -- The [multi-value proposal](https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md), - since otherwise the [`br_on_exn`](#exception-data-extraction) instruction - would only work with exceptions that contain one value. Moreover, by using - [multi-value](https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md), - the [`try` blocks](#try-and-catch-blocks) may use values already in the stack, - and also push multiple values onto the stack. - ---- +This explainer reflects the up-to-date version of the exception handling +proposal agreed on [the CG meeting on +09-15-2020](https://github.com/WebAssembly/meetings/blob/master/main/2020/CG-09-15.md). ## Overview @@ -52,42 +31,11 @@ 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. - -This also implies that embedders must provide a garbage collector for -exceptions. For embedders that have garbage collection (such as JavaScript), -this is not a problem. - -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 easily be reference-counted. 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. The exception reference type can be -represented as a subtype of `anyref` type introduced in [WebAssembly reference -type -proposal](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md). - A WebAssembly exception is created when you throw it with the `throw` instruction. Thrown exceptions are handled as follows: -1. 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. +1. They can be caught by one of `catch`/`catch_all`/`unwind` blocks in an + enclosing try block of a function body. 1. Throws not caught within a function body continue up the call stack, popping call frames, until an enclosing try block is found. @@ -109,8 +57,8 @@ 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. +a throw instruction is the suspending event of an exception. The catch or unwind +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 @@ -143,34 +91,53 @@ Exception indices are used by: 1. The `throw` instruction which creates a WebAssembly exception with the corresponding exception tag, and then throws it. -2. The `br_on_exn` instruction queries an exception to see if it matches the - corresponding exception tag denoted by the exception index. If true it - branches to the given label and pushes the corresponding argument values of - the exception onto the stack. +2. The `catch` instruction uses the tag to identify if the thrown exception is + one it can catch. If true it pushes the corresponding argument values of the + exception onto the stack. -### The exception reference data type - -Data types are extended to have a new `exnref` type, that refers to an -exception. The representation of an exception is left to the implementation. - -### Try and catch blocks +### Try-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 `end` -instruction. That is, a try block is sequence of instructions having the +instruction. That is, a try-catch block is sequence of instructions having the following form: ``` try blocktype instruction* -catch +catch i + instruction* +catch j + instruction* +... +catch_all instruction* end ``` -A try block ends with a `catch block` that is defined by the list of -instructions after the `catch` instruction. +A try-catch block contains zero or more `catch` blocks and zero or one +`catch_all` block. All `catch` blocks should precede the `catch_all` block, if +any. The `catch`/`catch_all` instructions (within the try construct) are called +the _catching_ instructions. There should be at least `catch` or `catch_all` +block within a try-catch block. + +The _body_ of the try block is the list of instructions before the first +catching instruction. The _body_ of each catch block is the sequence of +instructions following the corresponding catching instruction before the next +catching instruction (or the `end` instruction if it is the last catching +block). + +The `catch` instruction has an exception tag associated with it. The tag +identifies what exceptions it can catch. That is, any exception created with the +corresponding exception tag. Catch blocks that begin with a `catch` instruction +are considered _tagged_ catch blocks. + +The last catching instruction of a try-catch block can be the `catch_all` +instruction. If it begins with the `catch_all` instruction, it defines the +_default_ catch block. The default catch block has no exception type, and is +used to catch all exceptions not caught by any of the tagged catch blocks. The +term 'catch block' refers to both `catch` and `catch_all` blocks. Try blocks, like control-flow blocks, have a _block type_. The block type of a try block defines the values yielded by evaluating the try block when either no @@ -178,7 +145,31 @@ exception is thrown, or the exception is successfully caught by the catch block. Because `try` and `end` instructions define a control-flow block, they can be targets for branches (`br` and `br_if`) as well. -In the initial implementation, try blocks may only yield 0 or 1 values. +### Try-unwind blocks + +Try blocks can also be used with the `unwind` instruction. A try-unwind block +contains an `unwind` block with the following form: + +``` +try blocktype + instruction* +unwind + instruction* +end +``` + +The `unwind` block is meant to contain cleanup instructions, such as +destructors, in case any instruction in the corresponding try block throws. +Currently the `unwind` instruction has the same semantics as the `catch_all` +instruction that it catches all exceptions. In case an exception is caught by +the `unwind` block, it becomes the catching block. + +The `end` instruction at the end of `unwind` block is special that it +automatically rethrows the current exception. + +The current plan is, try-unwind blocks will have a different semantics from +that of try-catch blocks when we introduce two-phase unwinding as a follow-on +proposal in the future. ### Throwing an exception @@ -205,85 +196,115 @@ 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. +try block of the catch/unwind block, since instructions in the body of the +catch/unwind 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 `exnref` referring to the caught exception is pushed back onto the -operand stack. +is popped back to the size the operand stack had when the try block was entered. + +Then in case of a try-catch block, tagged catch blocks are tried in the +order they appear in the catching try block, until one matches. If a matched +tagged catch block is found, control is transferred to the body of the catch +block, and the data fields of the exception are pushed back onto the stack. + +Otherwise, control is transferred to the body of the `catch_all` block, if any. +However, unlike tagged catch blocks, the constructor arguments are not copied +back onto the operand stack. If no tagged catch blocks were matched, and the +catching try block doesn't have a `catch_all` block, the exception is rethrown +to the next enclosing try block. 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. +In case of a try-unwind block, the control is transfered to the body of the +`unwind` block. After executing instructions within the `unwind` block, the +exception is automatically rethrown when the control reaches the `end` +instruction. As in the case of the `catch_all` block, the exception arguments +are not copied onto the operand stack. + +If the selected catch/unwind 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 `rethrow` instruction. ### Rethrowing an exception -The `rethrow` instruction takes the exception associated with the `exnref` 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. The `rethrow` instruction traps -if the value on the top of the stack is null. +The `rethrow` instruction can only appear in the body of a catch/unwind block. +It always re-throws the exception caught by an enclosing catch block. -### Exception data extraction +Associated with the `rethrow` instruction is a _label_. The label is used to +disambiguate which exception is to be rethrown, when inside nested catch blocks. +The label is the relative block depth to the corresponding try block for which +the catching block appears. -The `br_on_exn` instruction is a conditional branch that checks the exception -tag of an exception on top of the stack, in the form of: +For example consider the following: ``` -br_on_exn label except_index +try + ... +catch 1 + ... + block + ... + try + ... + catch 2 + ... + try + ... + catch 3 + ... + rethrow N + end + end + end + ... +end ``` -The `br_on_exn` instruction checks the exception tag of an `exnref` on top of -the stack if it matches the given exception index. If it does, it branches out -to the label referenced by the instruction (In the binary form, the label will -be converted to a relative depth immediate, like other branch instructions), and -while doing that, pops the `exnref` value from the stack and instead pushes the -exception's argument values on top of the stack. In order to use these popped -values, the block signature of the branch target has to match the exception -types - because it receives the exception arguments as branch operands. If the -exception tag does not match, the `exnref` value remains on the stack. For -example, when an `exnref` contains an exception of type (i32 i64), the target -block signature should be (i32 i64) as well, as in the following example: +In this example, `N` is used to disambiguate which caught exception is being +rethrown. It could rethrow any of the three caught expceptions. Hence, +`rethrow 0` corresponds to the exception caught by `catch 3`, `rethrow 1` +corresponds to the exception caught by `catch 2`, and `rethrow 3` corresponds +to the exception caught by `catch 1`. + +Note that `rethrow 2` is not allowed because it does not reference a `try` +instruction. Rather, it references a `block` instruction. + +### Try-delegate blocks + +Try blocks can also be used with the `delegate` instruction. A try-delegate +block contains an `delegate` instruction with the following form: ``` -block $l (result i32 i64) - ... - ;; exnref $e is on the stack at this point - br_on_exn $l ;; branch to $l with $e's arguments - ... -end +try blocktype + instruction* +delegate label ``` -This can now be used to construct handler switches in the same way `br_table` -is used to construct regular switch: +The `delegate` instruction does not have an associated body, so try-delegate +blocks don't have an `end` instruction at the end. The `delegate` instruction +takes a label defined by a construct in which they are enclosed, and delegates +exception handling to a catch/unwind block specified by the label. For example, +consider this code: ``` -block $end - block $l1 - ... - block $lN - br_on_exn $l1 - ... - br_on_exn $lN - rethrow - end $lN - ;; handler for $eN here - br $end - ... - end $l1 - ;; handler for $e1 -end $end +try $label0 + try + call $foo + delegate $label0 +catch $tag + ... +catch_all + ... +end ``` -If the query fails, the control flow falls through, and no values are pushed -onto the stack. The `br_on_exn` instruction traps if the value on the top of the -stack is null. +If `call $foo` throws, searching for a catching block first finds `delegate`, +and because it delegates exception handling to catching instructions associated +with `$label0`, it will be next checked by the outer `catch` and then +`catch_all` instructions. ### Stack traces @@ -294,10 +315,10 @@ details of this is left to the embedder. ### Traps and JS API -The `catch` instruction catches exceptions generated by the `throw` instruction, -but does not catch traps. The rationale for this is that in general traps are -not locally recoverable and are not needed to be handled in local scopes like -try-catch. +The `catch`/`catch_all`/`unwind` instruction catches exceptions generated by the +`throw` instruction, but does not catch traps. The rationale for this is that in +general traps are not locally recoverable and are not needed to be handled in +local scopes like try-catch. The `catch` instruction catches foreign exceptions generated from calls to function imports as well, including JavaScript exceptions, with a few @@ -326,20 +347,20 @@ document](https://github.com/WebAssembly/spec/blob/master/document/core/text/ins The following rules are added to *instructions*: ``` - try blocktype instruction* catch instruction* end | + try blocktype instruction* (catch instruction*)+ (catch_all instruction*)+ end | + try blocktype instruction* unwind instruction* end | + try blocktype instruction* delegate label | throw (exception except_index) | - rethrow | - br_on_exn label (exception except_index) + rethrow label | ``` Like the `block`, `loop`, and `if` instructions, the `try` instruction is *structured* control flow instruction, and can be labeled. This allows branch instructions to exit try blocks. -The `except_index` of the `throw` and `br_on_exn` instructions defines the -exception (and hence, exception tag) to create/extract from. See [exception -index space](#exception-index-space) for further clarification of exception -tags. +The `except_index` of the `throw` and `catch` instructions defines the exception +(and hence, exception tag) to create/extract from. See [exception index +space](#exception-index-space) for further clarification of exception tags. ## Changes to Modules document @@ -365,23 +386,6 @@ elsewhere, and use the same tag. This section describes changes in the [binary encoding design document](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md). -### Data Types - -#### exnref - -An exception reference points to an exception. - -### Language Types - -| Opcode | Type constructor | -|--------|------------------| -| -0x18 | `exnref` | - -#### value_type - -A `varint7` indicating a `value_type` is extended to include `exnref` as encoded -above. - #### Other Types ##### exception_type @@ -412,20 +416,20 @@ or defined: [definition](Modules.md#linear-memory-section) * `3` indicating a `Global` [import](Modules.md#imports) or [definition](Modules.md#global-section) -* `4` indicating an `Exception` [import](#import-section) or -[definition](#exception-section) +* `4` indicating an `Event` [import](#import-section) or +[definition](#event-section) ### Module structure #### High-level structure -A new `exception` section is introduced and is named `exception`. If included, -it must appear immediately after the memory section. +A new `event` section is introduced and is named `event`. If included, it must +appear immediately after the memory section. ##### Exception section -The `exception` section is the named section 'exception'. For ease of -validation, this section comes after the [memory +The `event` section is the named section 'exception'. For ease of validation, +this section comes after the [memory section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#memory-section) and before the [global section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#global-section). @@ -438,7 +442,7 @@ So the list of all sections will be: | Function | `3` | Function declarations | | Table | `4` | Indirect function table and other tables | | Memory | `5` | Memory attributes | -| Exception | `13` | Exception declarations | +| Event | `13` | Event declarations | | Global | `6` | Global declarations | | Export | `7` | Exports | | Start | `8` | Start function declaration | @@ -446,34 +450,34 @@ So the list of all sections will be: | Code | `10` | Function bodies (code) | | Data | `11` | Data segments | -The exception section declares a list of exception types as follows: +The event section declares a list of event types as follows: | Field | Type | Description | |-------|------|-------------| -| count | `varuint32` | count of the number of exceptions to follow | -| type | `exception_type*` | The definitions of the exception types | +| count | `varuint32` | count of the number of event to follow | +| type | `event_type*` | The definitions of the event types | ##### Import section The import section is extended to include exception definitions by extending an `import_entry` as follows: -If the `kind` is `Exception`: +If the `kind` is `Event`: | Field | Type | Description | |-------|------|-------------| -| `type` | `exception_type` | the exception being imported | +| `type` | `event_type` | the event being imported | ##### Export section The export section is extended to reference exception types by extending an `export_entry` as follows: -If the `kind` is `Exception`: +If the `kind` is `Event`: | Field | Type | Description | |-------|------|-------------| -| `index` | `varuint32` | the index into the corresponding exception index space | +| `index` | `varuint32` | the index into the corresponding event index space | ##### Name section @@ -484,12 +488,12 @@ follows: | --------- | ---- | ----------- | | [Function](#function-names) | `1` | Assigns names to functions | | [Local](#local-names) | `2` | Assigns names to locals in functions | -| [Exception](#exception-names) | `3` | Assigns names to exception types | +| [Event](#event-names) | `3` | Assigns names to event types | -###### Exception names +###### Event names -The exception names subsection is a `name_map` which assigns names to a subset -of the exception indices (Used for both imports and module-defined). +The event names subsection is a `name_map` which assigns names to a subset of +the exception indices (Used for both imports and module-defined). ### Control flow operators @@ -499,10 +503,17 @@ throws, and rethrows as follows: | Name | Opcode | Immediates | Description | | ---- | ---- | ---- | ---- | | `try` | `0x06` | sig : `blocktype` | begins a block which can handle thrown exceptions | -| `catch` | `0x07` | | begins the catch block of the try block | +| `catch` | `0x07` | index : `varint32` | begins the catch block of the try block | +| `catch_all` | `0x05` | | begins the catch_all block of the try block | +| `unwind` | `0x0a` | | begins the unwind block of the try block | +| `delegate` | `0x??` | relative_depth | begins the delegate block of the try block | | `throw` | `0x08` | index : `varint32` | Creates an exception defined by the exception `index`and then throws it | -| `rethrow` | `0x09` | | Pops the `exnref` on top of the stack and throws it | -| `br_on_exn` | `0x0a` | relative_depth : `varuint32`, index : `varuint32` | Branches to the given label and extracts data within `exnref` on top of stack if it was created using the corresponding exception `index` | +| `rethrow` | `0x09` | relative_depth | Pops the `exnref` on top of the stack and throws it | The *sig* fields of `block`, `if`, and `try` operators are block signatures which describe their use of the operand stack. + +Note that the textual `catch_all` instruction is implemented using the +`else` operator. Since the `else` operator is always unambiguous in the binary +format, there is no need to tie up a separate opcode for the `catch_all` +instruction. From f7a4f60d11fb6326fc13f84d3889b11d3873f08a Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 7 Oct 2020 09:13:08 -0700 Subject: [PATCH 02/17] Add a bar separating the header --- proposals/Exceptions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/Exceptions.md b/proposals/Exceptions.md index c9671e58..b1bcb30a 100644 --- a/proposals/Exceptions.md +++ b/proposals/Exceptions.md @@ -4,6 +4,8 @@ This explainer reflects the up-to-date version of the exception handling proposal agreed on [the CG meeting on 09-15-2020](https://github.com/WebAssembly/meetings/blob/master/main/2020/CG-09-15.md). +--- + ## Overview Exception handling allows code to break control flow when an exception is From a8ab31908e9ef4db24b189282c2f4d326025ccf5 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 16 Dec 2020 23:01:18 -0800 Subject: [PATCH 03/17] Remove dependency from reference types proposal in README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index bd11d86f..619d10d4 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ for adding exception handling to WebAssembly. * See the [proposal overview](proposals/exception-handling/Exceptions.md) for a summary of the proposal. -The repository is now based on the [reference types proposal](proposals/reference-types/Overview.md) and includes all respective changes. - Original README from upstream repository follows... # spec From 0905d082b7642d517460ae45e9fd63bdeeb59ee5 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 16 Dec 2020 23:02:45 -0800 Subject: [PATCH 04/17] 'rethrow' cannot appear within 'unwind' --- proposals/exception-handling/Exceptions.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index b1bcb30a..6742ad2a 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -162,17 +162,12 @@ end The `unwind` block is meant to contain cleanup instructions, such as destructors, in case any instruction in the corresponding try block throws. -Currently the `unwind` instruction has the same semantics as the `catch_all` -instruction that it catches all exceptions. In case an exception is caught by -the `unwind` block, it becomes the catching block. +In case an exception is caught by the `unwind` block, it becomes the catching +block. The `end` instruction at the end of `unwind` block is special that it automatically rethrows the current exception. -The current plan is, try-unwind blocks will have a different semantics from -that of try-catch blocks when we introduce two-phase unwinding as a follow-on -proposal in the future. - ### Throwing an exception The `throw` instruction takes an exception index as an immediate argument. That @@ -232,8 +227,9 @@ Note that a caught exception can be rethrown using the `rethrow` instruction. ### Rethrowing an exception -The `rethrow` instruction can only appear in the body of a catch/unwind block. -It always re-throws the exception caught by an enclosing catch block. +The `rethrow` instruction can only appear in the body of a catch/catch_all block +but not within the body of an unwind block. It always re-throws the exception +caught by an enclosing catch block. Associated with the `rethrow` instruction is a _label_. The label is used to disambiguate which exception is to be rethrown, when inside nested catch blocks. From 7bd61c51644215cba2f4ae8e55078e755880b8a0 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 16 Dec 2020 23:04:51 -0800 Subject: [PATCH 05/17] Some runtimes, including web VMs can attach stack traces --- proposals/exception-handling/Exceptions.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 6742ad2a..ce961742 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -304,14 +304,16 @@ and because it delegates exception handling to catching instructions associated with `$label0`, it will be next checked by the outer `catch` and then `catch_all` instructions. -### Stack traces +### JS API + +#### Stack traces 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. +calls until a corresponding, enclosing try block is found. Some runtimes, +especially web VMs may also associate a stack trace that can be used to report +uncaught exceptions. However, the details of this is left to the embedder. -### Traps and JS API +#### Traps The `catch`/`catch_all`/`unwind` instruction catches exceptions generated by the `throw` instruction, but does not catch traps. The rationale for this is that in From 53170b5a94d0fe9795a158b0fea08183409f9347 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 17 Dec 2020 00:23:41 -0800 Subject: [PATCH 06/17] backup --- proposals/exception-handling/Exceptions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index ce961742..95875fb4 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -302,7 +302,9 @@ end If `call $foo` throws, searching for a catching block first finds `delegate`, and because it delegates exception handling to catching instructions associated with `$label0`, it will be next checked by the outer `catch` and then -`catch_all` instructions. +`catch_all` instructions. When the specified label within a `delegate` +instruction does not correspond to a `try` instruction, it is a validation +failure. ### JS API From d5ec45931982bddd2abe8bcf48b3133580294897 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 2 Jan 2021 21:19:53 -0800 Subject: [PATCH 07/17] relative depths are `varuint32`s --- proposals/exception-handling/Exceptions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 95875fb4..7106649d 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -508,9 +508,9 @@ throws, and rethrows as follows: | `catch` | `0x07` | index : `varint32` | begins the catch block of the try block | | `catch_all` | `0x05` | | begins the catch_all block of the try block | | `unwind` | `0x0a` | | begins the unwind block of the try block | -| `delegate` | `0x??` | relative_depth | begins the delegate block of the try block | +| `delegate` | `0x??` | relative_depth : `varuint32` | begins the delegate block of the try block | | `throw` | `0x08` | index : `varint32` | Creates an exception defined by the exception `index`and then throws it | -| `rethrow` | `0x09` | relative_depth | Pops the `exnref` on top of the stack and throws it | +| `rethrow` | `0x09` | relative_depth : `varuint32` | Pops the `exnref` on top of the stack and throws it | The *sig* fields of `block`, `if`, and `try` operators are block signatures which describe their use of the operand stack. From 82342b22e890c5218212a404a7591e4896869680 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 12 Feb 2021 13:26:09 -0800 Subject: [PATCH 08/17] `delegate`: 0x18, `catch_all`: 0x19 --- proposals/exception-handling/Exceptions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 7106649d..6ff24f6f 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -506,9 +506,9 @@ throws, and rethrows as follows: | ---- | ---- | ---- | ---- | | `try` | `0x06` | sig : `blocktype` | begins a block which can handle thrown exceptions | | `catch` | `0x07` | index : `varint32` | begins the catch block of the try block | -| `catch_all` | `0x05` | | begins the catch_all block of the try block | +| `catch_all` | `0x19` | | begins the catch_all block of the try block | | `unwind` | `0x0a` | | begins the unwind block of the try block | -| `delegate` | `0x??` | relative_depth : `varuint32` | begins the delegate block of the try block | +| `delegate` | `0x18` | relative_depth : `varuint32` | begins the delegate block of the try block | | `throw` | `0x08` | index : `varint32` | Creates an exception defined by the exception `index`and then throws it | | `rethrow` | `0x09` | relative_depth : `varuint32` | Pops the `exnref` on top of the stack and throws it | From 989ee10c7235c08ffbba9b04520e6ad6a767333f Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 12 Feb 2021 13:48:07 -0800 Subject: [PATCH 09/17] Add explanation for delegate and rethrow rules --- proposals/exception-handling/Exceptions.md | 52 +++++++++++++++++----- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 6ff24f6f..eae62286 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -239,21 +239,21 @@ the catching block appears. For example consider the following: ``` -try +try $l1 ... catch 1 ... block ... - try + try $l2 ... catch 2 ... - try + try $l3 ... catch 3 ... - rethrow N + rethrow N (or label) end end end @@ -265,10 +265,26 @@ In this example, `N` is used to disambiguate which caught exception is being rethrown. It could rethrow any of the three caught expceptions. Hence, `rethrow 0` corresponds to the exception caught by `catch 3`, `rethrow 1` corresponds to the exception caught by `catch 2`, and `rethrow 3` corresponds -to the exception caught by `catch 1`. +to the exception caught by `catch 1`. In wat format, the argument for the +`rethrow` instructions can also be written as a label, like branches. So +`rethrow 0` in the example above can also be written as `rethrow $l3`. Note that `rethrow 2` is not allowed because it does not reference a `try` -instruction. Rather, it references a `block` instruction. +instruction. Rather, it references a `block` instruction, so it will result in a +validation failure. + +Note that the example below is a validation failure: +``` +try $l1 + try $l2 + rethrow $l2 (= rethrow 0) + catch + end +catch +end +``` +The `rethrow` here references `try $l2`, but the `rethrow` is not within its +`catch` block. ### Try-delegate blocks @@ -288,11 +304,11 @@ exception handling to a catch/unwind block specified by the label. For example, consider this code: ``` -try $label0 +try $l1 try call $foo - delegate $label0 -catch $tag + delegate $l1 (= delegate 0) +catch ... catch_all ... @@ -302,10 +318,26 @@ end If `call $foo` throws, searching for a catching block first finds `delegate`, and because it delegates exception handling to catching instructions associated with `$label0`, it will be next checked by the outer `catch` and then -`catch_all` instructions. When the specified label within a `delegate` +`catch_all` instructions. +When the specified label within a `delegate` instruction does not correspond to a `try` instruction, it is a validation failure. +Note that the example below is a validation failure: +``` +try $l1 +catch 1 + try + call $foo + delegate $l1 (= delegate 0) +catch_all + ... +end +``` +Here `delegate` is trying to delegate to `catch 1`, which exists before the +`delegate`. The `delegate` instruction can only delegate to `catch`/`catch_all` +blocks in a `try` or to another `delegate` below the `delegate` itself. + ### JS API #### Stack traces From 7655f75bc920bfec0498a91f1de0adad4f7e355f Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 18 Feb 2021 02:53:58 +0900 Subject: [PATCH 10/17] Apply suggestions from code review Co-authored-by: Andreas Rossberg Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com> --- proposals/exception-handling/Exceptions.md | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index eae62286..56b73dad 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -119,9 +119,9 @@ end ``` A try-catch block contains zero or more `catch` blocks and zero or one -`catch_all` block. All `catch` blocks should precede the `catch_all` block, if +`catch_all` block. All `catch` blocks must precede the `catch_all` block, if any. The `catch`/`catch_all` instructions (within the try construct) are called -the _catching_ instructions. There should be at least `catch` or `catch_all` +the _catching_ instructions. There should be at least one `catch` or `catch_all` block within a try-catch block. The _body_ of the try block is the list of instructions before the first @@ -197,7 +197,7 @@ try block of the catch/unwind block, since instructions in the body of the catch/unwind 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. +is popped back to the size the operand stack had when the try block was entered after possible block parameters were popped. Then in case of a try-catch block, tagged catch blocks are tried in the order they appear in the catching try block, until one matches. If a matched @@ -206,9 +206,10 @@ block, and the data fields of the exception are pushed back onto the stack. Otherwise, control is transferred to the body of the `catch_all` block, if any. However, unlike tagged catch blocks, the constructor arguments are not copied -back onto the operand stack. If no tagged catch blocks were matched, and the -catching try block doesn't have a `catch_all` block, the exception is rethrown -to the next enclosing try block. +back onto the operand stack. + +If no tagged catch blocks were matched, and the +catching try block doesn't have a `catch_all` block, the exception is rethrown. 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. @@ -220,8 +221,7 @@ instruction. As in the case of the `catch_all` block, the exception arguments are not copied onto the operand stack. If the selected catch/unwind 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. +the value(s) expected by the corresponding catching try block. Note that a caught exception can be rethrown using the `rethrow` instruction. @@ -241,19 +241,19 @@ For example consider the following: ``` try $l1 ... -catch 1 +catch ;; $l1 ... block ... try $l2 ... - catch 2 + catch ;; $l2 ... try $l3 ... - catch 3 + catch ;; $l3 ... - rethrow N (or label) + rethrow N ;; (or label) end end end @@ -269,15 +269,15 @@ to the exception caught by `catch 1`. In wat format, the argument for the `rethrow` instructions can also be written as a label, like branches. So `rethrow 0` in the example above can also be written as `rethrow $l3`. -Note that `rethrow 2` is not allowed because it does not reference a `try` -instruction. Rather, it references a `block` instruction, so it will result in a +Note that `rethrow 2` is not allowed because it does not reference a catch +block. Rather, it references a `block` instruction, so it will result in a validation failure. Note that the example below is a validation failure: ``` try $l1 try $l2 - rethrow $l2 (= rethrow 0) + rethrow $l2 ;; (= rethrow 0) catch end catch @@ -289,7 +289,7 @@ The `rethrow` here references `try $l2`, but the `rethrow` is not within its ### Try-delegate blocks Try blocks can also be used with the `delegate` instruction. A try-delegate -block contains an `delegate` instruction with the following form: +block contains a `delegate` instruction with the following form: ``` try blocktype @@ -297,7 +297,7 @@ try blocktype delegate label ``` -The `delegate` instruction does not have an associated body, so try-delegate +The `delegate` clause does not have an associated body, so try-delegate blocks don't have an `end` instruction at the end. The `delegate` instruction takes a label defined by a construct in which they are enclosed, and delegates exception handling to a catch/unwind block specified by the label. For example, @@ -307,7 +307,7 @@ consider this code: try $l1 try call $foo - delegate $l1 (= delegate 0) + delegate $l1 ;; (= delegate 0) catch ... catch_all @@ -317,7 +317,7 @@ end If `call $foo` throws, searching for a catching block first finds `delegate`, and because it delegates exception handling to catching instructions associated -with `$label0`, it will be next checked by the outer `catch` and then +with `$l1`, it will be next checked by the outer `catch` and then `catch_all` instructions. When the specified label within a `delegate` instruction does not correspond to a `try` instruction, it is a validation @@ -329,7 +329,7 @@ try $l1 catch 1 try call $foo - delegate $l1 (= delegate 0) + delegate $l1 ;; (= delegate 0) catch_all ... end @@ -345,7 +345,7 @@ blocks in a `try` or to another `delegate` below the `delegate` itself. When an exception is thrown, the runtime will pop the stack across function calls until a corresponding, enclosing try block is found. Some runtimes, especially web VMs may also associate a stack trace that can be used to report -uncaught exceptions. However, the details of this is left to the embedder. +uncaught exceptions. However, the details of this are left to the embedder. #### Traps From 575705cb3e8564c7a5245a7eac02899bbd154103 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 17 Feb 2021 10:24:48 -0800 Subject: [PATCH 11/17] Clarify "In case of a try-unwind block..." paragraph --- proposals/exception-handling/Exceptions.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 56b73dad..7a95dbbb 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -214,11 +214,12 @@ catching try block doesn't have a `catch_all` block, the exception is rethrown. 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. -In case of a try-unwind block, the control is transfered to the body of the -`unwind` block. After executing instructions within the `unwind` block, the -exception is automatically rethrown when the control reaches the `end` -instruction. As in the case of the `catch_all` block, the exception arguments -are not copied onto the operand stack. +In case of a try-unwind block, if an exception is thrown within the try block, +the program control is transfered to the body of `unwind` block. After executing +instructions within the `unwind` block, the exception is automatically rethrown +when the control reaches the `end` instruction. As in the case of the +`catch_all` block, the exception arguments are not copied onto the operand +stack. If the selected catch/unwind block does not throw an exception, it must yield the value(s) expected by the corresponding catching try block. From f52db333e22e827f0b5881eea38cabfc74a4ceea Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 17 Feb 2021 10:25:36 -0800 Subject: [PATCH 12/17] Fix try-catch-catch_all's syntax --- proposals/exception-handling/Exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 7a95dbbb..04882ab4 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -382,7 +382,7 @@ document](https://github.com/WebAssembly/spec/blob/master/document/core/text/ins The following rules are added to *instructions*: ``` - try blocktype instruction* (catch instruction*)+ (catch_all instruction*)+ end | + try blocktype instruction* (catch instruction*)+ (catch instruction*)* (catch_all instruction*)? end | try blocktype instruction* unwind instruction* end | try blocktype instruction* delegate label | throw (exception except_index) | From 6b59bb1fcb17f3983f716464dce115aa8e92de7d Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 17 Feb 2021 10:26:01 -0800 Subject: [PATCH 13/17] Remove sentences saying `catch_all` and `else` share opcodes --- proposals/exception-handling/Exceptions.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 04882ab4..990421e0 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -547,8 +547,3 @@ throws, and rethrows as follows: The *sig* fields of `block`, `if`, and `try` operators are block signatures which describe their use of the operand stack. - -Note that the textual `catch_all` instruction is implemented using the -`else` operator. Since the `else` operator is always unambiguous in the binary -format, there is no need to tie up a separate opcode for the `catch_all` -instruction. From 263af137a57add972b931ece1f83c846fcc0d044 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 17 Feb 2021 10:39:21 -0800 Subject: [PATCH 14/17] 80-col wrapping --- proposals/exception-handling/Exceptions.md | 57 +++++++++++----------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 990421e0..46bd1e7e 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -59,8 +59,8 @@ 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 or unwind -block associated with a try block defines how to handle the throw. +a `throw` instruction is the suspending event of an exception. The catch or +unwind 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 @@ -93,8 +93,8 @@ Exception indices are used by: 1. The `throw` instruction which creates a WebAssembly exception with the corresponding exception tag, and then throws it. -2. The `catch` instruction uses the tag to identify if the thrown exception is - one it can catch. If true it pushes the corresponding argument values of the +2. The `catch` clause uses the tag to identify if the thrown exception is one it + can catch. If true it pushes the corresponding argument values of the exception onto the stack. ### Try-catch blocks @@ -161,8 +161,8 @@ end ``` The `unwind` block is meant to contain cleanup instructions, such as -destructors, in case any instruction in the corresponding try block throws. -In case an exception is caught by the `unwind` block, it becomes the catching +destructors, in case any instruction in the corresponding try block throws. In +case an exception is caught by the `unwind` block, it becomes the catching block. The `end` instruction at the end of `unwind` block is special that it @@ -197,19 +197,20 @@ try block of the catch/unwind block, since instructions in the body of the catch/unwind 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 after possible block parameters were popped. +is popped back to the size the operand stack had when the try block was entered +after possible block parameters were popped. -Then in case of a try-catch block, tagged catch blocks are tried in the -order they appear in the catching try block, until one matches. If a matched -tagged catch block is found, control is transferred to the body of the catch -block, and the data fields of the exception are pushed back onto the stack. +Then in case of a try-catch block, tagged catch blocks are tried in the order +they appear in the catching try block, until one matches. If a matched tagged +catch block is found, control is transferred to the body of the catch block, and +the data fields of the exception are pushed back onto the stack. Otherwise, control is transferred to the body of the `catch_all` block, if any. However, unlike tagged catch blocks, the constructor arguments are not copied back onto the operand stack. -If no tagged catch blocks were matched, and the -catching try block doesn't have a `catch_all` block, the exception is rethrown. +If no tagged catch blocks were matched, and the catching try block doesn't have +a `catch_all` block, the exception is rethrown. 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. @@ -263,12 +264,12 @@ end ``` In this example, `N` is used to disambiguate which caught exception is being -rethrown. It could rethrow any of the three caught expceptions. Hence, -`rethrow 0` corresponds to the exception caught by `catch 3`, `rethrow 1` -corresponds to the exception caught by `catch 2`, and `rethrow 3` corresponds -to the exception caught by `catch 1`. In wat format, the argument for the -`rethrow` instructions can also be written as a label, like branches. So -`rethrow 0` in the example above can also be written as `rethrow $l3`. +rethrown. It could rethrow any of the three caught expceptions. Hence, `rethrow +0` corresponds to the exception caught by `catch 3`, `rethrow 1` corresponds to +the exception caught by `catch 2`, and `rethrow 3` corresponds to the exception +caught by `catch 1`. In wat format, the argument for the `rethrow` instructions +can also be written as a label, like branches. So `rethrow 0` in the example +above can also be written as `rethrow $l3`. Note that `rethrow 2` is not allowed because it does not reference a catch block. Rather, it references a `block` instruction, so it will result in a @@ -298,11 +299,11 @@ try blocktype delegate label ``` -The `delegate` clause does not have an associated body, so try-delegate -blocks don't have an `end` instruction at the end. The `delegate` instruction -takes a label defined by a construct in which they are enclosed, and delegates -exception handling to a catch/unwind block specified by the label. For example, -consider this code: +The `delegate` clause does not have an associated body, so try-delegate blocks +don't have an `end` instruction at the end. The `delegate` instruction takes a +label defined by a construct in which they are enclosed, and delegates exception +handling to a catch/unwind block specified by the label. For example, consider +this code: ``` try $l1 @@ -318,11 +319,9 @@ end If `call $foo` throws, searching for a catching block first finds `delegate`, and because it delegates exception handling to catching instructions associated -with `$l1`, it will be next checked by the outer `catch` and then -`catch_all` instructions. -When the specified label within a `delegate` -instruction does not correspond to a `try` instruction, it is a validation -failure. +with `$l1`, it will be next checked by the outer `catch` and then `catch_all` +instructions. When the specified label within a `delegate` instruction does not +correspond to a `try` instruction, it is a validation failure. Note that the example below is a validation failure: ``` From c7faca368b6d0545c5c696e567baddb476042ad7 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 17 Feb 2021 10:55:21 -0800 Subject: [PATCH 15/17] Describe when there's br within catch/unwind blocks --- proposals/exception-handling/Exceptions.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 46bd1e7e..9c46365c 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -141,6 +141,10 @@ _default_ catch block. The default catch block has no exception type, and is used to catch all exceptions not caught by any of the tagged catch blocks. The term 'catch block' refers to both `catch` and `catch_all` blocks. +When the program runs `br` within `catch` or `catch_all` blocks, the rest of +the catching blocks will not run and the program control will branch to the +destination, as in normal blocks. + Try blocks, like control-flow blocks, have a _block type_. The block type of a try block defines the values yielded by evaluating the try block when either no exception is thrown, or the exception is successfully caught by the catch block. @@ -168,6 +172,11 @@ block. The `end` instruction at the end of `unwind` block is special that it automatically rethrows the current exception. +When the program runs `br` within `unwind` block, the rest of the `unwind` block +will not run and the program control will branch to the destination, as in +normal blocks. Because we don't reach `end` of `unwind` block, rethrowing does +not happen. + ### Throwing an exception The `throw` instruction takes an exception index as an immediate argument. That From 275c449e9063c1d3d02bbbffda4b9ab1f8869a68 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 19 Feb 2021 08:03:55 -0800 Subject: [PATCH 16/17] Fix catch / catch_all rule --- proposals/exception-handling/Exceptions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 9c46365c..2c8c5ba1 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -390,12 +390,13 @@ document](https://github.com/WebAssembly/spec/blob/master/document/core/text/ins The following rules are added to *instructions*: ``` - try blocktype instruction* (catch instruction*)+ (catch instruction*)* (catch_all instruction*)? end | + try blocktype instruction* (catch instruction*)* (catch_all instruction*)? end | try blocktype instruction* unwind instruction* end | try blocktype instruction* delegate label | throw (exception except_index) | rethrow label | ``` +(In the first rule, there should be at least one `catch` or `catch_all` block.) Like the `block`, `loop`, and `if` instructions, the `try` instruction is *structured* control flow instruction, and can be labeled. This allows branch From 4d6c635ce79413fd9f48abe87050d1926575a986 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 24 Feb 2021 14:18:38 -0800 Subject: [PATCH 17/17] Cannot rethrow exceptions caught by unwind blocks --- proposals/exception-handling/Exceptions.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 2c8c5ba1..fecb3b15 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -238,9 +238,9 @@ Note that a caught exception can be rethrown using the `rethrow` instruction. ### Rethrowing an exception -The `rethrow` instruction can only appear in the body of a catch/catch_all block -but not within the body of an unwind block. It always re-throws the exception -caught by an enclosing catch block. +The `rethrow` instruction can only appear in the body of a +catch/catch_all/unwind block. It always re-throws the exception caught by an +enclosing catch block. Associated with the `rethrow` instruction is a _label_. The label is used to disambiguate which exception is to be rethrown, when inside nested catch blocks. @@ -297,6 +297,21 @@ end The `rethrow` here references `try $l2`, but the `rethrow` is not within its `catch` block. +Also, the `rethrow` instruction cannot rethrow an exception caught +by an unwind block. For example: +``` +try $l1 +unwind + try $l2 + catch + try $l3 + unwind + rethrow label ;; only $l2 is valid + end + end +end +``` + ### Try-delegate blocks Try blocks can also be used with the `delegate` instruction. A try-delegate