Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

formal spec overview for the 3rd exception handling proposal #143

Merged
merged 27 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9c0c1a8
formal spec overview for the 3rd exception handling proposal
ioannad Nov 10, 2020
2dc31f8
Removes catch-labels for rethrow
ioannad Nov 26, 2020
57c41e5
Allows try blocks without catches.
ioannad Nov 26, 2020
51c360b
Removes auxiliary type for labels and allows delegate to reference an…
ioannad Nov 26, 2020
5a988d5
Revert making catchless `try`s possible, keeping the simpler execution
ioannad Dec 16, 2020
aafc4b1
Apply suggestions from code review
ioannad Feb 17, 2021
4bb9f84
Updated validation rules to match latest semantics of #137
ioannad Feb 19, 2021
1afd21a
Added forgotten reduction step and comment for example 1
ioannad Feb 19, 2021
4ff1c38
Apply suggestions from code review
ioannad Feb 24, 2021
fbe87b0
Apply suggestions from code review
ioannad Feb 24, 2021
fa5a237
Applied comments from @rossberg 's review and some typesetting.
ioannad Feb 24, 2021
83872ed
Adds typing rules for the administrative instructions and fixes rules…
ioannad Mar 12, 2021
4552306
Implemented latest review comments and updated.
ioannad Jun 8, 2021
8cfa1b6
Renamed Exceptions to Tags
ioannad Jun 25, 2021
7d122dd
Apply suggestions from code review
ioannad Jul 13, 2021
d874fcd
Addressed latest review and added the missing +1 to the adm. delegate.
ioannad Aug 25, 2021
c98ff33
Apply suggestions from code review
ioannad Aug 31, 2021
72bf4f3
Apply suggestions from code review
ioannad Sep 8, 2021
4606660
Removed try-labels and removed +1 from the administrative delegate.
ioannad Nov 22, 2021
662343c
Merge remote-tracking branch 'upstream/main' into formal-overview
ioannad Nov 22, 2021
b12eec9
Adjusted interpreter to the simplified reduction rule for delegate.
ioannad Nov 22, 2021
9b02deb
Fixed grammar definitions.
ioannad Nov 23, 2021
bb0a623
Apply suggestions from code review
ioannad Dec 2, 2021
5457b7a
Addressed latest reviews
ioannad Jan 28, 2022
6f681ff
Merge remote-tracking branch 'upstream/main' into formal-overview
ioannad Feb 3, 2022
315fe15
Added TODO item for uncaught exceptions
ioannad Feb 4, 2022
219d92e
Merge branch 'WebAssembly:main' into formal-overview
ioannad Feb 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions proposals/exception-handling/Exceptions-formal-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# 3rd proposal formal spec examples
ioannad marked this conversation as resolved.
Show resolved Hide resolved

This document contains WebAssembly code examples mentioned in comments on this repository, and what they reduce to, according to the "3rd proposal formal spec overview".
ioannad marked this conversation as resolved.
Show resolved Hide resolved

Its purpose is to make sure everyone is happy with the implications of the semantics in the current 3rd proposal, or to aid discussions on these semantics.

The first *example 0* contains all the new instructions, and it is the only one with an almost full reduction displayed. It is meant to easily show how the spec works, even if the reader has not spent much time with the WebAssembly formal spec.

For all other examples just the result of the reduction is given. These examples are taken from comments in this repository, which are linked. Some times/often the examples are modified to fit the current syntax.
ioannad marked this conversation as resolved.
Show resolved Hide resolved

If anyone would like that I add another reduction trace, or other examples, please let me know, I'd be happy to.

### notation
ioannad marked this conversation as resolved.
Show resolved Hide resolved

If `x` is an exception tag index, then `a_x` denotes its exception tag address, i.e., `F.tag[x] = a_x`, where `F` is the current frame.

## example 0
ioannad marked this conversation as resolved.
Show resolved Hide resolved

The only example with an almost full reduction trace, and all new instructions. The first 3 steps, reducing the several `try`s to their respective administrative instructions, are not shown.

```
(func (result i32) (local i32)
try
ioannad marked this conversation as resolved.
Show resolved Hide resolved
try
try
throw x
aheejin marked this conversation as resolved.
Show resolved Hide resolved
catch_all
i32.const 27
local.set 0
rethrow 0
ioannad marked this conversation as resolved.
Show resolved Hide resolved
end
delegate 0
catch x
local.get 0
end)
```

Take the frame `F = (locals i32.const 0, module m)`. We have:

```
↪ ↪ ↪ F; label_1{} (catch_1{a_x local.get 0}
(label_0{} (delegate{1}
(label_0{} (catch_0{i32.const 27 local.set 0 rethrow 0}
throw a_x end) end) end) end) end) end
```

For the empty throw context `T = [_]` the above is the same as

```
F; label_1{} (catch_1{a_x local.get 0}
label_0{} (delegate{1}
label_0{} (catch_0{i32.const 27 local.set 0 rethrow 0}
T[throw a_x] end) end) end) end) end

↪ F; label_1{} (catch_1{a_x local.get 0}
(label_0{} (delegate{1}
(label_0{} (caught{a_x} i32.const 27 local.set 0 rethrow 0
ioannad marked this conversation as resolved.
Show resolved Hide resolved
end) end) end) end) end) end
```

Let `F'` be the frame `{locals i32.const 27, module m}`, and let `B^0 = [_]`.

```
↪ F; label_1{} (catch_1{a_x local.get 0}
ioannad marked this conversation as resolved.
Show resolved Hide resolved
(label_0{} (delegate{1}
(label_0{} (caught{a_x} B^0 [rethrow 0]
ioannad marked this conversation as resolved.
Show resolved Hide resolved
end) end) end) end) end) end

↪ F; label_1{} (catch_1{a_x local.get 0}
ioannad marked this conversation as resolved.
Show resolved Hide resolved
(label_0{} (delegate{1}
(label_0{} (caught{a_x} B^0 [throw a_x]
ioannad marked this conversation as resolved.
Show resolved Hide resolved
end) end) end) end) end) end
```

Let `T' = label_0{} (caught{a_x} [_] end) end`, and `B^1 = label_0 [_] end`.
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
↪ F; label_1{} (catch_1{a_x local.get 0}
ioannad marked this conversation as resolved.
Show resolved Hide resolved
(B^1 [delegate{1}
T'[throw a_x] end] end) end

↪ F; label_1{} (catch_1{a_x local.get 0}
ioannad marked this conversation as resolved.
Show resolved Hide resolved
(throw a_x) end) end

↪ F; label_1{} (caught_1{a_x} local.get 0 end) end
ioannad marked this conversation as resolved.
Show resolved Hide resolved

↪ ↪ ↪ i32.const 27
```

## behaviour of `rethrow`
ioannad marked this conversation as resolved.
Show resolved Hide resolved

### example 1
ioannad marked this conversation as resolved.
Show resolved Hide resolved

Location of a rethrown exception.

```
try
val1
throw x
ioannad marked this conversation as resolved.
Show resolved Hide resolved
catch x
try
val2
throw y
catch_all
try
val3
throw z
catch z
rethrow 2
end
end
end
```

In the above example, all thrown exceptions get caught and the first one gets rethrown from the catching block of the last one. So the above reduces to

```
label_0{} caught{a_x val1}
val1 (label_0{} caught{a_y val2}
(label_0{} caught{a_z val3}
val3 val1 (throw a_x) end end)
end end) end end)
```

which in this case is the same as `val1 (throw a_x)`.

### example 2
ioannad marked this conversation as resolved.
Show resolved Hide resolved

`rethrow`'s immediate validation error.

@aheejin gave the following
[example in this comment](https://github.com/WebAssembly/exception-handling/pull/143#discussion_r522673735)

```
try $label0
rethrow $label0 ;; cannot be done, because it's not within catch below
catch
end
```

This is a validation error (no catch block at given rethrow depth).

## target of `delegate`'s immediate (label depth)
ioannad marked this conversation as resolved.
Show resolved Hide resolved

@aheejin gave the following
[examples in this comment](https://github.com/WebAssembly/exception-handling/pull/143#discussion_r522673735)

### example 3
ioannad marked this conversation as resolved.
Show resolved Hide resolved

`delegate` targeting a catch is a validation error.

```
try $label0
catch
try
...
delegate $label0 ;; cannot be done, because $label0's catch is not below but above here
end
```

This is a validation error because `delegate`'s `$label0` refers to the catch-label `label { result ε, type catch}`, not to a try-label.

### example 4
ioannad marked this conversation as resolved.
Show resolved Hide resolved

`delegate` correctly targeting a `try-delegate` and a `try-catch`.

```
try $label1
try $label0
try
throw x
delegate $label0 ;; delegate 0
delegate $label1 ;; delegate 1
catch x
instr*
end
```

The thrown exception is (eventually) caught by the outer try's `catch x`, so the above reduces to the following.

```
label_0 {} (caught_0{a_x} (label_0 {} instr* end) end
```
201 changes: 201 additions & 0 deletions proposals/exception-handling/Exceptions-formal-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# 3rd proposal formal spec overview
ioannad marked this conversation as resolved.
Show resolved Hide resolved

This is an overview of the 3rd proposal's formal spec additions, to aid in discussions concerning the proposed semantics.

## Abstract Syntax

### Types

Tag Types
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
tagtype ::= [t*]→[]
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```

### Instructions

```
instr ::= ... | throw x | rethrow l
| try bt instr* (catch x instr*)* (catch_all instr*)? end
| try bt instr* delegate l
```

### Modules

Tags
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
tag ::= export* tag tagtype | export* tag tagtype import
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```

Modules
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
mod ::= module ... tag*
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```

## Validation (Typing)

#### Modification to labels
ioannad marked this conversation as resolved.
Show resolved Hide resolved

To verify that a `try...delegate l` instruction refers to a label surrounding the instructions of a try block (call this a try-label), introduce a `kind` attribute to labels in the validation context, which is set to `try` when the label is a try-label.

Similarly, to verify that the `rethrow l` instruction refers to a label surrounding the instructions of a catch block (call this a catch-label), we allow the `kind` attribute of labels in the validation context to be set to `catch` when the label is a catch-label.
ioannad marked this conversation as resolved.
Show resolved Hide resolved
labelkind ::= try | catch
labeltype ::= {result resulttype, kind labelkind?}
aheejin marked this conversation as resolved.
Show resolved Hide resolved
C ::= {..., labels labeltype}
ioannad marked this conversation as resolved.
Show resolved Hide resolved
aheejin marked this conversation as resolved.
Show resolved Hide resolved
The original notation `labels [t*]` is now an abbreviation for:

```
labels {result [t*], kind ε}
```


### Instructions


```
C.tags[x] = [t*]→[]
-----------------------------
C ⊢ throw x : [t1* t*]→[t2*]


C.labels[l].kind = catch
----------------------------
C ⊢ rethrow l : [t1*]→[t2*]


C.types[bt] = [t1*]→[t2*]
ioannad marked this conversation as resolved.
Show resolved Hide resolved
C, labels {result [t2*], kind try} ⊢ instr* : [t1*]→[t2*]
(C.tags[x] = [t*]→[] ∧
C, labels { result [t2*], kind catch } ⊢ instr* : [t*]→[t2*])*
(C, labels { result [t2*], kind catch } ⊢ instr'* : []→[t2*])?
ioannad marked this conversation as resolved.
Show resolved Hide resolved
-----------------------------------------------------------------------------
C ⊢ try bt instr* (catch x instr'*)* (catch_all instr''*)? end : [t1*]→[t2*]
ioannad marked this conversation as resolved.
Show resolved Hide resolved


C.types[bt] = [t1*]→[t2*]
ioannad marked this conversation as resolved.
Show resolved Hide resolved
C, labels {result [t2*], kind try} ⊢ instr* : [t1*]→[t2*]
C.labels[l].kind = try
----------------------------------------------------------
C ⊢ try bt instr* delegate l : [t1*]→[t2*]
```

## Execution (Reduction)

### Runtime structure
ioannad marked this conversation as resolved.
Show resolved Hide resolved

Stores
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
S ::= {..., tags taginst*}
```

Tag Instances
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
taginst ::= {type tagtype}
```

Module Instances
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
m ::= {..., tags a*}
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```

Administrative Instructions
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
instr ::= ... | throw a | catch_n{a? instr*}* instr* end
| delegate{l} instr* end | caught_m{a val^n} instr* end
```

Block contexts and label kinds
ioannad marked this conversation as resolved.
Show resolved Hide resolved

So far block contexts are only used in the reduction of `br l` and `return`, and only include labels or values on the stack above the hole. If we want to be able to break jumping over try-catch and try-delegate blocks, we must allow for the new administrative control instructions to appear after labels in block contexts, mirroring the label kinds of labels in validation contexts.

```
B^k ::= val* B'^k instr*
B'^0 ::= [_]
B'^{k+1} ::= label_n{instr*} B^k end | catch_m{a? instr*}* B^{k+1} end | caught_m{a val*} B^{k+1} end | delegate{l} B^{k+1} end
```

Note that `label_n{instr*} label_kind? [_] end? end` could be seen as a simplified control frame.

(Alternatively, we could have the above `label_kind`s be also labels, remove the additional `label_m` from the execution rules below, and remove the execution rules below where the new administrative instructions only contain `val*`. This would make labels even more similar to control frames.)

Throw Contexts
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
T ::= val* [_] instr* | label_n{instr*} T end | caught_m{a val^n} T end
| frame_n{F} T end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what purpose the throw contexts serve. Is it something like block contexts? I might be mistaken but I thought block contexts are a way to concisely describe multiple levels of nested labels... But not sure if that applies to this too. Why do reduction rules need throw contexts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC block contexts do not automatically contain other nested constructs (these are the delimited administrative catch...end, delegate...end, and caught...end). I think we have to explicitly add these to the definition of block contexts (lines 120-122 above).

On the other hand, throw contexts exclude the nested constructs catch_m { a^? instr* }* [_] end and delegate{l} [_] end, so any throw would have to stop when we encounter the first (innest) catch_m {...}* [_] end or delegate{l} [_] end.

The prose we used in the 2nd proposal might be clearer:

Throw contexts allow matching the program context around a throw instruction up to the nearest enclosing catch handler, thereby selecting the exception handler responsible for an exception.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a short explanation above the definition of throw contexts. Does that and my comment above clarify their purpose, @aheejin ?

```

### Instructions


```
F; throw x ↪ F; throw a (if F.module.tagaddrs[x]=a)

caught_m{a val^n} B^l[rethrow l] end
↪ caught_m{a val^n} B^l[val^n (throw a)] end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means rethrowing an exception is semantically the same as throwing the same values, which is true for the core spec.. It may carry auxiliary info like stack traces for JS API spec, in which case they are not identical, but I guess it is fine because this is only for core spec..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this doesn't affect the JS API, WDYT @Ms2ger ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
↪ caught_m{a val^n} B^l[val^n (throw a)] end
↪ caught_m{a val^n} T[val^n (throw a)] end

I think this can be fine at the moment because this is the core spec... By the way why is it still B^l?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that both B^l should be T? The intention is that the lin B^l is matching the one inrethrow l, otherwise how will we know which caught to target?`Did I miss a spec change on this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we consider this resolved, or am I missing the error?


caught_m{a val^n} val^m end ↪ val^m
```

An absent tagaddr in a `catch_m` clause (i.e., `a? = ε`) represents a `catch_all`.

```
F; val^n (try bt instr* (catch x instr'*)* (catch_all instr''*)? end)
↪ F; label_m{} (catch_m{a instr'*}*{instr''*}? val^n instr* end) end
(if bt = [t1^n]→[t2^m] ∧ (F.module.tagaddrs[x]=a)*)
ioannad marked this conversation as resolved.
Show resolved Hide resolved

catch_m{a? instr*}* val^m end ↪ val^m

S; F; catch_m{a1? instr*}{a'? instr'*}* T[val^n (throw a)] end
ioannad marked this conversation as resolved.
Show resolved Hide resolved
↪ S; F; caught_m{a val^n} (val^n)? instr* end
(if (a1? = ε ∨ a1? = a) ∧ S.tags(a).type = [t^n]→[])

S; F; catch_m{a1? instr*}{a'? instr'*}* T[val^n (throw a)] end
↪ S; F; catch_m{a'? instr'*}* T[val^n (throw a)] end
(if a1? ≠ ε ∧ a1? ≠ a)

S; F; catch_m T[val^n (throw a)] end
↪ S; F; val^n (throw a)
ioannad marked this conversation as resolved.
Show resolved Hide resolved


val^n (try bt instr* delegate l)
↪ label_m{} (delegate{l+1} val^n instr* end) end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why l+1? It would help if we have a brief description on what the administrative instruction delegate{} is (not the Wasm instruction delegate). The same for the other administrative instructions too

Copy link
Collaborator Author

@ioannad ioannad Sep 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why l+1?

The l becomes l+1 because we are adding a label around it, so we can break to it from inside instr*.

It would help if we have a brief description on what the administrative instruction delegate{} is (not the Wasm instruction delegate). The same for the other administrative instructions too

The administrative instructions catch_n ... end, caught_n ... end and delegate ... end are label-like, in that they (plus a surrounding label), delimit an instruction sequence, while also adding information on the special behaviour of these label/label-like combinations.

Off the top of my head perhaps it's possible to merge each of these with its label, and instead define new labels. So in particular redefine labels to be:

label ::= label_n{instr*} |  catch_n{a^? instr*}* |  delegate_n{l} | caught_n{a val*}

WDYT?
(cc. @rossberg)

Copy link
Member

@rossberg rossberg Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is another indication that the hack I suggested for typing catch labels – which is to put the block label outside the catch – is not the greatest idea (see also this comment).

I'd much prefer to keep the label statement separate. If you treated all of these as labels, all of them would need to carry a continuation. And likewise future block-constructs we might add. That conflates concerns, increasing complexity.

Unfortunately, there is some inherent conflation due to rethrow addressing its source catch via a label. (If we hadn't done that, i.e., only count catch clauses, we wouldn't have this problem.) That's why we need the ad-hoc annotation on the label in the context, and that's where the reduction hack comes from.

I'll try to come up with some replacement for this hack – dealing with a typing corner case shouldn't bleed into reduction rules in counter-intuitive ways.

Copy link
Collaborator Author

@ioannad ioannad Sep 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aheejin, @rossberg, thinking about it, is the +1 really necessary? If we look at try-delegate as:

  1. catching an exn,
  2. "breaking" inside the lth surrounding block,
  3. and throwing exn from there,

then I think we don't need the +1 when reducing to the administrative delegate. In fact I think the reduction of the administrative delegate is wrong to throw after (outside of) the l+1'th surrounding block. We should be breaking after the lth surrounding block (including the label added by try-delegate's reduction), and then doing a throw from there. Only then can we be redirected to a try-block's landing pad, if the destination of l is a try-block. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, you may be right that there is an off-by-1 bug that could work in our favour. Should be easy to check by working through an example. And of course with the interpreter, provided it matches the rules faithfully.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the long delay!

Please never mind my previous explanation. I looked at this again and realised that the reduction rule of the administrative delegate{ l } should resemble the reduction of br l:

label_m{} B^l[ delegate{l} T[val^n (throw a)] end ] end
  ↪ val^n (throw a)

This way, there is a unique B^l that matches the reduction rule.

I modified Example 4 and added Example 5 in Exceptions-formal-examples.md which show the reduction steps of the administrative delegates, to check that this works, and modified (or rather simplified) the interpreter accordingly, adding a couple of extra tests there.

I think that now it matches the idea of break-and-throw better. Note that the interpreter part that was removed didn't previously have a corresponding reduction rule in this formal-overview.

(if bt = [t^n]→[t^m])
ioannad marked this conversation as resolved.
Show resolved Hide resolved

delegate{l} val^n end ↪ val^n

B^l[ delegate{l} T[val^n (throw a)] end ]
↪ val^n (throw a)
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```

### Typing rules for administrative instructions
ioannad marked this conversation as resolved.
Show resolved Hide resolved

```
S.tags[a].type = [t*]→[]
--------------------------------
S;C ⊢ throw a : [t1* t*]→[t2*]

((S.tags[a].type = [t'*]→[])?
S;C, labels {result [t*], kind catch} ⊢ instr'* : [t'*?]→[t*])*
S;C, labels {result [t*], kind try} ⊢ instr* : []→[t*]
-----------------------------------------------------------------------
S;C, labels [t*] ⊢ catch_n{a? instr'*}* instr* end : []→[t*]

S;C, labels {result [t*], kind try} ⊢ instr* : []→[t*]
C.labels[l].kind = try
-----------------------------------------------------------------------
S;C, labels [t*] ⊢ delegate{l+1} instr* end : []→[t*]

S.tags[a].type = [t'*]→[]
(val:t')*
S;C, labels {result [t*], kind catch} ⊢ instr* : []→[t*]
aheejin marked this conversation as resolved.
Show resolved Hide resolved
--------------------------------------------------------------------------------
S;C, labels [t*] ⊢ caught_m{a val^n} instr* end : []→[t'*]
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```
ioannad marked this conversation as resolved.
Show resolved Hide resolved

ioannad marked this conversation as resolved.
Show resolved Hide resolved