-
Notifications
You must be signed in to change notification settings - Fork 36
formal spec overview for the 3rd exception handling proposal #143
Changes from 16 commits
9c0c1a8
2dc31f8
57c41e5
51c360b
5a988d5
aafc4b1
4bb9f84
1afd21a
4ff1c38
fbe87b0
fa5a237
83872ed
4552306
8cfa1b6
7d122dd
d874fcd
c98ff33
72bf4f3
4606660
662343c
b12eec9
9b02deb
bb0a623
5457b7a
6f681ff
315fe15
219d92e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
# 3rd proposal formal spec examples | ||
|
||
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 | ||
``` |
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 On the other hand, throw contexts exclude the nested constructs The prose we used in the 2nd proposal might be clearer:
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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..? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this doesn't affect the JS API, WDYT @Ms2ger ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think this can be fine at the moment because this is the core spec... By the way why is it still There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean that both There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The
The administrative instructions 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:
WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
then I think we don't need the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
This way, there is a unique I modified Example 4 and added Example 5 in 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
|
Uh oh!
There was an error while loading. Please reload this page.