Skip to content

Commit 7722992

Browse files
committed
squashed
1 parent fbb55e6 commit 7722992

File tree

4 files changed

+525
-31
lines changed

4 files changed

+525
-31
lines changed

proposals/stack-switching/Explainer.md

Lines changed: 202 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ validation rules to facilitate stack-switching.
2121
1. [Suspending continuations](#suspending-continuations)
2222
1. [Partial continuation application](#partial-continuation-application)
2323
1. [Continuation lifetime](#continuation-lifetime)
24+
1. [Further examples](#further-examples)
25+
1. [Extending the generator](#extending-the-generator)
26+
1. [Canceling tasks](#canceling-tasks)
2427
1. [Design considerations](#design-considerations)
2528
1. [Asymmetric switching](#asymmetric-switching)
2629
1. [Symmetric switching](#symmetric-switching)
@@ -116,20 +119,19 @@ The overall module implementing our example has the following shape.
116119
```wat
117120
(module $generator
118121
(type $ft (func))
119-
;; Types of continuations used by the generator:
122+
;; Type of continuations used by the generator:
120123
;; No need for param or result types: No data passed back to the
121124
;; generator when resuming it, and $generator function has no return
122125
;; values.
123126
(type $ct (cont $ft))
124127
125-
;; Tag used to coordinate between generator and consumer: The i32 param
126-
;; corresponds to the generated values passed; no values passed back from
127-
;; generator to consumer.
128-
(tag $gen (param i32))
129-
130-
131128
(func $print (import "spectest" "print_i32") (param i32))
132129
130+
;; Tag used to coordinate between generator and consumer: The i32 param
131+
;; corresponds to the generated values passed to consumer; no values passed
132+
;; back from generator to consumer.
133+
(tag $gen (param i32))
134+
133135
;; Simple generator yielding values from 100 down to 1
134136
(func $generator ...)
135137
(elem declare func $generator)
@@ -147,16 +149,16 @@ manipulate suspended continuations of type `(ref $ct)`.
147149
The generator is defined as follows.
148150

149151
```wat
150-
;; Simple generator yielding values from 100 down to 1
152+
;; Simple generator yielding values from 100 down to 1.
151153
(func $generator
152154
(local $i i32)
153155
(local.set $i (i32.const 100))
154-
(loop $l
155-
;; Suspend execution, pass current value of $i to consumer
156+
(loop $loop
157+
;; Suspend execution, pass current value of $i to consumer.
156158
(suspend $gen (local.get $i))
157-
;; Decrement $i and exit loop once $i reaches 0
159+
;; Decrement $i and exit loop once $i reaches 0.
158160
(local.tee $i (i32.sub (local.get $i) (i32.const 1)))
159-
(br_if $l)
161+
(br_if $loop)
160162
)
161163
)
162164
```
@@ -178,9 +180,9 @@ The consumer is defined as follows.
178180
179181
(loop $loop
180182
(block $on_gen (result i32 (ref $ct))
181-
;; Resume continuation $c
183+
;; Resume continuation $c.
182184
(resume $ct (on $gen $on_gen) (local.get $c))
183-
;; $generator returned: no more data
185+
;; $generator returned: no more data.
184186
(return)
185187
)
186188
;; Generator suspended, stack now contains [i32 (ref $ct)]
@@ -682,6 +684,192 @@ In order to ensure that continuations are one-shot, `resume`,
682684
suspended continuation such that any subsequent use of the same
683685
suspended continuation will result in a trap.
684686

687+
## Further examples
688+
689+
We now provide examples using tags with result values as well as the
690+
instructions `cont.bind` and `resume.throw`.
691+
To this end, we revisit the examples from [Section
692+
3](#introduction-to-continuation-based-stack-switching).
693+
694+
695+
696+
### Extending the generator
697+
698+
The `$generator` function introduced in [Section 3](#generators)
699+
produced the values 100 down to 1. It uses the tag `$gen`, defined as
700+
`(tag $gen (param i32))`, to send values to the `$producer` function.
701+
702+
We now consider a situation where the producer wants to indicate to
703+
the generator to reset itself (i.e., start counting down from 100
704+
again). To this end, we allow the producer to pass a boolean flag to
705+
the generator when resuming a continuation. We use type `i32` for the
706+
flag, leading to the following definition of `$gen`:
707+
708+
```wat
709+
(tag $gen (param i32) (result i32))
710+
```
711+
712+
In the generator, the instruction `(suspend $gen)` now has type `[i32]
713+
-> [i32]`: Its argument type represents the generated value (as in the
714+
original version of the example), the result type represents the flag
715+
obtained back from the producer. We change the generator to behave as
716+
follows, choosing between resetting or decrementing `$i`:
717+
718+
```wat
719+
(func $generator
720+
(local $i i32)
721+
(local.set $i (i32.const 100))
722+
(loop $loop
723+
;; Suspend execution, pass current value of $i to consumer
724+
(suspend $gen (local.get $i))
725+
;; We now have the flag on the stack given to us by the consumer, telling
726+
;; us whether to reset the generator or not.
727+
(if (result i32)
728+
(then (i32.const 100))
729+
(else (i32.sub (local.get $i) (i32.const 1)))
730+
)
731+
(local.tee $i)
732+
(br_if $loop)
733+
)
734+
)
735+
```
736+
737+
738+
In the producer, we then add some logic to pick the value of the flag,
739+
and pass it to the generator continuation on `resume`. However, this
740+
poses a challenge: The continuation created with `(cont.new $ct0
741+
(ref.func $generator)))` has the same type as before: A continuation
742+
type with no parameter or return types. In contrast, the type of the
743+
continuation received in a handler block for tag `$gen` is different
744+
from that type, due to the result type added to `$gen`: The result
745+
type of the tag becomes an additional parameter of the continuation
746+
received when handling that tag. This means that the producer now has
747+
to deal with two different continuation types:
748+
749+
```wat
750+
(type $ft0 (func))
751+
(type $ft1 (func (param i32)))
752+
;; Types of continuations used by the generator:
753+
;; No param or result types for $ct0: $generator function has no
754+
;; parameters or return values.
755+
(type $ct0 (cont $ft0))
756+
;; One param of type i32 for $ct1: An i32 is passed back to the
757+
;; generator when resuming it, and $generator function has no return
758+
;; values.
759+
(type $ct1 (cont $ft1))
760+
```
761+
762+
To avoid making the producer function unnecessarily complicated, we
763+
want to make sure that there is only a single local variable that
764+
contains the next continuation to resume. Its type will be `(ref $ct0)`.
765+
We can then use `cont.bind` to turn the continuations received in the
766+
handler block from type `(ref $ct1)` into `(ref $ct0)` by binding the
767+
value of the flag to be passed.
768+
769+
The overall function is then defined as follows:
770+
771+
```wat
772+
(func $consumer (export "consumer")
773+
;; The continuation of the generator.
774+
(local $c0 (ref $ct0))
775+
;; For temporarily storing the continuation received in handler.
776+
(local $c1 (ref $ct1))
777+
(local $i i32)
778+
;; Create continuation executing function $generator.
779+
;; Execution only starts when resumed for the first time.
780+
(local.set $c0 (cont.new $ct0 (ref.func $generator)))
781+
;; Just counts how many values we have received so far.
782+
(local.set $i (i32.const 1))
783+
784+
(loop $loop
785+
(block $on_gen (result i32 (ref $ct1))
786+
;; Resume continuation $c0
787+
(resume $ct0 (on $gen $on_gen) (local.get $c0))
788+
;; $generator returned: no more data
789+
(return)
790+
)
791+
;; Generator suspended, stack now contains [i32 (ref $ct0)]
792+
;; Save continuation to resume it in next iteration
793+
(local.set $c1)
794+
;; Stack now contains the i32 value yielded by $generator
795+
(call $print)
796+
797+
;; Calculate flag to be passed back to generator:
798+
;; Reset after the 42nd iteration
799+
(i32.eq (local.get $i) (i32.const 42))
800+
;; Create continuation of type (ref $ct0) by binding flag value.
801+
(cont.bind $ct1 $ct0 (local.get $c1))
802+
(local.set $c0)
803+
804+
(local.tee $i (i32.add (local.get $i) (i32.const 1)))
805+
(br_if $loop)
806+
)
807+
)
808+
```
809+
810+
811+
Here, we set the flag for resetting the generator exactly
812+
once, after it returned 42 values.
813+
814+
The full version of the extended generator example can be found
815+
[here](examples/generator-extended.wast).
816+
817+
### Canceling tasks
818+
819+
We now revisit the task scheduling example originally introduced
820+
in [Section 3](#task-scheduling).
821+
To yield execution, tasks either suspend to the scheduler running in
822+
the parent (first variant of example) or call a scheduling function
823+
that uses `switch` (second variant).
824+
825+
826+
We may want to adapt the example such that there is a limit on the
827+
number of tasks that can exist at the same time. Once that limit is
828+
reached, some task must be canceled before being able to schedule
829+
another one.
830+
831+
We can implement this with a small addition to the previous example.
832+
Instead of adding tasks to be scheduled directly to a queue, we
833+
call the following function `$schedule_task` with any continuation that
834+
should be scheduled.
835+
836+
```wat
837+
(func $schedule_task (param $c (ref null $ct))
838+
;; If the task queue is too long, cancel a task in the queue
839+
(if (i32.ge_s (call $task_queue-count) (global.get $concurrent_task_limit))
840+
(then
841+
(block $exc_handler
842+
(try_table (catch $abort $exc_handler)
843+
(resume_throw $ct $abort (call $task_dequeue))
844+
)
845+
)
846+
)
847+
)
848+
(call $task_enqueue (local.get $c))
849+
)
850+
```
851+
852+
The function checks if the current number of elements in the queue has
853+
already reached the limit. If so, the function takes an existing
854+
continuation from the queue and calls `resume_throw` on it.
855+
856+
Note that
857+
the `resume_throw` instruction is annotated with a newly defined tag, `$abort`.
858+
This tag denotes an exception that will be raised at the
859+
suspension point of the continuation. We then wrap the `resume_throw`
860+
instruction in a `try_table`, which installs an exception handler for
861+
`$abort`. This exception handler simply does nothing.
862+
Altogether this means that the exception raised at the suspension
863+
point cannot escape outside of the `$schedule_task` function, which
864+
then proceeds to enqueue the continuation given as a function
865+
argument.
866+
867+
Integrating the `$schedule_task` function above into the second
868+
variant of the task scheduling example (i.e., the variant using
869+
`switch`) can be found [here](examples/scheduler2-throw.wast).
870+
The changes to the first variant are analogous.
871+
872+
685873
## Design considerations
686874

687875
In this section we discuss some key design considerations.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
(module $generator
2+
(type $ft0 (func))
3+
(type $ft1 (func (param i32)))
4+
;; Type of continuations used by the generator:
5+
;; No param or result types for $ct0: $generator function has no
6+
;; parameters or return values.
7+
(type $ct0 (cont $ft0))
8+
9+
;; One param of type i32 for $ct1: An i32 is passed back to the
10+
;; generator when resuming it, and $generator function has no return
11+
;; values.
12+
(type $ct1 (cont $ft1))
13+
14+
(func $print (import "spectest" "print_i32") (param i32))
15+
16+
;; Tag used to coordinate between generator and consumer: The i32 param
17+
;; corresponds to the generated values passed to consumer, and the i32 result
18+
;; corresponds to the value passed from the consumer back to the generator.
19+
(tag $gen (param i32) (result i32))
20+
21+
22+
;; Simple generator yielding values from 100 down to 1.
23+
;; If non-zero value received back from consumer, resets counter to 100.
24+
(func $generator
25+
(local $i i32)
26+
(local.set $i (i32.const 100))
27+
(loop $loop
28+
;; Suspend execution, pass current value of $i to consumer.
29+
(suspend $gen (local.get $i))
30+
;; We now have the flag on the stack given to us by the consumer, telling
31+
;; us whether to reset the generator or not.
32+
(if (result i32)
33+
(then (i32.const 100))
34+
(else (i32.sub (local.get $i) (i32.const 1)))
35+
)
36+
(local.tee $i)
37+
(br_if $loop)
38+
)
39+
)
40+
(elem declare func $generator)
41+
42+
(func $consumer (export "consumer")
43+
;; The continuation of the generator.
44+
(local $c0 (ref $ct0))
45+
;; For temporarily storing the continuation received in handler.
46+
(local $c1 (ref $ct1))
47+
(local $i i32)
48+
;; Create continuation executing function $generator.
49+
;; Execution only starts when resumed for the first time.
50+
(local.set $c0 (cont.new $ct0 (ref.func $generator)))
51+
;; Just counts how many values we have received so far.
52+
(local.set $i (i32.const 1))
53+
54+
(loop $loop
55+
(block $on_gen (result i32 (ref $ct1))
56+
;; Resume continuation $c0
57+
(resume $ct0 (on $gen $on_gen) (local.get $c0))
58+
;; $generator returned: no more data
59+
(return)
60+
)
61+
;; Generator suspended, stack now contains [i32 (ref $ct0)]
62+
;; Save continuation to resume it in next iteration
63+
(local.set $c1)
64+
;; Stack now contains the i32 value yielded by $generator
65+
(call $print)
66+
67+
;; Calculate flag to be passed back to generator:
68+
;; Reset after the 42nd iteration
69+
(i32.eq (local.get $i) (i32.const 42))
70+
;; Create continuation of type (ref $ct0) by binding flag value.
71+
(cont.bind $ct1 $ct0 (local.get $c1))
72+
(local.set $c0)
73+
74+
(local.tee $i (i32.add (local.get $i) (i32.const 1)))
75+
(br_if $loop)
76+
)
77+
)
78+
79+
)
80+
81+
(invoke "consumer")

0 commit comments

Comments
 (0)