diff --git a/proposals/stack-switching/examples/async-await.wat b/proposals/stack-switching/examples/async-await.wat new file mode 100644 index 00000000..015c0f41 --- /dev/null +++ b/proposals/stack-switching/examples/async-await.wat @@ -0,0 +1,279 @@ +;; async-await interface +(module $async-await + (type $i-func (func (param i32))) + (type $i-cont (cont $i-func)) + + ;; We use yield and fulfill to simulate asynchronous operations. + ;; + ;; Given a suitable asynchronous I/O API, they needn't be exposed to + ;; user code. + ;; + ;; yield : [] -> [] + ;; fulfill : [i32] -> [i32] + (tag $yield (export "yield")) + (tag $fulfill (export "fulfill-tag") (param i32) (param i32)) + + ;; async : [cont ([i32] -> [])] -> [i32] + ;; await : [i32] -> [i32] + (tag $async (export "async") (param (ref $i-cont)) (result i32)) + (tag $await-tag (export "await-tag") (param i32) (result i32)) + + ;; yield : [] -> [] + ;; fulfill : [i32] -> [i32] + ;; async : [cont ([i32] -> [])] -> [i32] + ;; await : [i32] -> [i32] + + ;; queue interface + + ;; promise interface + + (type $iii-fun (func (param i32 i32 i32))) + (type $iii-cont (cont $iii-fun)) + + (elem declare func $sum) + + (func $log (param $x i32) + ) + + ;; an asynchronous function that computes i + i+1 + ... + j + ;; + ;; (instead of computing synchronously, it allows other computations + ;; to execute each time round the loop) + ;; + ;; the final result is written to the promise $p + (func $sum (param $i i32) (param $j i32) (param $p i32) + (local $a i32) + (loop $l + (call $log (local.get $i)) + (local.set $a (i32.add (local.get $a) (local.get $i))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (if (i32.le_u (local.get $i) (local.get $j)) + (then (suspend $yield) + (br $l)) + ) + ) + (suspend $fulfill (local.get $p) (local.get $a)) + ) + + ;; compute p = 1+..+3; q = 5+..+7; r = 10+...+15 asynchronously + ;; once p and q have finished computing, compute x = p*q + ;; once r has finished computing, return x+r + (func $example-run (export "example-run") + (local $p i32) + (local $q i32) + (local $r i32) + + (local $x i32) + (local $y i32) + + (call $log (i32.const -1)) + (local.set $p (suspend $async (cont.bind $iii-cont $i-cont (i32.const 1) (i32.const 3) (cont.new $iii-cont (ref.func $sum))))) + (call $log (i32.const -2)) + (local.set $q (suspend $async (cont.bind $iii-cont $i-cont (i32.const 5) (i32.const 7) (cont.new $iii-cont (ref.func $sum))))) + (call $log (i32.const -3)) + (local.set $r (suspend $async (cont.bind $iii-cont $i-cont (i32.const 10) (i32.const 15) (cont.new $iii-cont (ref.func $sum))))) + (call $log (i32.const -4)) + + (local.set $x (i32.mul (suspend $await-tag (local.get $p)) + (suspend $await-tag (local.get $q)))) + + (call $log (i32.const -5)) + + (local.set $y (i32.add (suspend $await-tag (local.get $r)) (local.get $x))) + + (call $log (i32.const -6)) + (call $log (local.get $y)) + (call $log (i32.const -7)) + ) + +;; queue of threads + (type $func (func)) + (type $cont (cont $func)) + + ;; 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 (export "queue-empty") (result i32) + (i32.eq (global.get $qfront) (global.get $qback)) + ) + + (func $dequeue (export "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 (export "enqueue") (param $k (ref null $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))) + ) + + + ;; a simplistic implementation of promises that assumes a maximum of + ;; 1000 promises and a maximum of one observer per promise + + (tag $too-many-promises) + (tag $too-many-observers) + + (global $num-promises (mut i32) (i32.const 0)) + (global $max-promises i32 (i32.const 1000)) + (table $observers 1000 (ref null $i-cont)) ;; observers waiting for promises to be fulfilled + (memory 1) ;; promise values + + ;; create and return a new promise + (func $new-promise (export "new-promise") (result i32) + (local $offset i32) + (local $p i32) + (if (i32.eq (global.get $num-promises) (global.get $max-promises)) + (then (unreachable))) + (local.set $p (global.get $num-promises)) + (local.set $offset (i32.mul (local.get $p) (i32.const 4))) + (table.set $observers (local.get $p) (ref.null $i-cont)) + (i32.store (local.get $offset) (i32.const -1)) + (global.set $num-promises (i32.add (local.get $p) (i32.const 1))) + (return (local.get $p)) + ) + + ;; check whether promise $p is fulfilled + (func $promise-fulfilled (export "promise-fulfilled") (param $p i32) (result i32) + (local $offset i32) + (local.set $offset (i32.mul (local.get $p) (i32.const 4))) + (i32.ne (i32.load (local.get $offset)) (i32.const -1)) + ) + + ;; current value of promise $p + (func $promise-read (export "promise-read") (param $p i32) (result i32) + (local $offset i32) + (local.set $offset (i32.mul (local.get $p) (i32.const 4))) + (i32.load (local.get $offset)) + ) + + ;; register an observer for when promise $p is fulfilled + (func $promise-await (export "await") (param $p i32) (param $k (ref $i-cont)) + (if (ref.is_null (table.get $observers (local.get $p))) + (then (table.set $observers (local.get $p) (local.get $k))) + (else (unreachable)) + ) + ) + + ;; fulfill promise $p with value $v + (func $fulfill-promise (export "fulfill-promise") (param $p i32) (param $v i32) (result (ref null $cont)) + (local $offset i32) + (local $k (ref null $i-cont)) + (local.set $offset (i32.mul (local.get $p) (i32.const 4))) + (i32.store (local.get $offset) (local.get $v)) + (local.set $k (table.get $observers (local.get $p))) + (if (ref.is_null (local.get $k)) + (then (return (ref.null $cont))) + ) + (return (cont.bind $i-cont $cont (local.get $v) (local.get $k))) + ) + + + + ;; async-await interface + ;; + ;; yield : [] -> [] + ;; fulfill : [i32] -> [i32] + ;; async : [cont ([i32] -> [])] -> [i32] + ;; await : [i32] -> [i32] + + (func $scheduler-run (export "scheduler-run") (param $nextk (ref null $cont)) + (local $p i32) + (local $v i32) + (local $ik (ref $i-cont)) + (local $ak (ref $i-cont)) + (local $k (ref null $cont)) + (loop $l + (if (ref.is_null (local.get $nextk)) (then (return))) + (block $on_yield (result (ref $cont)) + (block $on_fulfill (result i32 i32 (ref $cont)) + (block $on_async (result (ref $i-cont) (ref $i-cont)) + (block $on_await (result i32 (ref $i-cont)) + (resume $cont (on $yield $on_yield) + (on $fulfill $on_fulfill) + (on $async $on_async) + (on $await-tag $on_await) + (local.get $nextk) + ) + (local.set $nextk (call $dequeue)) + (br $l) ;; thread terminated + ) ;; $on_await (result i32 (ref $i-cont)) + (local.set $ik) + (local.set $p) + (if (call $promise-fulfilled (local.get $p)) + ;; if promise fulfilled then run continuation partially applied to value + (then (local.set $nextk (cont.bind $i-cont $cont (call $promise-read (local.get $p)) (local.get $ik)))) + ;; else add continuation to promise and run next continuation from the queue + (else (call $promise-await (local.get $p) (local.get $ik)) + (local.set $nextk (call $dequeue))) + ) + (br $l) + ) ;; $on_async (result (ref $i-func) (ref $i-cont)) + (local.set $ik) + (local.set $ak) + ;; create new promise + (call $new-promise) + (local.set $p) + ;; enqueue continuation partially applied to promise + (call $enqueue (cont.bind $i-cont $cont (local.get $p) (local.get $ik))) + ;; run computation partially applied to promise + (local.set $nextk (cont.bind $i-cont $cont (local.get $p) (local.get $ak))) + (br $l) + ) ;; $on_fulfill (result i32 i32 (ref $cont)) + (local.set $nextk) + (local.set $v) + (local.set $p) + (call $fulfill-promise (local.get $p) (local.get $v)) + (local.set $k) + (if (ref.is_null (local.get $k)) + (then) + (else (call $enqueue (local.get $k))) + ) + (br $l) + ) ;; $on_yield (result (ref $cont)) + (call $enqueue) ;; current thread + (local.set $nextk (call $dequeue)) ;; next thread + (br $l) + ) + ) + + (func $_start + (call $scheduler-run (cont.new $cont (ref.func $example-run))) + ) + (export "_start" (func $_start)) + )