forked from WebAssembly/spec
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathscheduler1.wast
160 lines (138 loc) · 4.89 KB
/
scheduler1.wast
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
;; queue of threads
(module $queue
(type $ft (func))
(type $ct (cont $ft))
;; Table as simple queue (keeping it simple, no ring buffer)
(table $task_queue 0 (ref null $ct))
(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 $ct))
(local $i i32)
(if (call $queue_empty)
(then (return (ref.null $ct)))
)
(local.set $i (global.get $qfront))
(global.set $qfront (i32.add (local.get $i) (i32.const 1)))
(table.get $task_queue (local.get $i))
)
(func $enqueue (export "enqueue") (param $k (ref null $ct))
;; Check if queue is full
(if (i32.eq (global.get $qback) (table.size $task_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 $task_queue (ref.null $ct) (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 $task_queue $task_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 $task_queue ;; null out old entries to avoid leaks
(global.get $qback) ;; start = new back
(ref.null $ct) ;; init value
(global.get $qfront) ;; len = old front = old front - new front
)
(global.set $qfront (i32.const 0))
)
)
)
)
(table.set $task_queue (global.get $qback) (local.get $k))
(global.set $qback (i32.add (global.get $qback) (i32.const 1)))
)
)
(register "queue")
(module $scheduler1
(type $ft (func))
;; Continuation type of all tasks
(type $ct (cont $ft))
(func $task_enqueue (import "queue" "enqueue") (param (ref null $ct)))
(func $task_dequeue (import "queue" "dequeue") (result (ref null $ct)))
(func $task_queue-empty (import "queue" "queue-empty") (result i32))
(func $print_i32 (import "spectest" "print_i32") (param i32))
;; Tag used to yield execution in one task and resume another one.
(tag $yield)
;; Entry point, becomes parent of all tasks.
;; Also acts as scheduler when tasks yield or finish.
(func $entry (param $initial_task (ref $ft))
(local $next_task (ref null $ct))
;; initialise $task_queue with initial task
(call $task_enqueue (cont.new $ct (local.get $initial_task)))
(loop $resume_next
;; pick $next_task from queue, or return if no more tasks.
(if (call $task_queue-empty)
(then (return))
(else (local.set $next_task (call $task_dequeue)))
)
(block $on_yield (result (ref $ct))
(resume $ct (on $yield $on_yield) (local.get $next_task))
;; task finished execution
(br $resume_next)
)
;; task suspended: put continuation in queue, then loop to determine next
;; one to resume.
(call $task_enqueue)
(br $resume_next)
)
)
;; To simplify the example, all task_i functions execute this function. Each
;; task has an $id, but this is only used for printing.
;; $to_spawn represents another task that this function will add to the task
;; queue, unless the reference is null.
(func $task_impl
(param $id i32)
(param $to_spawn (ref null $ft))
(if (ref.is_null (local.get $to_spawn))
(then)
(else (call $task_enqueue (cont.new $ct (local.get $to_spawn)))))
(call $print_i32 (local.get $id))
(suspend $yield)
(call $print_i32 (local.get $id))
)
;; The actual $task_i functions simply call $task_impl, with i as the value
;; for $id, and $task_(i+1) as the task to spawn, except for $task_3, which
;; does not spawn another task.
;;
;; The observant reader may note that all $task_i functions may be seen as
;; partial applications of $task_impl.
;; Indeed, we could obtain *continuations* running each $task_i from a
;; continuation running $task_impl and cont.bind.
(func $task_3
(i32.const 3)
(ref.null $ft)
(call $task_impl)
)
(elem declare func $task_3)
(func $task_2
(i32.const 2)
(ref.func $task_3)
(call $task_impl)
)
(elem declare func $task_2)
(func $task_1
(i32.const 1)
(ref.func $task_2)
(call $task_impl)
)
(elem declare func $task_1)
(func $task_0
(i32.const 0)
(ref.func $task_1)
(call $task_impl)
)
(elem declare func $task_0)
(func (export "main")
(call $entry (ref.func $task_0))
)
)
(invoke "main")