Skip to content
Merged
Changes from all commits
Commits
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
149 changes: 133 additions & 16 deletions proposals/stack-switching/Explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,15 @@ We change the wellformedness condition for tag types to be more liberal, i.e.

In other words, the return type of tag types is allowed to be non-empty.

We also introduce tag uses, which can be either tag indices or tag addresses:

- `taguse ::= tagidx | tagaddr`
- `$e : [t1*] -> [t2*]`
- iff `C.tags[$e] = tag $ft`
- and `C.types[$ft] ~~ func [t1*] -> [t2*]`
- `ea : [t1*] -> [t2*]`
- iff `S.tags[ea].type ~~ func [t1*] -> [t2*]`

### Instructions

The new instructions and their validation rules are as follows. To simplify the presentation, we write this:
Expand Down Expand Up @@ -793,31 +802,27 @@ This abbreviation will be formalised with an auxiliary function or other means i
- and `C.types[$ft] ~~ func [te*] -> []`
- and `(hdl : t2*)*`

- `hdl = (on <tagidx> <labelidx>) | (on <tagidx> switch)`
- `hdl = (on <taguse> <labelidx>) | (on <taguse> switch)`
- Handlers attached to `resume` and `resume_throw`, handling control tags for `suspend` and `switch`, respectively.
- `(on $e $l) : t*`
- iff `C.tags[$e] = tag $ft`
- and `C.types[$ft] ~~ func [t1*] -> [t2*]`
- `(on tu $l) : t*`
- iff `tu : [t1*] -> [t2*]`
- and `C.labels[$l] = [t1'* (ref null? $ct)]`
- and `t1* <: t1'*`
- and `C.types[$ct] ~~ cont [t2'*] -> [t'*]`
- and `[t2*] -> [t*] <: [t2'*] -> [t'*]`
- `(on $e switch) : t*`
- iff `C.tags[$e] = tag $ft`
- and `C.types[$ft] ~~ func [] -> [t*]`
- `(on tu switch) : t*`
- iff `tu : [] -> [t*]`

- `suspend <tagidx>`
- `suspend <taguse>`
- Use a control tag to suspend the current computation.
- `suspend $t : [t1*] -> [t2*]`
- iff `C.tags[$t] = tag $ft`
- and `C.types[$ft] ~~ func [t1*] -> [t2*]`
- `suspend tu : [t1*] -> [t2*]`
- iff `tu : func [t1*] -> [t2*]`

- `switch <typeidx> <tagidx>`
- `switch <typeidx> <taguse>`
- Switch to executing a given continuation directly, suspending the current execution.
- The suspension and switch are performed from the perspective of a parent `(on $e switch)` handler, determined by the annotated control tag.
- `switch $ct1 $e : [t1* (ref null $ct1)] -> [t2*]`
- iff `C.tags[$e] = tag $ft`
- and `C.types[$ft] ~~ func [] -> [t*]`
- `switch $ct1 tu : [t1* (ref null $ct1)] -> [t2*]`
- iff `tu : [] -> [t*]`
- and `C.types[$ct1] ~~ cont [t1* (ref null? $ct2)] -> [te1*]`
- and `te1* <: t*`
- and `C.types[$ct2] ~~ cont [t2*] -> [te2*]`
Expand All @@ -833,6 +838,118 @@ events and only `(on $e switch)` handlers can handle `switch`
events. The handler search continues past handlers for the wrong kind
of event, even if they use the correct tag.

#### Store extensions

* New store component `conts` for allocated continuations
- `S ::= {..., conts <continst>?*}`

* A continuation is a context annotated with its hole's arity
- `continst ::= (E : n)`


#### Administrative instructions

* `(ref.cont a)` represents a continuation value, where `a` is a *continuation address* indexing into the store's `conts` component
- `ref.cont a : [] -> [(ref $ct)]`
Copy link
Member

Choose a reason for hiding this comment

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

There may be many valid choices of $ct here, which violates our principal typing rules. Or are those not intended to apply to administrative instructions?

Copy link
Member

Choose a reason for hiding this comment

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

@rossberg, can you answer this?

- iff `S.conts[a] = epsilon \/ S.conts[a] = (E : n)`
+ iff `E[val^n] : t2*`
Copy link
Contributor

Choose a reason for hiding this comment

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

There are two ways of doing this. The first is the one displayed here, where type-checking the ref.cont instructions requires typechecking the body of the continuation here and now. The other one (which is the one used in the Iris-WasmFX mechanisation) is to merely read a type annotation here; and instead add a clause to the (unshown here) store_typing predicate that describes a well-formed state, mandating that all continuations in the store must have a body that type-checks. From a theoretical point of view, I prefer the second solution. Besides, the second approach is the one used when typechecking the invoke administrative instruction.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, I'd generally prefer the first option, since that is a more faithful reflection of the intended runtime representation that erases these types. We really want to know that this is sound, so ideally, even a mechanised soundness proof would model the store without introducing additional type information that may affect the result in subtle ways.

Invoke is different in that functions are already type-annotated in the source program, and these types are in fact kept around in real implementations (e.g., to perform link-time type-checks).

Copy link
Contributor

Choose a reason for hiding this comment

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

Does typechecking the instructions inside the continuation require a type context C? If so, where does the context come from?

Copy link
Contributor

Choose a reason for hiding this comment

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

In the WasmFX-Cert mechanisation in the Rocq proof assistant, we use the empty context. This is ultimately irrelevant since all continuations start off as a function call and the body of a function call is typechecked using a different typing context as per the typing rules of (plain) WebAssembly

+ and `(val : t1)^n`
- and `$ct ~~ cont $ft`
- and `$ft ~~ [t1^n] -> [t2*]`

* `(prompt{<hdl>*} <instr>* end)` represents an active handler
Copy link
Contributor

Choose a reason for hiding this comment

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

Sidenote: the Iris-WasmFX mechanisation adds one more immediate argument to the prompt instruction: the type t* expected for the body <instr>*. This is necessary to define the behaviour of the suspend instruction since the mechanisation stores each continuation together with its expected type t1* -> t2*. If the type annotation was not present in the prompt instruction, it would be impossible to know the return type t2* of the captured continuation when reducing suspend. It is easy to transform the presentation from the mechanisation into this one (just forget the type annonation).

- `(prompt{hdl*}? instr* end) : [] -> [t*]`
- iff `instr* : [] -> [t*]` in the empty context
- and `(hdl : [t*])*`

#### Handler contexts

```
H^ea ::=
_
val* H^ea instr*
label_n{instr*} H^ea end
frame_n{F} H^ea end
catch{...} H^ea end
prompt{hdl*} H^ea end (iff ea notin hdl*)
```


#### Reduction

* `S; F; (ref.null t) (cont.new $ct) --> S; F; trap`

* `S; F; (ref.func fa) (cont.new $ct) --> S'; F; (ref.cont |S.conts|)`
- iff `S' = S with conts += (E : n)`
- and `E = _ (invoke fa)`
- and `$ct ~~ cont $ft`
- and `$ft ~~ [t1^n] -> [t2*]`

* `S; F; (ref.null t) (cont.bind $ct $ct') --> S; F; trap`

* `S; F; (ref.cont ca) (cont.bind $ct $ct') --> S; F; trap`
- iff `S.conts[ca] = epsilon`

* `S; F; v^n (ref.cont ca) (cont.bind $ct $ct') --> S'; F; (ref.cont |S.conts|)`
- iff `S.conts[ca] = (E' : n')`
- and `$ct' ~~ cont $ft'`
- and `$ft' ~~ [t1'*] -> [t2'*]`
- and `n = n' - |t1'*|`
- and `S' = S with conts[ca] = epsilon with conts += (E : |t1'*|)`
- and `E = E'[v^n _]`

* `S; F; (ref.null t) (resume $ct hdl*) --> S; F; trap`

* `S; F; (ref.cont ca) (resume $ct hdl*) --> S; F; trap`
- iff `S.conts[ca] = epsilon`

* `S; F; v^n (ref.cont ca) (resume $ct hdl*) --> S'; F; prompt{hdl'*} E[v^n] end`
- iff `S.conts[ca] = (E : n)`
- and `S' = S with conts[ca] = epsilon`
Copy link
Contributor

Choose a reason for hiding this comment

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

hdl must be desugared here: on the LHS it contains tags and on the RHS it should contain tag addresses. The field F.tags in the frame converts one to the other.

- and `hdl'* = hdl*[F.module.tags / 0..|F.module.tags|-1]`

* `S; F; (ref.null t) (resume_throw $ct $e hdl*) --> S; F; trap`

* `S; F; (ref.cont ca) (resume_throw $ct $e hdl*) --> S; F; trap`
- iff `S.conts[ca] = epsilon`

* `S; F; v^m (ref.cont ca) (resume_throw $ct $e hdl*) --> S''; F; prompt{hdl'*} E[(ref.exn |S'.exns|) throw_ref] end`
- iff `S.conts[ca] = (E : n)`
- and `ta = S.tags[F.module.tags[$e]]`
- and `ta.type ~~ [t1^m] -> [t2*]`
- and `S' = S with exns += {ta, v^m}`
- and `S'' = S' with conts[ca] = epsilon`
- and `hdl'* = hdl*[F.module.tags / 0..|F.module.tags|-1]`

* `S; F; (prompt{hdl*} v* end) --> S; F; v*`

* `S; F; (suspend $e) --> S; F; (suspend ea)`
- iff `ea = F.module.tags[$e]`

* `S; F; (prompt{hdl1* (on ea $l) hdl2*} H^ea[v^n (suspend ea)] end) --> S'; F; v^n (ref.cont |S.conts|) (br $l)`
- iff `forall $l', (on ea $l') notin hdl1*`
- and `S.tags[ea].type ~~ [t1^n] -> [t2^m]`
- and `S' = S with conts += (H^ea : m)`

* `S; F; (switch $ct $e) --> S; F; (switch $ct ea)`
- iff `ea = F.module.tags[$e]`

* `S; F; (ref.null t) (switch $ct ea) --> S; F; trap`

* `S; F; (ref.cont ca) (switch $ct ea) --> S; F; trap`
- iff `S.conts[ca] = epsilon`

* `S; F; (prompt{hdl1* (on ea switch) hdl2*} H^ea[v^n (ref.cont ca) (switch $ct ea)] end) --> S''; F; prompt{hdl1* (on ea switch) hdl2*} E[v^n (ref.cont |S.conts|)] end`
- iff `S.conts[ca] = (E : n')`
- and `n' = 1 + n`
- and `(on switch ea) notin hdl1*`
- and `$ct ~~ cont $ft`
- and `$ft ~~ [t1* (ref $ct2)] -> [t2*]`
- and `$ct2 ~~ cont $ft2`
- and `$ft2 ~~ [t1'^m] -> [t2'*]`
- and `S' = S with conts[ca] = epsilon`
- and `S'' = S' with conts += (H^ea : m)`
Copy link
Contributor

Choose a reason for hiding this comment

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

Sidenote: the Iris-WasmFX mechanisation does not yet have the switch instruction. I will add it shortly, but cannot at present comment on this reduction rule. However on a first glance, it appears that this reduction rule might suffer from the same issue as the suspend rule: the tag $e should be desugared not with frame F but the innermost frame of H.

Copy link
Contributor

Choose a reason for hiding this comment

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

Update: the Iris-WasmFX mechanisation now has the switch instruction and type soundness has been proven. The issue using the correct frame when desugaring $e is indeed present


### Binary format

We extend the binary format of composite types, heap types, and instructions.
Expand All @@ -856,7 +973,7 @@ The opcode for heap types is encoded as an `s33`.

#### Instructions

We use the use the opcode space `0xe0-0xe5` for the seven new instructions.
We use the use the opcode space `0xe0-0xe5` for the six new instructions.

| Opcode | Instruction | Immediates |
| ------ | ------------------------ | ---------- |
Expand Down