stack-switching
* First class continuations (e.g., Scheme, react-style programming over large trees)
tag: Control tag
Exception Handling proposal で導入されたやつに result type を入れたやつ
(tag $t (param t1*) (result t2*))
param型 : 継続をsuspendするときにhandlerに渡す値
result型 : 継続がsuspendせずにreturnしたときの返り値型
code:lisp
;; Tag used to coordinate between generator and consumer: The i32 param
;; corresponds to the generated values passed; no values passed back from
;; generator to consumer.
(tag $gen (param i32))
cont 継続を表す新しい参照型 (heap type?)
継続は"特定のスタック上"(つまり今のstackとは別のstackで実行される?)での実行のスナップショット
code:lisp
(type $ft (func))
(type $ct (cont $ft))
cont.new : 関数から "suspended continuation" を作って継続の参照型を (ref $ct)
(cont.new $ct (ref.func $generator))
$generator の型は $ft でないといけない
- $ft = func [t1*] -> [t2*]
- $ct = cont $ft
resume : "handler" のもとで suspended continuation を実行
resume $ct hdl* : [t1* (ref $ct)] -> [t2*]
code:lisp
(local.set $c (cont.new $ct (ref.func $generator)))
(loop
(block $on_gen (result i32 (ref $ct))
(resume $ct (on $gen $on_gen) (local.get $c))
(return)
)
;; ...
)
ここでの suspended continuationとは $generator から作った継続 $c
hdl は "handler dispatch table"
(on $e $l) mapping the control tag $e to the label $l. Intercepting $e causes a branch to $l.
例えば (on $gen $on_gen) は継続が $gen で suspend したときに $on_gen にブランチする
なので $on_gen block のresult型は [i32, (ref $ct)] (i32 は $gen の tag で指定した param 型)
(on $e switch) allowing a direct switch with control tag $e.
???
resume_throw : 中断された継続を再開するが、注釈付きの例外をすぐにスローするように強制
suspend : continuation tag を使って "suspend" the current computation.
suspended continuation の中で実行
suspend <tagidx>
この tag $ft が C.types[$ft] ~~ func [t1*] -> [t2*] であるとき、 suspend <tagidx> は [t1*] -> [t2*]
つまり tagの param は handler に与える値の型、result は継続を再開するときに受け取る値
suspends the currently running computation and reifies it into a continuation. In terms of implementation, this can be viewed as switching from the current stack to its parent.
switch :
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.
cont.bind :
中断された継続にその引数のプレフィックスを部分的に適用して、別の中断された継続を生成します
one-shot delimited continuation
In order to ensure that continuations are one-shot, resume, resume_throw, switch, and cont.bind destructively modify the suspended continuation such that any subsequent use of the same suspended continuation will result in a trap.
hr.icon
Generator
code:lisp
(module $generator
(type $ft (func))
(type $ct (cont $ft))
;; param: suspendでhandlerに与える値
;; result: suspendせずに継続がreturnしたときの値。つまりこのresultと$ctのresult型は一致していないといけない
(tag $gen (param i32))
(func $print (import "spectest" "print_i32") (param i32))
;; Simple generator yielding values from 100 down to 1
;; $consumer はこの関数を cont.new で継続にして実行していく
(func $generator ...)
;; code section で $generator を参照するので declare が必要
(elem declare func $generator)
(func $consumer
(local $c (ref $ct))
;; Create "suspended" continuation executing function $generator.
;; Execution only starts when resumed for the first time.
(local.set $c (cont.new $ct (ref.func $generator)))
(loop $loop
(block $on_gen (result i32 (ref $ct))
;; Resume continuation $c
;; 継続の参照型 $ct の関数型は (func) なので、ここでは引数なし
;; 引数がある場合は (local.get $c) の前に引数を追加
;; where:
;; 実行が継続$cにうつる
;; $c実行中に suspend $gen で継続がsuspendされた場合、(inner_mostの)$on_gen labelに実行がうつる
(resume $ct (on $gen $on_gen) (local.get $c))
;; continuation の返り値型がstackに乗る。(func) なので何もなし
;; $generator returned: no more data
(return)
)
;; Save continuation to resume it in the next iteration
(local.set $c)
(call $print)
(br $loop)
)
)
)
code:lisp
;; Simple generator yielding values from 100 down to 1
(func $generator
(local $i i32)
(local.set $i (i32.const 100))
(loop $l
;; Suspend execution, pass current value of $i to consumer
;; $gen tag に対するinnermost handlerを探索して、i32の値を渡しつつ実行をハンドラ側に移す
;; where:
;; $e は tag の param/result型
(suspend $gen (local.get $i))
;; suspend によって作られた suspended continuation を resume したときはここから実行が再開される
;; tag の result 型はなにもないので、この継続が再開したときstackにはなにもない
;; Decrement $i and exit loop once $i reaches 0
(local.tee $i (i32.sub (local.get $i) (i32.const 1)))
(br_if $l)
)
)
suspendせずに継続が終了したときVMはその継続をどう破棄するのだろうか?
hr.icon
Task Scheduling