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 4 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
47 changes: 24 additions & 23 deletions proposals/exception-handling/Exceptions-formal-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,22 @@ Note that the block contexts and throw contexts given for the reductions are the

The only example with an almost full reduction trace, and all new instructions. Such explicit reduction steps are only shown in Example 4 and Example 5, to highlight the reduction step of the administrative `delegate`.

In the following reduction, we don't show the first 4 steps, which just reduce the several `try`s and the `throw x` to their respective administrative instructions.
In the following reduction, we don't show the first 4 steps, which just reduce the several `try`s and the `throw x` to their respective administrative instructions. The type of the tag `$x` here is `[]→[]`.

```
(tag $x)
(func (result i32) (local i32)
try (result i32)
try
try
throw x
throw $x
catch_all
i32.const 27
local.set 0
rethrow 0
end
delegate 0
catch x
catch $x
local.get 0
end)
```
Expand All @@ -48,12 +49,12 @@ We write the body of this function in folded form, because it is easier to parse
(do
(try
(do
(throw x))
(throw $x))
(catch_all
(local.set 0 (i32.const 27))
(rethrow 0))))
(delegate 0)))
(catch x
(catch $x
(local.get 0)))
```

Expand Down Expand Up @@ -149,22 +150,22 @@ Use the trivial throw context `T` again, this time to match the throw to the `ca

### Example 1

Location of a rethrown exception.
Location of a rethrown exception. Let `x, y, z` be tag indices of tags with type `[t_x]→[]`, `[t_y]→[]`, and `[t_z]→[]` respectively. Let `val_p : t_p` for every `p` among `x, y, z`.

```
try
val1
val_x
throw x
ioannad marked this conversation as resolved.
Show resolved Hide resolved
catch x
try
val2
try $label2
val_y
throw y
catch_all
try
val3
val_z
throw z
catch z
rethrow 2
rethrow $label2 ;; This is rethrow 2.
end
end
end
Expand All @@ -175,17 +176,17 @@ Folded it looks as follows.
```
(try
(do
val1
val_x
(throw x))
(catch x ;; <--- (rethrow 2) targets this catch.
(try
(do
val2
val_y
(throw y))
(catch_all
(try
(do
val3
val_z
(throw z))
(catch z
(rethrow 2)))))))
Expand All @@ -195,20 +196,20 @@ In the above example, all thrown exceptions get caught and the first one gets re

```
(label_0{}
(caught_0{ a_x val1 }
val1
(caught_0{ a_x val_x } ;; <---- The exception rethrown by `rethrow 2` below.
val_x
(label_0{}
(caught_0{ a_y val2 }
;; The catch_all does not leave val2 here.
(caught_0{ a_y val_y }
;; The catch_all does not leave val_y here.
(label_0{}
(caught_0{ a_z val3 }
val3
;; (rethrow 2) puts val1 and the throw below.
val1
(caught_0{ a_z val_z }
val_z
;; (rethrow 2) puts val_x and the throw below.
val_x
(throw a_x) end) end) end) end) end) end)
```

This reduces to `val1 (throw a_x)`, throwing to the caller.
This reduces to `val_x (throw a_x)`, throwing to the caller.

### Example 2

Expand Down
101 changes: 58 additions & 43 deletions proposals/exception-handling/Exceptions-formal-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,25 @@ mod ::= 'module' ... tag*
To verify that the `rethrow l` instruction refers to a label surrounding the instructions of a catch block (call this a catch-label), we introduce a `kind` attribute to labels in the validation context, which is set to `catch` when the label is a catch-label and empty otherwise.

```
labelkind ::= catch
labeltype ::= {result resulttype, kind labelkind?}
C ::= {..., labels labeltype}
labelkind ::= 'catch'
labeltype ::= 'catch'? resulttype
C ::= {..., 'labels' labeltype}
```

ioannad marked this conversation as resolved.
Show resolved Hide resolved
The original notation `labels [t*]` is now an abbreviation for:

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

### Validation Contexts

### Instructions
Validation contexts now hold a list of tag types, one for each tag known to them.
```
C ::= { ..., 'tags' tagtype*}
Copy link
Member

Choose a reason for hiding this comment

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

You also modify the definition of C on line 46. Maybe move that here as well?

```

### Validation Rules for Instructions


```
Expand All @@ -70,8 +76,8 @@ C ⊢ rethrow l : [t1*]→[t2*]
C ⊢ bt : [t1*]→[t2*]
C, labels [t2*] ⊢ instr* : [t1*]→[t2*]
(C.tags[x] = [t*]→[] ∧
C, labels { result [t2*], kind catch } ⊢ instr'* : [t*]→[t2*])*
(C, labels { result [t2*], kind catch } ⊢ instr''* : []→[t2*])?
C, labels catch [t2*] ⊢ instr'* : [t*]→[t2*])*
(C, labels catch [t2*] ⊢ instr''* : []→[t2*])?
Comment on lines +79 to +80
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
C, labels catch [t2*] ⊢ instr'* : [t*][t2*])*
(C, labels catch [t2*] ⊢ instr''* : [][t2*])?
C, labels (catch [t2*]) ⊢ instr'* : [t*][t2*])*
(C, labels (catch [t2*]) ⊢ instr''* : [][t2*])?

-----------------------------------------------------------------------------
C ⊢ try bt instr* (catch x instr'*)* (catch_all instr''*)? end : [t1*]→[t2*]

Expand All @@ -90,86 +96,87 @@ C ⊢ try bt instr* delegate l : [t1*]→[t2*]
#### Stores

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

#### Tag Instances

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

#### Module Instances

```
m ::= {..., tags tagaddr*}
m ::= {..., 'tags' tagaddr*}
```

#### Administrative Instructions

```
instr ::= ... | 'throw' tagaddr | 'catch'_n{ tagaddr? instr* }* instr* 'end'
| delegate{ labelidx } instr* end | caught_m{ tagaddr val^n } instr* end
instr ::= ... | 'throw' tagaddr | 'catch'{ tagaddr? instr* }* instr* 'end'
| 'delegate'{ labelidx } instr* 'end' | 'caught'{ tagaddr val^n } instr* 'end'
```

#### Block Contexts and Label Kinds

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.
So far block contexts are only used in the reduction of `br l` and `return`, and only include labels or values on the stack on the left side of 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.

```
B^0 ::= val* [_] instr*
B^k ::= catch_m{ tagaddr? instr* }* B^k end | caught_m{ tagaddr val* } B^k end
| delegate{ labelidx } B^k end
B^{k+1} ::= val* (label_n{instr*} B^k end) instr*
B^0 ::= val* '[_]' instr* | val* C^0 instr*
B^{k+1} ::= val* ('label'_n{instr*} B^k 'end') instr* | val* C^{k+1} instr*
C^k ::= 'catch'{ tagaddr? instr* }* B^k 'end'
| 'caught'{ tagaddr val* } B^k 'end'
| 'delegate'{ labelidx } B^k 'end'
```

#### Throw Contexts

Throw contexts don't skip over handlers, they are used to match a thrown exception with the innermost handler.
Throw contexts don't skip over handlers (administrative `catch` or `delegate` instructions).
Throw contexts are used to match a thrown exception with the innermost handler.

```
T ::= val* [_] instr* | label_n{instr*} T end | caught_m{ tagaddr val^n } T end
| frame_n{F} T end
T ::= val* '[_]' instr* | 'label'_n{instr*} T 'end'
| 'caught'{ tagaddr val^n } T 'end'
| 'frame'_n{F} T end
```

Note that because `catch_n` and `delegate` instructions are not included above, there is always a unique maximal throw context.
Note that because `catch` and `delegate` instructions are not included above, there is always a unique maximal throw context to match the reduction rules. Note that this basically means that `caught{..} instr* end` is not a potential catching block for exceptions thrown by `instr*`. The instruction sequence `instr*` is inside a `catch` or `catch_all` block.

### Reduction of Instructions

Reduction steps for the new instructions or administrative instructions.

An absent tag address in a `catch` administrative instruction (i.e., `a? = ε`) represents a `catch_all`.

```
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
caught{a val^n} B^l[rethrow l] end
caught{a val^n} B^l[val^n (throw a)] end

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

An absent tag address in a `handler` 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
↪ F; label_m{} (catch{a instr'*}*{ε instr''*}? val^n instr* end) end
(if expand_F(bt) = [t1^n]→[t2^m] ∧ (F.module.tagaddrs[x]=a)*)

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

S; F; catch_m{a1? instr*}{a'? instr'*}* T[val^n (throw a)] end
↪ S; F; caught_m{a val^n} (val^n)? instr* end
S; F; catch{a1? instr*}{a'? instr'*}* T[val^n (throw a)] end
↪ S; F; caught{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
catch{a1? instr*}{a'? instr'*}* T[val^n (throw a)] end
catch{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)
catch T[val^n (throw a)] end ↪ val^n (throw a)


val^n (try bt instr* delegate l)
↪ label_m{} (delegate{l} val^n instr* end) end
F; val^n (try bt instr* delegate l)
F; label_m{} (delegate{l} val^n instr* end) end
(if expand_F(bt) = [t^n]→[t^m])

delegate{l} val^n end ↪ val^n
Expand All @@ -178,7 +185,12 @@ label_m{} B^l[ delegate{l} T[val^n (throw a)] end ] end
↪ val^n (throw a)
ioannad marked this conversation as resolved.
Show resolved Hide resolved
```

Note that the last reduction step above is similar to the reduction of `br`.
Note that the last reduction step above is similar to the reduction of `br l` [1](https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-br-l), if we look at the entire `delegate{l}...end` as the `br l`, but also doing a throw after it breaks.

There is a subtle difference though. The instruction `br l` searches for the `l+1`th surrounding block and breaks out after that block. Because `delegate{l}` is always wrapped in its own `label_n{} ... end` [2], with the same lookup as for `br l` we end up breaking inside the `l+1`th surrounding block, and throwing there. So if that `l+1`th surrounding block is a try, we end up throwing in its "try code", and thus correctly getting delegated to that try's catches.

- [1] [The execution step for `br l`](https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-br-l)
- [2] The label that always wraps `delegate{l}...end` can be thought of as "level -1" and cannot be referred to by the delegate's label index `l`.

### Typing Rules for Administrative Instructions

Expand All @@ -188,10 +200,10 @@ 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 catch [t*] ⊢ instr'* : [t'*?]→[t*])*
S;C, labels [t*] ⊢ instr* : []→[t*]
-----------------------------------------------------------------
S;C, labels [t*] ⊢ catch_n{a? instr'*}* instr* end : []→[t*]
-----------------------------------------------------------
S;C, labels [t*] ⊢ catch{a? instr'*}* instr* end : []→[t*]

S;C, labels [t*] ⊢ instr* : []→[t*]
C.labels[l] = [t'*]
Copy link
Member

Choose a reason for hiding this comment

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

I believe this should actually be

Suggested change
C.labels[l] = [t'*]
C.labels[l+1] = [t'*]

Otherwise, it would be referring to a different label than the original try-delegate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@rossberg: I think the +1 is missing because of the behaviour of "breaking inside the label". This way we also cover the case of delegating to the function body. WDYT?

Copy link
Member

@rossberg rossberg Feb 15, 2022

Choose a reason for hiding this comment

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

Are you sure? If you have

try ... delegate $l

you'll reduce to

label{} (delegate{$l} ... end) end

When you type this, the outer label adds an entry to the labels environment before you get to this typing rule, unlike with the try-delegate before the reduction. So you have to skip over that prior label in this rule.

Copy link
Collaborator Author

@ioannad ioannad Feb 21, 2022

Choose a reason for hiding this comment

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

@rossberg Apologies for the delay, last week ended up unexpectedly way too full.

Is it possible that the +1 you're missing is hidden in the labels [t*] attached to C in the other two judgements? The typing rule for try-delegate gives exactly the premises of this typing rule for adm.delegate, which in turn matches the typing for adm.label, to derive the type for that last expression in the end - I think.

More concretely, if I'm not mistaken, in your question we have a derivation of

(1)   C ⊢ try bt instr* delegate $l : []→[t^n]

and because try bt instr* delegate $l reduces to label_n{} (delegate{$l} instr* end) end we should be able to derive that C ⊢ label_n{} (delegate{$l} instr* end) end : []→[t^n].

If this is what you ask to show, then from the derivation of (1) we know the following.

(2)   C ⊢ bt : []→[t^n]
(3)   C, labels [t^n] ⊢ instr* : []→[t^n]
(4)   C.labels[l] = [t'*]

By having (3) and (4) as the premises for the typing rule of adm.delegate we derive

C, labels [t^n] ⊢ delegate{l} instr* end : []→[t^n]

which we can use as a premise to the typing rule for adm.label to get the desired

C ⊢ label_n{} (delegate{l} instr* end) end : []→[t^n]

WDYT?

Copy link
Member

@rossberg rossberg Feb 22, 2022

Choose a reason for hiding this comment

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

The problem I see is that (4) is not the premise you'll need when arriving at the adm.delegate rule. Instead you'll need

(4') C'[l'] = [t'*]

with C' = C, labels [t^n] and some l', because that's the context you now enter the rule with. The only suitable l' that allows you to prove this from (4) is l+1, I believe.

Copy link
Member

Choose a reason for hiding this comment

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

If we change the current rule

F; val^n (try bt instr* delegate l)
  ↪ F; label_m{} (delegate{l} val^n instr* end) end

to

F; val^n (try bt instr* delegate l)
  ↪ F; delegate(l) (label_m{} val^n instr* end) end

meaning, if we put the label created by the try-delegate itself within the scope of adm.delegate, can this confusion be helped?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Continuing the discussion with a suggestion in #205 .

Expand All @@ -200,8 +212,11 @@ S;C, labels [t*] ⊢ delegate{l} instr* end : []→[t*]

S.tags[a].type = [t'*]→[]
(val:t')*
S;C, labels {result [t*], kind catch} ⊢ instr* : []→[t*]
S;C, labels catch [t*] ⊢ instr* : []→[t*]
----------------------------------------------------------
S;C, labels [t*] ⊢ caught_m{a val^n} instr* end : []→[t*]
S;C, labels [t*] ⊢ caught{a val^n} instr* end : []→[t*]
```

ioannad marked this conversation as resolved.
Show resolved Hide resolved
## TODO Uncaught Exceptions

We haven't yet described the formalism for an uncaught exception being the result of evaluation.