|
| 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)) |
| 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) |
| 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 | + (call $blocking-call) |
| 72 | + ) |
| 73 | + ) |
| 74 | + (canon lower (func $blocking-call) (core func $blocking-call')) |
| 75 | + (core instance $core_sync_middle (instantiate $CoreSyncMiddle (with "" (instance |
| 76 | + (export "blocking-call" (func $blocking-call')) |
| 77 | + )))) |
| 78 | + (func (export "sync-func") (result u32) (canon lift |
| 79 | + (core func $core_sync_middle "sync-func") |
| 80 | + )) |
| 81 | + ) |
| 82 | + |
| 83 | + (component $AsyncMiddle |
| 84 | + (import "blocking-call" (func $blocking-call (result u32))) |
| 85 | + (core module $CoreSyncMiddle |
| 86 | + (import "" "task.return" (func $task.return (param i32))) |
| 87 | + (import "" "blocking-call" (func $blocking-call (result i32))) |
| 88 | + (func $sync-func (export "sync-func") (result i32) |
| 89 | + (call $task.return (call $blocking-call)) |
| 90 | + (i32.const 0 (; EXIT ;)) |
| 91 | + ) |
| 92 | + (func $sync-func-cb (export "sync-func-cb") (param i32 i32 i32) (result i32) |
| 93 | + unreachable |
| 94 | + ) |
| 95 | + ) |
| 96 | + (canon task.return (result u32) (core func $task.return)) |
| 97 | + (canon lower (func $blocking-call) (core func $blocking-call')) |
| 98 | + (core instance $core_sync_middle (instantiate $CoreSyncMiddle (with "" (instance |
| 99 | + (export "task.return" (func $task.return)) |
| 100 | + (export "blocking-call" (func $blocking-call')) |
| 101 | + )))) |
| 102 | + (func (export "sync-func") (result u32) (canon lift |
| 103 | + (core func $core_sync_middle "sync-func") |
| 104 | + async (callback (func $core_sync_middle "sync-func-cb")) |
| 105 | + )) |
| 106 | + ) |
| 107 | + |
| 108 | + (component $AsyncOuter |
| 109 | + (import "unblock" (func $unblock)) |
| 110 | + (import "sync-func1" (func $sync-func1 (result u32))) |
| 111 | + (import "sync-func2" (func $sync-func2 (result u32))) |
| 112 | + |
| 113 | + (core module $Memory (memory (export "mem") 1)) |
| 114 | + (core instance $memory (instantiate $Memory)) |
| 115 | + (core module $CoreAsyncOuter |
| 116 | + (import "" "mem" (memory 1)) |
| 117 | + (import "" "task.return" (func $task.return (param i32))) |
| 118 | + (import "" "subtask.drop" (func $subtask.drop (param i32))) |
| 119 | + (import "" "waitable.join" (func $waitable.join (param i32 i32))) |
| 120 | + (import "" "waitable-set.new" (func $waitable-set.new (result i32))) |
| 121 | + (import "" "unblock" (func $unblock)) |
| 122 | + (import "" "sync-func1" (func $sync-func1 (param i32 i32) (result i32))) |
| 123 | + (import "" "sync-func2" (func $sync-func2 (param i32 i32) (result i32))) |
| 124 | + |
| 125 | + (global $ws (mut i32) (i32.const 0)) |
| 126 | + (func $start (global.set $ws (call $waitable-set.new))) |
| 127 | + (start $start) |
| 128 | + |
| 129 | + (global $remain (mut i32) (i32.const -1)) |
| 130 | + |
| 131 | + (func $run (export "run") (result i32) |
| 132 | + (local $ret i32) |
| 133 | + |
| 134 | + ;; call 'sync-func1' and 'sync-func2' asynchronously, both of which will block |
| 135 | + ;; (on $AsyncInner.blocking-call). because 'sync-func1/2' are in different instances, |
| 136 | + ;; both calls will reach the STARTED state. |
| 137 | + (local.set $ret (call $sync-func1 (i32.const 0xdeadbeef) (i32.const 8))) |
| 138 | + (if (i32.ne (i32.const 0x21 (; STARTED=1 | (subtask=2 << 4) ;)) (local.get $ret)) |
| 139 | + (then unreachable)) |
| 140 | + (call $waitable.join (i32.const 2) (global.get $ws)) |
| 141 | + (local.set $ret (call $sync-func2 (i32.const 0xdeadbeef) (i32.const 12))) |
| 142 | + (if (i32.ne (i32.const 0x31 (; STARTED=1 | (subtask=3 << 4) ;)) (local.get $ret)) |
| 143 | + (then unreachable)) |
| 144 | + (call $waitable.join (i32.const 3) (global.get $ws)) |
| 145 | + |
| 146 | + ;; now start another pair of 'sync-func1/2' calls, both of which should see auto |
| 147 | + ;; backpressure and get stuck in the STARTING state. |
| 148 | + (local.set $ret (call $sync-func1 (i32.const 0xdeadbeef) (i32.const 16))) |
| 149 | + (if (i32.ne (i32.const 0x40 (; STARTING=0 | (subtask=4 << 4) ;)) (local.get $ret)) |
| 150 | + (then unreachable)) |
| 151 | + (call $waitable.join (i32.const 4) (global.get $ws)) |
| 152 | + (local.set $ret (call $sync-func2 (i32.const 0xdeadbeef) (i32.const 20))) |
| 153 | + (if (i32.ne (i32.const 0x50 (; STARTING=0 | (subtask=5 << 4) ;)) (local.get $ret)) |
| 154 | + (then unreachable)) |
| 155 | + (call $waitable.join (i32.const 5) (global.get $ws)) |
| 156 | + |
| 157 | + ;; this POLL should return that nothing is ready |
| 158 | + (i32.or (i32.const 3 (; POLL ;)) (i32.shl (global.get $ws) (i32.const 4))) |
| 159 | + ) |
| 160 | + (func $run-cb (export "run-cb") (param $event_code i32) (param $index i32) (param $payload i32) (result i32) |
| 161 | + (local $ret i32) |
| 162 | + |
| 163 | + ;; $remain is initially -1, so confirm that POLL found nothing was ready and then |
| 164 | + ;; unblock all the subtasks and set $remain to 4 to count how many to wait for. |
| 165 | + (if (i32.eq (global.get $remain) (i32.const -1)) (then |
| 166 | + (if (i32.ne (local.get $event_code) (i32.const 0 (; NONE ;))) |
| 167 | + (then unreachable)) |
| 168 | + (call $unblock) |
| 169 | + (global.set $remain (i32.const 4)) |
| 170 | + (return (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4)))) |
| 171 | + )) |
| 172 | + |
| 173 | + ;; confirm we only receive SUBTASK events after the first NONE event. |
| 174 | + (if (i32.ne (local.get $event_code) (i32.const 1 (; SUBTASK ;))) |
| 175 | + (then unreachable)) |
| 176 | + |
| 177 | + ;; if we receive a SUBTASK STARTED event, it should only be for the 3rd or |
| 178 | + ;; 4th subtask (at indices 4/5, resp), so keep waiting for completion |
| 179 | + (if (i32.eq (local.get $payload) (i32.const 1 (; STARTED ;))) (then |
| 180 | + (if (i32.and |
| 181 | + (i32.ne (local.get $index) (i32.const 4)) |
| 182 | + (i32.ne (local.get $index) (i32.const 5))) |
| 183 | + (then unreachable)) |
| 184 | + (return (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4)))) |
| 185 | + )) |
| 186 | + |
| 187 | + ;; when we receive a SUBTASK RETURNED event, check the return value is equal to the |
| 188 | + ;; subtask index (which we've ensured by having $AsyncInner.$counter start at 2, the |
| 189 | + ;; first subtask index. The address of the return buffer is the index*4. |
| 190 | + (if (i32.ne (local.get $payload) (i32.const 2 (; RETURNED ;))) |
| 191 | + (then unreachable)) |
| 192 | + (if (i32.ne (local.get $index) (i32.load (i32.mul (local.get $index) (i32.const 4)))) |
| 193 | + (then unreachable)) |
| 194 | + |
| 195 | + ;; decrement $remain and exit if 0 |
| 196 | + (call $subtask.drop (local.get $index)) |
| 197 | + (global.set $remain (i32.sub (global.get $remain) (i32.const 1))) |
| 198 | + (if (i32.gt_u (global.get $remain) (i32.const 0)) (then |
| 199 | + (return (i32.or (i32.const 2 (; WAIT ;)) (i32.shl (global.get $ws) (i32.const 4)))) |
| 200 | + )) |
| 201 | + (call $task.return (i32.const 42)) |
| 202 | + (i32.const 0 (; EXIT ;)) |
| 203 | + ) |
| 204 | + ) |
| 205 | + (canon task.return (result u32) (core func $task.return)) |
| 206 | + (canon subtask.drop (core func $subtask.drop)) |
| 207 | + (canon waitable.join (core func $waitable.join)) |
| 208 | + (canon waitable-set.new (core func $waitable-set.new)) |
| 209 | + (canon lower (func $unblock) (core func $unblock)) |
| 210 | + (canon lower (func $sync-func1) async (memory $memory "mem") (core func $sync-func1')) |
| 211 | + (canon lower (func $sync-func2) async (memory $memory "mem") (core func $sync-func2')) |
| 212 | + (core instance $em (instantiate $CoreAsyncOuter (with "" (instance |
| 213 | + (export "mem" (memory $memory "mem")) |
| 214 | + (export "task.return" (func $task.return)) |
| 215 | + (export "subtask.drop" (func $subtask.drop)) |
| 216 | + (export "waitable.join" (func $waitable.join)) |
| 217 | + (export "waitable-set.new" (func $waitable-set.new)) |
| 218 | + (export "unblock" (func $unblock)) |
| 219 | + (export "sync-func1" (func $sync-func1')) |
| 220 | + (export "sync-func2" (func $sync-func2')) |
| 221 | + )))) |
| 222 | + (func (export "run") (result u32) (canon lift |
| 223 | + (core func $em "run") |
| 224 | + async (callback (func $em "run-cb")) |
| 225 | + )) |
| 226 | + ) |
| 227 | + |
| 228 | + ;; run1 uses $SyncMiddle |
| 229 | + (instance $async_inner1 (instantiate $AsyncInner)) |
| 230 | + (instance $sync_middle11 (instantiate $SyncMiddle |
| 231 | + (with "blocking-call" (func $async_inner1 "blocking-call")) |
| 232 | + )) |
| 233 | + (instance $sync_middle12 (instantiate $SyncMiddle |
| 234 | + (with "blocking-call" (func $async_inner1 "blocking-call")) |
| 235 | + )) |
| 236 | + (instance $async_outer1 (instantiate $AsyncOuter |
| 237 | + (with "unblock" (func $async_inner1 "unblock")) |
| 238 | + (with "sync-func1" (func $sync_middle11 "sync-func")) |
| 239 | + (with "sync-func2" (func $sync_middle12 "sync-func")) |
| 240 | + )) |
| 241 | + (func (export "run1") (alias export $async_outer1 "run")) |
| 242 | + |
| 243 | + ;; run2 uses $AsyncMiddle |
| 244 | + (instance $async_inner2 (instantiate $AsyncInner)) |
| 245 | + (instance $sync_middle21 (instantiate $SyncMiddle |
| 246 | + (with "blocking-call" (func $async_inner2 "blocking-call")) |
| 247 | + )) |
| 248 | + (instance $sync_middle22 (instantiate $AsyncMiddle |
| 249 | + (with "blocking-call" (func $async_inner2 "blocking-call")) |
| 250 | + )) |
| 251 | + (instance $async_outer2 (instantiate $AsyncOuter |
| 252 | + (with "unblock" (func $async_inner2 "unblock")) |
| 253 | + (with "sync-func1" (func $sync_middle21 "sync-func")) |
| 254 | + (with "sync-func2" (func $sync_middle22 "sync-func")) |
| 255 | + )) |
| 256 | + (func (export "run2") (alias export $async_outer2 "run")) |
| 257 | +) |
| 258 | +(assert_return (invoke "run1") (u32.const 42)) |
| 259 | +(assert_return (invoke "run2") (u32.const 42)) |
0 commit comments