|
| 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)) |
0 commit comments