@@ -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 )
24271 . [ Design considerations] ( #design-considerations )
2528 1 . [ Asymmetric switching] ( #asymmetric-switching )
2629 1 . [ Symmetric switching] ( #symmetric-switching )
@@ -78,12 +81,13 @@ design provides a form of *symmetric switching*.
7881
7982We illustrate the proposed stack-switching mechanism using two
8083examples: generators and task scheduling. The generators example uses
81- asymmetric stack-switching and the task scheduling example uses
82- symmetric stack-switching.
84+ asymmetric stack-switching. The task scheduling example has two
85+ variants: the first variant uses asymmetric stack-switching and the
86+ second variant uses symmetric stack-switching.
8387
8488### Generators
8589
86- The first example illustrates a generator-consumer pattern. Execution
90+ Our first example illustrates a generator-consumer pattern. Execution
8791switches back and forth between a generator and a consumer execution
8892stack. Whenever execution switches from the generator to the consumer
8993the generator also passes a value to the consumer.
@@ -116,20 +120,19 @@ The overall module implementing our example has the following shape.
116120``` wat
117121(module $generator
118122 (type $ft (func))
119- ;; Types of continuations used by the generator:
123+ ;; Type of continuations used by the generator:
120124 ;; No need for param or result types: No data passed back to the
121125 ;; generator when resuming it, and $generator function has no return
122126 ;; values.
123127 (type $ct (cont $ft))
124128
129+ (func $print (import "spectest" "print_i32") (param i32))
130+
125131 ;; 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.
132+ ;; corresponds to the generated values passed to consumer ; no values passed
133+ ;; back from generator to consumer.
128134 (tag $gen (param i32))
129135
130-
131- (func $print (import "spectest" "print_i32") (param i32))
132-
133136 ;; Simple generator yielding values from 100 down to 1
134137 (func $generator ...)
135138 (elem declare func $generator)
@@ -147,16 +150,16 @@ manipulate suspended continuations of type `(ref $ct)`.
147150The generator is defined as follows.
148151
149152``` wat
150- ;; Simple generator yielding values from 100 down to 1
153+ ;; Simple generator yielding values from 100 down to 1.
151154(func $generator
152155 (local $i i32)
153156 (local.set $i (i32.const 100))
154- (loop $l
155- ;; Suspend execution, pass current value of $i to consumer
157+ (loop $loop
158+ ;; Suspend execution, pass current value of $i to consumer.
156159 (suspend $gen (local.get $i))
157- ;; Decrement $i and exit loop once $i reaches 0
160+ ;; Decrement $i and exit loop once $i reaches 0.
158161 (local.tee $i (i32.sub (local.get $i) (i32.const 1)))
159- (br_if $l )
162+ (br_if $loop )
160163 )
161164)
162165```
@@ -178,9 +181,9 @@ The consumer is defined as follows.
178181
179182 (loop $loop
180183 (block $on_gen (result i32 (ref $ct))
181- ;; Resume continuation $c
184+ ;; Resume continuation $c.
182185 (resume $ct (on $gen $on_gen) (local.get $c))
183- ;; $generator returned: no more data
186+ ;; $generator returned: no more data.
184187 (return)
185188 )
186189 ;; Generator suspended, stack now contains [i32 (ref $ct)]
@@ -257,7 +260,7 @@ The full definition of this module can be found
257260
258261### Task scheduling
259262
260- The second example demonstrates how to implement task scheduling with
263+ Our second example demonstrates how to implement task scheduling with
261264the stack-switching instructions. Specifically, suppose we want to
262265schedule a number of tasks, represented by functions ` $task_0 ` to
263266` $task_n ` , to be executed concurrently. Scheduling is cooperative,
@@ -276,12 +279,10 @@ This approach is illustrated by the following skeleton code.
276279
277280``` wat
278281(module $scheduler1
279-
280282 (type $ft (func))
281283 ;; Continuation type of all tasks
282284 (type $ct (cont $ft))
283285
284-
285286 ;; Tag used to yield execution in one task and resume another one.
286287 (tag $yield)
287288
@@ -442,16 +443,16 @@ continuation references. The task that we switched to is now
442443responsible for enqueuing the previous continuation (i.e., the one
443444received as a payload) in the task list.
444445
445- As a minor complication, we need to encode the fact that the
446- continuation switched to receives the current one as an argument in
447- the type of the continuations handled by all scheduling logic. This
448- means the type ` $ct ` must be recursive: a continuation of this type
446+ As a minor complication, we must encode the fact that the continuation
447+ switched to receives the current continuation as an argument in the
448+ type of the continuations handled by all scheduling logic. This means
449+ that the type ` $ct ` must be recursive: a continuation of this type
449450takes a value of type ` (ref null $ct) ` as a parameter. In order to
450451give the same type to continuations that have yielded execution (those
451452created by ` switch ` ) and those continuations that correspond to
452453beginning the execution of a ` $task_i ` function (those created by
453454` cont.new ` ), we add a ` (ref null $ct) ` parameter to all of the
454- ` $task_i ` functions. Finally, observe that the event loop passes a
455+ ` $task_i ` functions. Finally, observe that the event loop passes a
455456null continuation to any continuation it resumes, indicating to the
456457resumed continuation that there is no previous continuation to enqueue
457458in the task list.
@@ -682,6 +683,180 @@ In order to ensure that continuations are one-shot, `resume`,
682683suspended continuation such that any subsequent use of the same
683684suspended continuation will result in a trap.
684685
686+ ## Further examples
687+
688+ We now illustrate the use of tags with result values and the
689+ instructions ` cont.bind ` and ` resume.throw ` , by adapting and extending
690+ the examples of [ Section
691+ 3] ( #introduction-to-continuation-based-stack-switching ) .
692+
693+ ### Extending the generator
694+
695+ The ` $generator ` function in [ Section 3] ( #generators )
696+ produces the values 100 down to 1. It uses the tag ` $gen ` , defined as
697+ ` (tag $gen (param i32)) ` , to send values to the ` $producer ` function.
698+
699+ We now adapt the producer to indicate to the generator when to reset
700+ (i.e., start counting down from 100 again). The producer is adapted to
701+ pass a boolean flag to the generator when resuming a continuation.
702+ Correspondingly, the ` $gen ` tag is adapted to include an ` i32 ` result
703+ type for the flag:
704+
705+ ``` wat
706+ (tag $gen (param i32) (result i32))
707+ ```
708+
709+ In the generator, the instruction ` (suspend $gen) ` now has type `[ i32]
710+ -> [ i32] `: the parameter type represents the generated value (as in
711+ the original version of the example) and the result type represents
712+ the flag obtained back from the producer. We adapt the generator to
713+ behave as follows, choosing between resetting or decrementing ` $i ` :
714+
715+ ``` wat
716+ (func $generator
717+ (local $i i32)
718+ (local.set $i (i32.const 100))
719+ (loop $loop
720+ ;; Suspend execution, pass current value of $i to consumer
721+ (suspend $gen (local.get $i))
722+ ;; We now have the flag on the stack given to us by the consumer, telling
723+ ;; us whether to reset the generator or not.
724+ (if (result i32)
725+ (then (i32.const 100))
726+ (else (i32.sub (local.get $i) (i32.const 1)))
727+ )
728+ (local.tee $i)
729+ (br_if $loop)
730+ )
731+ )
732+ ```
733+
734+ In the producer, we add logic to select the value of the flag, and
735+ pass it to the generator continuation on ` resume ` . However, this poses
736+ a challenge: the continuation created with `(cont.new $ct0 (ref.func
737+ $generator)))` has the same type as before: a continuation type with
738+ no parameter or return types. In contrast, the type of the
739+ continuation received in a handler block for tag ` $gen ` expects an
740+ ` i32 ` due to the result type we added to ` $gen ` . This means that the
741+ producer must now manipulate two different continuation types:
742+
743+ ``` wat
744+ (type $ft0 (func))
745+ (type $ft1 (func (param i32)))
746+ ;; Types of continuations used by the generator:
747+ ;; No param or result types for $ct0: $generator function has no
748+ ;; parameters or return values.
749+ (type $ct0 (cont $ft0))
750+ ;; One param of type i32 for $ct1: An i32 is passed back to the
751+ ;; generator when resuming it, and $generator function has no return
752+ ;; values.
753+ (type $ct1 (cont $ft1))
754+ ```
755+
756+ In order to avoid making the producer function unnecessarily
757+ complicated, we ensure that there is a single local variable that
758+ contains the next continuation to resume; its type will be `(ref
759+ $ct0)` . We can then use ` cont.bind` to turn the continuations received
760+ in the handler block from type ` (ref $ct1) ` into ` (ref $ct0) ` by
761+ binding the value of the flag to be passed.
762+
763+ The overall function is then defined as follows:
764+
765+ ``` wat
766+ (func $consumer (export "consumer")
767+ ;; The continuation of the generator.
768+ (local $c0 (ref $ct0))
769+ ;; For temporarily storing the continuation received in handler.
770+ (local $c1 (ref $ct1))
771+ (local $i i32)
772+ ;; Create continuation executing function $generator.
773+ ;; Execution only starts when resumed for the first time.
774+ (local.set $c0 (cont.new $ct0 (ref.func $generator)))
775+ ;; Just counts how many values we have received so far.
776+ (local.set $i (i32.const 1))
777+
778+ (loop $loop
779+ (block $on_gen (result i32 (ref $ct1))
780+ ;; Resume continuation $c0
781+ (resume $ct0 (on $gen $on_gen) (local.get $c0))
782+ ;; $generator returned: no more data
783+ (return)
784+ )
785+ ;; Generator suspended, stack now contains [i32 (ref $ct0)]
786+ ;; Save continuation to resume it in next iteration
787+ (local.set $c1)
788+ ;; Stack now contains the i32 value yielded by $generator
789+ (call $print)
790+
791+ ;; Calculate flag to be passed back to generator:
792+ ;; Reset after the 42nd iteration
793+ (i32.eq (local.get $i) (i32.const 42))
794+ ;; Create continuation of type (ref $ct0) by binding flag value.
795+ (cont.bind $ct1 $ct0 (local.get $c1))
796+ (local.set $c0)
797+
798+ (local.tee $i (i32.add (local.get $i) (i32.const 1)))
799+ (br_if $loop)
800+ )
801+ )
802+ ```
803+
804+ Here, we set the flag for resetting the generator exactly once (after
805+ it has returned 42 values).
806+
807+ The full version of the extended generator example can be found
808+ [ here] ( examples/generator-extended.wast ) .
809+
810+ ### Canceling tasks
811+
812+ The task scheduling examples from [ Section 3] ( #task-scheduling ) yield
813+ either by suspending to the scheduler running in the parent
814+ (asymmetric variant) or by calling a scheduling function that uses
815+ ` switch ` (symmetric variant).
816+
817+ Suppose we wish to adapt our schedulers to impose a limit on the
818+ number of tasks that can exist at the same time. A simple way to
819+ enforce the limit is to cancel the task at the head of the queue
820+ whenever an attempt is made to add a continuation to a full queue. We
821+ can implement this behaviour with a small modification to our previous
822+ schedulers. Instead of adding tasks to be scheduled directly to a
823+ queue, we call the following function.
824+
825+ ``` wat
826+ (func $schedule_task (param $c (ref null $ct))
827+ ;; If the task queue is too long, cancel a task in the queue
828+ (if (i32.ge_s (call $task_queue-count) (global.get $concurrent_task_limit))
829+ (then
830+ (block $exc_handler
831+ (try_table (catch $abort $exc_handler)
832+ (resume_throw $ct $abort (call $task_dequeue))
833+ )
834+ )
835+ )
836+ )
837+ (call $task_enqueue (local.get $c))
838+ )
839+ ```
840+
841+ The ` $schedule_task ` function checks if the current number of elements
842+ in the queue has already reached the limit. If so, the function takes
843+ an existing continuation from the queue and calls ` resume_throw ` on
844+ it.
845+
846+ The ` resume_throw ` instruction is annotated with a newly defined tag,
847+ ` $abort ` . This tag denotes an exception that will be raised at the
848+ suspension point of the continuation. We then wrap the ` resume_throw `
849+ instruction in a ` try_table ` , which installs an exception handler for
850+ ` $abort ` . This exception handler simply swallows the exception, which
851+ means that the exception raised at the suspension point cannot escape
852+ the ` $schedule_task ` function. The old continuation is deallocated and
853+ the function proceeds to enqueue the new continuation.
854+
855+ The full version of the symmetric scheduling example using the
856+ ` $schedule_task ` function can be found
857+ [ here] ( examples/scheduler2-throw.wast ) . The changes to the asymmetric
858+ scheduling example are analogous.
859+
685860## Design considerations
686861
687862In this section we discuss some key design considerations.
0 commit comments