Skip to content

Commit 3156e4a

Browse files
committed
Add some wast tests for async
1 parent 935c478 commit 3156e4a

18 files changed

Lines changed: 1578 additions & 0 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO: async calls async which then does a sync thing
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
;; This test contains 3 components, $AsyncInner, $SyncMiddle and $AsyncOuter,
2+
;; where there are two instances of $SyncMiddle that import a single instance
3+
;; of $AsyncInner, and $AsyncOuter imports all 3 preceding instances.
4+
;;
5+
;; $AsyncOuter.run asynchronously calls $SyncMiddle.sync-func twice concurrently
6+
;; in each instance (4 total calls), hitting the synchronous backpressure case
7+
;; in 2 of the 4 calls.
8+
;;
9+
;; $SyncMiddle.sync-func makes a blocking call to $AsyncInner.blocking-call
10+
;; which is used to emulate a host call that blocks until $AsyncOuter.run
11+
;; calls $AsyncInner.unblock to unblock all the 'blocking-call' calls.
12+
(component
13+
(component $AsyncInner
14+
(core module $CoreAsyncInner
15+
(import "" "context.set" (func $context.set (param i32)))
16+
(import "" "context.get" (func $context.get (result i32)))
17+
(import "" "task.return0" (func $task.return0))
18+
(import "" "task.return1" (func $task.return1 (param i32)))
19+
20+
(memory 1)
21+
(global $blocked (mut i32) (i32.const 1))
22+
(global $counter (mut i32) (i32.const 2))
23+
24+
;; 'blocking-call' cooperatively "spin-waits" until $blocked is 0.
25+
(func $blocking-call (export "blocking-call") (result i32)
26+
(call $context.set (global.get $counter))
27+
(global.set $counter (i32.add (i32.const 1) (global.get $counter)))
28+
(i32.const 1 (; YIELD ;))
29+
)
30+
(func $blocking-call-cb (export "blocking-call-cb") (param i32 i32 i32) (result i32)
31+
(if (i32.eqz (global.get $blocked)) (then
32+
(call $task.return1 (call $context.get)) ;; TODO: check if getting this wrong traps
33+
(return (i32.const 0 (; EXIT ;)))
34+
))
35+
(i32.const 1 (; YIELD ;))
36+
)
37+
(func $unblock (export "unblock") (result i32)
38+
(global.set $blocked (i32.const 0))
39+
(call $task.return0) ;; TODO: check if getting this wrong traps
40+
(i32.const 0 (; EXIT ;))
41+
)
42+
(func $unblock-cb (export "unblock-cb") (param i32 i32 i32) (result i32)
43+
unreachable
44+
)
45+
)
46+
(canon task.return (core func $task.return0))
47+
(canon task.return (result u32) (core func $task.return1))
48+
(canon context.set i32 0 (core func $context.set))
49+
(canon context.get i32 0 (core func $context.get))
50+
(core instance $core_async_inner (instantiate $CoreAsyncInner (with "" (instance
51+
(export "task.return0" (func $task.return0))
52+
(export "task.return1" (func $task.return1))
53+
(export "context.set" (func $context.set))
54+
(export "context.get" (func $context.get))
55+
))))
56+
(func (export "blocking-call") (result u32) (canon lift
57+
(core func $core_async_inner "blocking-call")
58+
async (callback (func $core_async_inner "blocking-call-cb"))
59+
))
60+
(func (export "unblock") (canon lift
61+
(core func $core_async_inner "unblock")
62+
async (callback (func $core_async_inner "unblock-cb"))
63+
))
64+
)
65+
66+
(component $SyncMiddle
67+
(import "blocking-call" (func $blocking-call (result u32)))
68+
(core module $CoreSyncMiddle
69+
(import "" "blocking-call" (func $blocking-call (result i32)))
70+
(func $sync-func (export "sync-func") (result i32)
71+
(local $i i32)
72+
(call $blocking-call)
73+
)
74+
)
75+
(canon lower (func $blocking-call) (core func $blocking-call'))
76+
(core instance $sync_middle (instantiate $CoreSyncMiddle (with "" (instance
77+
(export "blocking-call" (func $blocking-call'))
78+
))))
79+
(func (export "sync-func") (result u32) (canon lift
80+
(core func $sync_middle "sync-func")
81+
))
82+
)
83+
84+
(component $AsyncOuter
85+
(import "unblock" (func $unblock))
86+
(import "sync-func1" (func $sync-func1 (result u32)))
87+
(import "sync-func2" (func $sync-func2 (result u32)))
88+
89+
(core module $Memory (memory (export "mem") 1))
90+
(core instance $memory (instantiate $Memory))
91+
(core module $CoreAsyncOuter
92+
(import "" "mem" (memory 1))
93+
(import "" "task.return" (func $task.return (param i32)))
94+
(import "" "subtask.drop" (func $subtask.drop (param i32)))
95+
(import "" "waitable.join" (func $waitable.join (param i32 i32)))
96+
(import "" "waitable-set.new" (func $waitable-set.new (result i32)))
97+
(import "" "waitable-set.wait" (func $waitable-set.wait (param i32 i32) (result i32)))
98+
(import "" "unblock" (func $unblock))
99+
(import "" "sync-func1" (func $sync-func1 (param i32 i32) (result i32)))
100+
(import "" "sync-func2" (func $sync-func2 (param i32 i32) (result i32)))
101+
102+
(global $ws (mut i32) (i32.const 0))
103+
(func $start (global.set $ws (call $waitable-set.new)))
104+
(start $start)
105+
106+
(global $remain (mut i32) (i32.const -1))
107+
108+
(func $run (export "run") (result i32)
109+
(local $ret i32)
110+
111+
;; call 'sync-func1' and 'sync-func2' asynchronously, both of which will block
112+
;; (on $AsyncInner.blocking-call). because 'sync-func1/2' are in different instances,
113+
;; both calls will reach the STARTED state.
114+
(local.set $ret (call $sync-func1 (i32.const 0xdeadbeef) (i32.const 8)))
115+
(if (i32.ne (i32.const 0x21 (; STARTED=1 | (subtask=2 << 4) ;)) (local.get $ret))
116+
(then unreachable))
117+
(call $waitable.join (i32.const 2) (global.get $ws))
118+
(local.set $ret (call $sync-func2 (i32.const 0xdeadbeef) (i32.const 12)))
119+
(if (i32.ne (i32.const 0x31 (; STARTED=1 | (subtask=3 << 4) ;)) (local.get $ret))
120+
(then unreachable))
121+
(call $waitable.join (i32.const 3) (global.get $ws))
122+
123+
;; now start another pair of 'sync-func1/2' calls, both of which should see auto
124+
;; backpressure and get stuck in the STARTING state.
125+
(local.set $ret (call $sync-func1 (i32.const 0xdeadbeef) (i32.const 16)))
126+
(if (i32.ne (i32.const 0x40 (; STARTING=0 | (subtask=4 << 4) ;)) (local.get $ret))
127+
(then unreachable))
128+
(call $waitable.join (i32.const 4) (global.get $ws))
129+
(local.set $ret (call $sync-func2 (i32.const 0xdeadbeef) (i32.const 20)))
130+
(if (i32.ne (i32.const 0x50 (; STARTING=0 | (subtask=5 << 4) ;)) (local.get $ret))
131+
(then unreachable))
132+
(call $waitable.join (i32.const 5) (global.get $ws))
133+
134+
;; this POLL should return that nothing is ready
135+
(i32.or (i32.const 3 (; POLL ;)) (i32.shl (global.get $ws) (i32.const 4)))
136+
)
137+
(func $run-cb (export "run-cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32)
138+
(local $ret i32)
139+
140+
;; $remain is initially -1, so confirm that POLL found nothing was ready and then
141+
;; unblock all the subtasks and set $remain to 4 to count how many to wait for.
142+
(if (i32.eq (global.get $remain) (i32.const -1)) (then
143+
(if (i32.ne (local.get $event_code) (i32.const 0 (; NONE ;)))
144+
(then unreachable))
145+
(call $unblock)
146+
(global.set $remain (i32.const 4))
147+
(return (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4))))
148+
))
149+
150+
;; confirm we only receive SUBTASK events after the first NONE event.
151+
(if (i32.ne (local.get $event_code) (i32.const 1 (; SUBTASK ;)))
152+
(then unreachable))
153+
154+
;; if we receive a SUBTASK STARTED event, it should only be for the 3rd or
155+
;; 4th subtask (at indices 4/5, resp), so keep waiting for completion
156+
(if (i32.eq (local.get $payload) (i32.const 1 (; STARTED ;))) (then
157+
(if (i32.and
158+
(i32.ne (local.get $index) (i32.const 4))
159+
(i32.ne (local.get $index) (i32.const 5)))
160+
(then unreachable))
161+
(return (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4))))
162+
))
163+
164+
;; when we receive a SUBTASK RETURNED event, check the return value is equal to the
165+
;; subtask index (which we've ensured by having $AsyncInner.$counter start at 2, the
166+
;; first subtask index. The address of the return buffer is the index*4.
167+
(if (i32.ne (local.get $payload) (i32.const 2 (; RETURNED ;)))
168+
(then unreachable))
169+
(if (i32.ne (local.get $index) (i32.load (i32.mul (local.get $index) (i32.const 4))))
170+
(then unreachable))
171+
172+
;; decrement $remain and exit if 0
173+
(call $subtask.drop (local.get $index))
174+
(global.set $remain (i32.sub (global.get $remain) (i32.const 1)))
175+
(if (i32.gt_u (global.get $remain) (i32.const 0)) (then
176+
(return (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4))))
177+
))
178+
(call $task.return (i32.const 42))
179+
(i32.const 0 (; EXIT ;))
180+
)
181+
)
182+
(canon task.return (result u32) (core func $task.return))
183+
(canon subtask.drop (core func $subtask.drop))
184+
(canon waitable.join (core func $waitable.join))
185+
(canon waitable-set.new (core func $waitable-set.new))
186+
(canon waitable-set.wait (memory $memory "mem") (core func $waitable-set.wait))
187+
(canon lower (func $unblock) (core func $unblock))
188+
(canon lower (func $sync-func1) async (memory $memory "mem") (core func $sync-func1'))
189+
(canon lower (func $sync-func2) async (memory $memory "mem") (core func $sync-func2'))
190+
(core instance $em (instantiate $CoreAsyncOuter (with "" (instance
191+
(export "mem" (memory $memory "mem"))
192+
(export "task.return" (func $task.return))
193+
(export "subtask.drop" (func $subtask.drop))
194+
(export "waitable.join" (func $waitable.join))
195+
(export "waitable-set.new" (func $waitable-set.new))
196+
(export "waitable-set.wait" (func $waitable-set.wait))
197+
(export "unblock" (func $unblock))
198+
(export "sync-func1" (func $sync-func1'))
199+
(export "sync-func2" (func $sync-func2'))
200+
))))
201+
(func (export "run") (result u32) (canon lift
202+
(core func $em "run")
203+
async (callback (func $em "run-cb"))
204+
))
205+
)
206+
207+
(instance $async_inner (instantiate $AsyncInner))
208+
(instance $sync_middle1 (instantiate $SyncMiddle
209+
(with "blocking-call" (func $async_inner "blocking-call"))
210+
))
211+
(instance $sync_middle2 (instantiate $SyncMiddle
212+
(with "blocking-call" (func $async_inner "blocking-call"))
213+
))
214+
(instance $async_outer (instantiate $AsyncOuter
215+
(with "unblock" (func $async_inner "unblock"))
216+
(with "sync-func1" (func $sync_middle1 "sync-func"))
217+
(with "sync-func2" (func $sync_middle2 "sync-func"))
218+
))
219+
(func (export "run") (alias export $async_outer "run"))
220+
)
221+
(assert_return (invoke "run") (u32.const 42))

test/concurrency/backpressure.wast

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO: if multiple pending async calls, only start 1 at a time

0 commit comments

Comments
 (0)