blob: fc48d01ca2fb808d199cb448158b6dccdcdb725e [file] [log] [blame] [edit]
;; Unhandled tags & guards
(module
(tag $exn)
(tag $e1)
(tag $e2)
(type $f1 (func))
(type $k1 (cont $f1))
(func $f1 (export "unhandled-1")
(suspend $e1)
)
(func (export "unhandled-2")
(resume $k1 (cont.new $k1 (ref.func $f1)))
)
(func (export "unhandled-3")
(block $h (result (ref $k1))
(resume $k1 (on $e2 $h) (cont.new $k1 (ref.func $f1)))
(unreachable)
)
(drop)
)
(func (export "handled")
(block $h (result (ref $k1))
(resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1)))
(unreachable)
)
(drop)
)
(elem declare func $f2)
(func $f2
(throw $exn)
)
(func (export "uncaught-1")
(block $h (result (ref $k1))
(resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f2)))
(unreachable)
)
(drop)
)
(func (export "uncaught-2")
(block $h (result (ref $k1))
(resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f1)))
(unreachable)
)
(resume_throw $k1 $exn)
)
(elem declare func $f3)
(func $f3
(call $f4)
)
(func $f4
(suspend $e1)
)
(func (export "uncaught-3")
(block $h (result (ref $k1))
(resume $k1 (on $e1 $h) (cont.new $k1 (ref.func $f3)))
(unreachable)
)
(resume_throw $k1 $exn)
)
(elem declare func $r0 $r1)
(func $r0)
(func $r1 (suspend $e1) (suspend $e1))
(func $nl1 (param $k (ref $k1))
(resume $k1 (local.get $k))
(resume $k1 (local.get $k))
)
(func $nl2 (param $k (ref $k1))
(block $h (result (ref $k1))
(resume $k1 (on $e1 $h) (local.get $k))
(unreachable)
)
(resume $k1 (local.get $k))
(unreachable)
)
(func $nl3 (param $k (ref $k1))
(local $k' (ref null $k1))
(block $h1 (result (ref $k1))
(resume $k1 (on $e1 $h1) (local.get $k))
(unreachable)
)
(local.set $k')
(block $h2 (result (ref $k1))
(resume $k1 (on $e1 $h2) (local.get $k'))
(unreachable)
)
(resume $k1 (local.get $k'))
(unreachable)
)
(func $nl4 (param $k (ref $k1))
(drop (cont.bind $k1 $k1 (local.get $k)))
(resume $k1 (local.get $k))
)
(func (export "non-linear-1")
(call $nl1 (cont.new $k1 (ref.func $r0)))
)
(func (export "non-linear-2")
(call $nl2 (cont.new $k1 (ref.func $r1)))
)
(func (export "non-linear-3")
(call $nl3 (cont.new $k1 (ref.func $r1)))
)
(func (export "non-linear-4")
(call $nl4 (cont.new $k1 (ref.func $r1)))
)
(func (export "null-resume")
(resume $k1
(ref.null $k1)
)
)
(func (export "null-new") (result (ref null $k1))
(cont.new $k1
(ref.null $f1)
)
)
)
(assert_suspension (invoke "unhandled-1") "unhandled")
(assert_suspension (invoke "unhandled-2") "unhandled")
(assert_suspension (invoke "unhandled-3") "unhandled")
(assert_return (invoke "handled"))
(assert_exception (invoke "uncaught-1"))
(assert_exception (invoke "uncaught-2"))
(assert_exception (invoke "uncaught-3"))
(assert_trap (invoke "non-linear-1") "continuation already consumed")
(assert_trap (invoke "non-linear-2") "continuation already consumed")
(assert_trap (invoke "non-linear-3") "continuation already consumed")
(assert_trap (invoke "non-linear-4") "continuation already consumed")
(assert_trap (invoke "null-resume") "null continuation reference")
(assert_trap (invoke "null-new") "null function reference")
(assert_invalid
(module
(type $ft (func))
(func
(cont.new $ft (ref.null $ft))
(drop)))
"non-continuation type 0")
(assert_invalid
(module
(type $ft (func))
(type $ct (cont $ft))
(func
(resume $ft (ref.null $ct))
(unreachable)))
"non-continuation type 0")
(assert_invalid
(module
(type $ft (func))
(type $ct (cont $ft))
(tag $exn)
(func
(resume_throw $ft $exn (ref.null $ct))
(unreachable)))
"non-continuation type 0")
(assert_invalid
(module
(type $ft (func))
(type $ct (cont $ft))
(func
(cont.bind $ft $ct (ref.null $ct))
(unreachable)))
"non-continuation type 0")
(assert_invalid
(module
(type $ft (func))
(type $ct (cont $ft))
(func
(cont.bind $ct $ft (ref.null $ct))
(unreachable)))
"non-continuation type 0")
(assert_invalid
(module
(type $ft (func))
(type $ct (cont $ft))
(tag $foo)
(func
(block $on_foo (result (ref $ft))
(resume $ct (on $foo $on_foo) (ref.null $ct))
(unreachable)
)
(drop)))
"non-continuation type 0")
(assert_invalid
(module
(type $ft (func))
(type $ct (cont $ft))
(tag $foo)
(func
(block $on_foo (result (ref $ct) (ref $ft))
(resume $ct (on $foo $on_foo) (ref.null $ct))
(unreachable)
)
(drop)
(drop)))
"non-continuation type 0")
(assert_invalid
(module
(type $ct (cont $ct)))
"non-function type 0")
(assert_invalid
(module
(rec
(type $s0 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1))))
(type $s1 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1))))
)
(type $ct (cont $s0)))
"non-function type 0")
(module
(rec
(type $f1 (func (param (ref $f2))))
(type $f2 (func (param (ref $f1))))
)
(type $c1 (cont $f1))
(type $c2 (cont $f2))
)
(assert_invalid
(module
(rec
(type $fA (func))
(type $fB (func))
(type $cont (cont $fA))
)
(elem declare func $b)
(func $a
(drop
(cont.new $cont ;; expects a ref of $fA, not $fB
(ref.func $b)
)
)
)
(func $b (type $fB)
)
)
"type mismatch")
;; Simple state example
(module $state
(tag $get (result i32))
(tag $set (param i32) (result i32))
(type $f (func (param i32) (result i32)))
(type $k (cont $f))
(func $runner (param $s i32) (param $k (ref $k)) (result i32)
(loop $loop
(block $on_get (result (ref $k))
(block $on_set (result i32 (ref $k))
(resume $k (on $get $on_get) (on $set $on_set)
(local.get $s) (local.get $k)
)
(return)
)
;; on set
(local.set $k)
(local.set $s)
(br $loop)
)
;; on get
(local.set $k)
(br $loop)
)
(unreachable)
)
(func $f (param i32) (result i32)
(drop (suspend $set (i32.const 7)))
(i32.add
(suspend $get)
(i32.mul
(i32.const 2)
(i32.add
(suspend $set (i32.const 3))
(suspend $get)
)
)
)
)
(elem declare func $f)
(func (export "run") (result i32)
(call $runner (i32.const 0) (cont.new $k (ref.func $f)))
)
)
(assert_return (invoke "run") (i32.const 19))
;; Simple generator example
(module $generator
(type $gen (func (param i64)))
(type $geny (func (param i32)))
(type $cont0 (cont $gen))
(type $cont (cont $geny))
(tag $yield (param i64) (result i32))
;; Hook for logging purposes
(global $hook (export "hook") (mut (ref $gen)) (ref.func $dummy))
(func $dummy (param i64))
(func $gen (export "start") (param $i i64)
(loop $l
(br_if 1 (suspend $yield (local.get $i)))
(call_ref $gen (local.get $i) (global.get $hook))
(local.set $i (i64.add (local.get $i) (i64.const 1)))
(br $l)
)
)
(elem declare func $gen)
(func (export "sum") (param $i i64) (param $j i64) (result i64)
(local $sum i64)
(local $n i64)
(local $k (ref null $cont))
(local.get $i)
(cont.new $cont0 (ref.func $gen))
(block $on_first_yield (param i64 (ref $cont0)) (result i64 (ref $cont))
(resume $cont0 (on $yield $on_first_yield))
(unreachable)
)
(loop $on_yield (param i64) (param (ref $cont))
(local.set $k)
(local.set $n)
(local.set $sum (i64.add (local.get $sum) (local.get $n)))
(i64.eq (local.get $n) (local.get $j))
(local.get $k)
(resume $cont (on $yield $on_yield))
)
(return (local.get $sum))
)
)
(register "generator")
(assert_return (invoke "sum" (i64.const 0) (i64.const 0)) (i64.const 0))
(assert_return (invoke "sum" (i64.const 2) (i64.const 2)) (i64.const 2))
(assert_return (invoke "sum" (i64.const 0) (i64.const 3)) (i64.const 6))
(assert_return (invoke "sum" (i64.const 1) (i64.const 10)) (i64.const 55))
(assert_return (invoke "sum" (i64.const 100) (i64.const 2000)) (i64.const 1_996_050))
;; Simple scheduler example
(module $scheduler
(type $proc (func))
(type $cont (cont $proc))
(tag $yield (export "yield"))
(tag $spawn (export "spawn") (param (ref $cont)))
;; Table as simple queue (keeping it simple, no ring buffer)
(table $queue 0 (ref null $cont))
(global $qdelta i32 (i32.const 10))
(global $qback (mut i32) (i32.const 0))
(global $qfront (mut i32) (i32.const 0))
(func $queue-empty (result i32)
(i32.eq (global.get $qfront) (global.get $qback))
)
(func $dequeue (result (ref null $cont))
(local $i i32)
(if (call $queue-empty)
(then (return (ref.null $cont)))
)
(local.set $i (global.get $qfront))
(global.set $qfront (i32.add (local.get $i) (i32.const 1)))
(table.get $queue (local.get $i))
)
(func $enqueue (param $k (ref $cont))
;; Check if queue is full
(if (i32.eq (global.get $qback) (table.size $queue))
(then
;; Check if there is enough space in the front to compact
(if (i32.lt_u (global.get $qfront) (global.get $qdelta))
(then
;; Space is below threshold, grow table instead
(drop (table.grow $queue (ref.null $cont) (global.get $qdelta)))
)
(else
;; Enough space, move entries up to head of table
(global.set $qback (i32.sub (global.get $qback) (global.get $qfront)))
(table.copy $queue $queue
(i32.const 0) ;; dest = new front = 0
(global.get $qfront) ;; src = old front
(global.get $qback) ;; len = new back = old back - old front
)
(table.fill $queue ;; null out old entries to avoid leaks
(global.get $qback) ;; start = new back
(ref.null $cont) ;; init value
(global.get $qfront) ;; len = old front = old front - new front
)
(global.set $qfront (i32.const 0))
)
)
)
)
(table.set $queue (global.get $qback) (local.get $k))
(global.set $qback (i32.add (global.get $qback) (i32.const 1)))
)
(func $scheduler (export "scheduler") (param $main (ref $cont))
(call $enqueue (local.get $main))
(loop $l
(if (call $queue-empty) (then (return)))
(block $on_yield (result (ref $cont))
(block $on_spawn (result (ref $cont) (ref $cont))
(resume $cont (on $yield $on_yield) (on $spawn $on_spawn)
(call $dequeue)
)
(br $l) ;; thread terminated
)
;; on $spawn, proc and cont on stack
(call $enqueue) ;; continuation of old thread
(call $enqueue) ;; new thread
(br $l)
)
;; on $yield, cont on stack
(call $enqueue)
(br $l)
)
)
)
(register "scheduler")
(module
(type $proc (func))
(type $pproc (func (param i32))) ;; parameterised proc
(type $cont (cont $proc))
(type $pcont (cont $pproc)) ;; parameterised continuation proc
(tag $yield (import "scheduler" "yield"))
(tag $spawn (import "scheduler" "spawn") (param (ref $cont)))
(func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont)))
(func $log (import "spectest" "print_i32") (param i32))
(global $width (mut i32) (i32.const 0))
(global $depth (mut i32) (i32.const 0))
(elem declare func $main $thread1 $thread2 $thread3)
(func $main
(call $log (i32.const 0))
(suspend $spawn (cont.new $cont (ref.func $thread1)))
(call $log (i32.const 1))
(suspend $spawn (cont.bind $pcont $cont (global.get $depth) (cont.new $pcont (ref.func $thread2))))
(call $log (i32.const 2))
(suspend $spawn (cont.new $cont (ref.func $thread3)))
(call $log (i32.const 3))
)
(func $thread1
(call $log (i32.const 10))
(suspend $yield)
(call $log (i32.const 11))
(suspend $yield)
(call $log (i32.const 12))
(suspend $yield)
(call $log (i32.const 13))
)
(func $thread2 (param $d i32)
(local $w i32)
(local.set $w (global.get $width))
(call $log (i32.const 20))
(br_if 0 (i32.eqz (local.get $d)))
(call $log (i32.const 21))
(loop $l
(if (local.get $w)
(then
(call $log (i32.const 22))
(suspend $yield)
(call $log (i32.const 23))
(suspend $spawn
(cont.bind $pcont $cont
(i32.sub (local.get $d) (i32.const 1))
(cont.new $pcont (ref.func $thread2))
)
)
(call $log (i32.const 24))
(local.set $w (i32.sub (local.get $w) (i32.const 1)))
(br $l)
)
)
)
(call $log (i32.const 25))
)
(func $thread3
(call $log (i32.const 30))
(suspend $yield)
(call $log (i32.const 31))
(suspend $yield)
(call $log (i32.const 32))
)
(func (export "run") (param $width i32) (param $depth i32)
(global.set $depth (local.get $depth))
(global.set $width (local.get $width))
(call $log (i32.const -1))
(call $scheduler (cont.new $cont (ref.func $main)))
(call $log (i32.const -2))
)
)
(assert_return (invoke "run" (i32.const 0) (i32.const 0)))
(assert_return (invoke "run" (i32.const 0) (i32.const 1)))
(assert_return (invoke "run" (i32.const 1) (i32.const 0)))
(assert_return (invoke "run" (i32.const 1) (i32.const 1)))
(assert_return (invoke "run" (i32.const 3) (i32.const 4)))
;; Nested example: generator in a thread
(module $concurrent-generator
(func $log (import "spectest" "print_i64") (param i64))
(tag $syield (import "scheduler" "yield"))
(tag $spawn (import "scheduler" "spawn") (param (ref $cont)))
(func $scheduler (import "scheduler" "scheduler") (param $main (ref $cont)))
(type $ghook (func (param i64)))
(func $gsum (import "generator" "sum") (param i64 i64) (result i64))
(global $ghook (import "generator" "hook") (mut (ref $ghook)))
(global $result (mut i64) (i64.const 0))
(global $done (mut i32) (i32.const 0))
(elem declare func $main $bg-thread $syield)
(func $syield (param $i i64)
(call $log (local.get $i))
(suspend $syield)
)
(func $bg-thread
(call $log (i64.const -10))
(loop $l
(call $log (i64.const -11))
(suspend $syield)
(br_if $l (i32.eqz (global.get $done)))
)
(call $log (i64.const -12))
)
(func $main (param $i i64) (param $j i64)
(suspend $spawn (cont.new $cont (ref.func $bg-thread)))
(global.set $ghook (ref.func $syield))
(global.set $result (call $gsum (local.get $i) (local.get $j)))
(global.set $done (i32.const 1))
)
(type $proc (func))
(type $pproc (func (param i64 i64)))
(type $cont (cont $proc))
(type $pcont (cont $pproc))
(func (export "sum") (param $i i64) (param $j i64) (result i64)
(call $log (i64.const -1))
(call $scheduler
(cont.bind $pcont $cont (local.get $i) (local.get $j) (cont.new $pcont (ref.func $main)))
)
(call $log (i64.const -2))
(global.get $result)
)
)
(assert_return (invoke "sum" (i64.const 10) (i64.const 20)) (i64.const 165))
;; Subtyping
(module
(type $ft1 (func (param i32)))
(type $ct1 (sub (cont $ft1)))
(type $ft0 (func))
(type $ct0 (sub (cont $ft0)))
(func $test (param $x (ref $ct1))
(i32.const 123)
(local.get $x)
(cont.bind $ct1 $ct0)
(drop)
)
)
(module
(type $f1 (sub (func (result anyref))))
(type $f2 (sub $f1 (func (result eqref))))
(type $c1 (sub (cont $f1)))
(type $c2 (sub $c1 (cont $f2)))
)
;; Globals
(module
(type $ft (func))
(type $ct (cont $ft))
(global $k (mut (ref null $ct)) (ref.null $ct))
(global $g (ref null $ct) (ref.null $ct))
(func $f)
(elem declare func $f)
(func (export "set-global")
(global.set $k (cont.new $ct (ref.func $f))))
)
(assert_return (invoke "set-global"))
(assert_invalid
(module
(rec
(type $ft (func (param (ref null $ct))))
(type $ct (cont $ft)))
(type $ft2 (func))
(type $ct2 (cont $ft2))
(tag $swap)
(func $f (type $ft)
(switch $ct $swap (cont.new $ct2 (ref.null $ft2)))
(drop)))
"type mismatch")
(module
(rec
(type $ft (func (param (ref null $ct))))
(type $ct (cont $ft)))
(tag $t)
(func
(cont.new $ct (ref.null $ft))
(unreachable))
(func
(cont.bind $ct $ct (ref.null $ct))
(unreachable))
(func
(resume $ct (ref.null $ct) (ref.null $ct))
(unreachable))
(func
(resume_throw $ct $t (ref.null $ct))
(unreachable))
(func
(switch $ct $t (ref.null $ct))
(unreachable))
)
(module $co2
(type $task (func (result i32))) ;; type alias task = [] -> []
(type $ct (cont $task)) ;; type alias ct = $task
(tag $pause (export "pause")) ;; pause : [] -> []
(tag $cancel (export "cancel")) ;; cancel : [] -> []
;; run : [(ref $task) (ref $task)] -> []
;; implements a 'seesaw' (c.f. Ganz et al. (ICFP@99))
(func $run (export "seesaw") (param $up (ref $ct)) (param $down (ref $ct)) (result i32)
(local $result i32)
;; run $up
(loop $run_next (result i32)
(block $on_pause (result (ref $ct))
(resume $ct (on $pause $on_pause)
(local.get $up))
;; $up finished, store its result
(local.set $result)
;; next cancel $down
(block $on_cancel
(try_table (catch $cancel $on_cancel)
;; inject the cancel exception into $down
(resume_throw $ct $cancel (local.get $down))
(drop) ;; drop the return value if it handled $cancel
;; itself and returned normally...
)
) ;; ... otherwise catch $cancel and return $up's result.
(return (local.get $result))
) ;; on_pause clause, stack type: [(cont $ct)]
(local.set $up)
;; swap $up and $down
(local.get $down)
(local.set $down (local.get $up))
(local.set $up)
(br $run_next)
)
)
)
(register "co2")
(module $client
(type $task-0 (func (param i32) (result i32)))
(type $ct-0 (cont $task-0))
(type $task (func (result i32)))
(type $ct (cont $task))
(func $seesaw (import "co2" "seesaw") (param (ref $ct)) (param (ref $ct)) (result i32))
(func $print-i32 (import "spectest" "print_i32") (param i32))
(tag $pause (import "co2" "pause"))
(func $even (param $niter i32) (result i32)
(local $next i32) ;; zero initialised.
(local $i i32)
(loop $print-next
(call $print-i32 (local.get $next))
(suspend $pause)
(local.set $next (i32.add (local.get $next) (i32.const 2)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br_if $print-next (i32.lt_u (local.get $i) (local.get $niter)))
)
(local.get $next)
)
(func $odd (param $niter i32) (result i32)
(local $next i32) ;; zero initialised.
(local $i i32)
(local.set $next (i32.const 1))
(loop $print-next
(call $print-i32 (local.get $next))
(suspend $pause)
(local.set $next (i32.add (local.get $next) (i32.const 2)))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br_if $print-next (i32.lt_u (local.get $i) (local.get $niter)))
)
(local.get $next)
)
(func (export "main") (result i32)
(call $seesaw
(cont.bind $ct-0 $ct
(i32.const 5) (cont.new $ct-0 (ref.func $even)))
(cont.bind $ct-0 $ct
(i32.const 5) (cont.new $ct-0 (ref.func $odd)))))
(elem declare func $even $odd)
)
(assert_return (invoke "main") (i32.const 10))
;; Syntax: check unfolded forms
(module
(type $ft (func))
(type $ct (cont $ft))
(rec
(type $ft2 (func (param (ref null $ct2))))
(type $ct2 (cont $ft2)))
(tag $yield (param i32))
(tag $swap)
;; Check cont.new
(func (result (ref $ct))
ref.null $ft
block (param (ref null $ft)) (result (ref $ct))
cont.new $ct
end
)
;; Check cont.bind
(func (param (ref $ct)) (result (ref $ct))
local.get 0
block (param (ref $ct)) (result (ref $ct))
cont.bind $ct $ct
end
)
;; Check suspend
(func
block
suspend $swap
end
)
;; Check resume
(func (param $k (ref $ct)) (result i32)
(local.get $k)
block $on_yield (param (ref $ct)) (result i32 (ref $ct))
resume $ct (on $yield $on_yield)
i32.const 42
return
end
local.set $k
)
;; Check resume_throw
(func (param $k (ref $ct)) (result i32)
block $on_yield (result i32 (ref $ct))
i32.const 42
local.get $k
resume_throw $ct $yield
i32.const 42
return
end
local.set $k
)
;; Check switch
(func (param $k (ref $ct2))
local.get $k
block (param (ref $ct2)) (result (ref null $ct2))
switch $ct2 $swap
end
drop
)
)
;; Syntax: check instructions in tail position in unfolded form
(module
(type $ft (func))
(type $ct (cont $ft))
(rec
(type $ft2 (func (param (ref null $ct2))))
(type $ct2 (cont $ft2)))
(tag $yield (param i32))
(tag $swap)
;; Check cont.new
(func (result (ref $ct))
ref.null $ft
cont.new $ct
)
;; Check cont.bind
(func (param (ref $ct)) (result (ref $ct))
local.get 0
cont.bind $ct $ct
)
;; Check resume
(func (;2;) (param $k (ref $ct))
local.get $k
resume $ct
)
;; Check resume_throw
(func (param $k (ref $ct))
i32.const 42
local.get $k
resume_throw $ct $yield
)
;; Check switch
(func (param $k (ref $ct2)) (result (ref null $ct2))
local.get $k
switch $ct2 $swap
)
;; Check suspend
(func
suspend $swap
)
)
(module
(type $ft0 (func))
(type $ct0 (cont $ft0))
(type $ft1 (func (param (ref $ct0))))
(type $ct1 (cont $ft1))
(tag $t)
(func $f
(cont.new $ct1 (ref.func $g))
(switch $ct1 $t)
)
(elem declare func $f)
(func $g (param (ref $ct0)))
(elem declare func $g)
(func $entry
(cont.new $ct0 (ref.func $f))
(resume $ct0 (on $t switch))
)
)
(assert_invalid
(module
(rec
(type $ft (func (param (ref $ct))))
(type $ct (cont $ft)))
(tag $t (param i32))
(func (param $k (ref $ct))
(switch $ct $t)))
"type mismatch in switch tag")