@@ -21,6 +21,9 @@ validation rules to facilitate stack-switching.
21
21
1 . [ Suspending continuations] ( #suspending-continuations )
22
22
1 . [ Partial continuation application] ( #partial-continuation-application )
23
23
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 )
24
27
1 . [ Design considerations] ( #design-considerations )
25
28
1 . [ Asymmetric switching] ( #asymmetric-switching )
26
29
1 . [ Symmetric switching] ( #symmetric-switching )
@@ -116,20 +119,19 @@ The overall module implementing our example has the following shape.
116
119
``` wat
117
120
(module $generator
118
121
(type $ft (func))
119
- ;; Types of continuations used by the generator:
122
+ ;; Type of continuations used by the generator:
120
123
;; No need for param or result types: No data passed back to the
121
124
;; generator when resuming it, and $generator function has no return
122
125
;; values.
123
126
(type $ct (cont $ft))
124
127
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
-
131
128
(func $print (import "spectest" "print_i32") (param i32))
132
129
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
+
133
135
;; Simple generator yielding values from 100 down to 1
134
136
(func $generator ...)
135
137
(elem declare func $generator)
@@ -147,16 +149,16 @@ manipulate suspended continuations of type `(ref $ct)`.
147
149
The generator is defined as follows.
148
150
149
151
``` wat
150
- ;; Simple generator yielding values from 100 down to 1
152
+ ;; Simple generator yielding values from 100 down to 1.
151
153
(func $generator
152
154
(local $i i32)
153
155
(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.
156
158
(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.
158
160
(local.tee $i (i32.sub (local.get $i) (i32.const 1)))
159
- (br_if $l )
161
+ (br_if $loop )
160
162
)
161
163
)
162
164
```
@@ -178,9 +180,9 @@ The consumer is defined as follows.
178
180
179
181
(loop $loop
180
182
(block $on_gen (result i32 (ref $ct))
181
- ;; Resume continuation $c
183
+ ;; Resume continuation $c.
182
184
(resume $ct (on $gen $on_gen) (local.get $c))
183
- ;; $generator returned: no more data
185
+ ;; $generator returned: no more data.
184
186
(return)
185
187
)
186
188
;; Generator suspended, stack now contains [i32 (ref $ct)]
@@ -682,6 +684,192 @@ In order to ensure that continuations are one-shot, `resume`,
682
684
suspended continuation such that any subsequent use of the same
683
685
suspended continuation will result in a trap.
684
686
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
+
685
873
## Design considerations
686
874
687
875
In this section we discuss some key design considerations.
0 commit comments