1;;! component_model_async = true
2;;! component_model_threading = true
3
4;; Spawns 5 threads, makes them write their indices into a buffer out-of-order, then yields to them in-order,
5;; ensuring that the yield order is as-expected.
6
7;; More concretely:
8;; Thread Index | Assigned Number
9;;       1      |        16
10;;       2      |        12
11;;       3      |        04
12;;       4      |        00
13;;       5      |        08
14
15;; After all threads have spawned and written their indices to the byte position given by their assigned number,
16;; the buffer state will be:
17;; 4 3 5 2 1
18
19;; The main thread will then yield to these threads in the order that they are stored in the buffer,
20;; and they will write their assigned number into the buffer, after the indices.
21;; After all threads have been yielded to, the buffer contents will be:
22;; 4 3 5 2 1 0 4 8 12 16
23
24;; The main thread then ensures that the assigned numbers have been written into the correct locations.
25
26(component
27    ;; Defines the table for the thread start function
28    (core module $libc
29        (table (export "__indirect_function_table") 1 funcref))
30    ;; Defines the thread start function and a function that calls thread.new-indirect
31    (core module $m
32        ;; Import the threading builtins and the table from libc
33        (import "" "thread.new-indirect" (func $thread-new-indirect (param i32 i32) (result i32)))
34        (import "" "thread.suspend" (func $thread-suspend (result i32)))
35        (import "" "thread.yield-to-suspended" (func $thread-yield-to-suspended (param i32) (result i32)))
36        (import "" "thread.suspend-to-suspended" (func $thread-suspend-to-suspended (param i32) (result i32)))
37        (import "" "thread.yield" (func $thread-yield (result i32)))
38        (import "" "thread.index" (func $thread-index (result i32)))
39        (import "" "thread.unsuspend" (func $thread-unsuspend (param i32)))
40        (import "libc" "__indirect_function_table" (table $indirect-function-table 1 funcref))
41
42        ;; A memory block that threads will write their thread indexes and assigned values into
43        (memory 1)
44
45        ;; A global that points to the next memory index to write into
46        ;; We initialize this to 20 (threads * 4 bytes of storage per thread)
47        (global $g (mut i32) (i32.const 20))
48
49        ;; The thread entry point, which writes the thread's index into memory at the assigned location,
50        ;; suspends back to the main thread, then writes the assigned value into memory
51        (func $thread-start (param i32)
52            ;; Store the thread index into the assigned location
53            (i32.store (local.get 0) (call $thread-index))
54            (drop (call $thread-suspend))
55            (i32.store (global.get $g) (local.get 0))
56            (global.set $g
57                (i32.add (global.get $g) (i32.const 4))))
58        (export "thread-start" (func $thread-start))
59
60        ;; Initialize the function table with our thread-start function; this will be
61        ;; used by thread.new-indirect
62        (elem (table $indirect-function-table) (i32.const 0) func $thread-start)
63
64        (func $new-thread (param i32)
65            (drop
66                (call $thread-yield-to-suspended
67                    (call $thread-new-indirect (i32.const 0) (local.get 0)))))
68
69        ;; The main entry point
70        (func (export "run") (result i32)
71            ;; Spawn 5 new threads with assigned numbers
72            (call $new-thread (i32.const 16))
73            (call $new-thread (i32.const 12))
74            (call $new-thread (i32.const 4))
75            (call $new-thread (i32.const 0))
76            (call $new-thread (i32.const 8))
77
78            ;; Yield to all threads in ascending order of assigned number
79            (drop (call $thread-yield-to-suspended (i32.load (i32.const 0))))
80            (drop (call $thread-yield-to-suspended (i32.load (i32.const 4))))
81            (drop (call $thread-yield-to-suspended (i32.load (i32.const 8))))
82            (drop (call $thread-yield-to-suspended (i32.load (i32.const 12))))
83            (drop (call $thread-yield-to-suspended (i32.load (i32.const 16))))
84
85            ;; Ensure all assigned numbers have been written to the buffer in order
86            (if (i32.ne (i32.load (i32.const 20)) (i32.const 0)) (then unreachable))
87            (if (i32.ne (i32.load (i32.const 24)) (i32.const 4)) (then unreachable))
88            (if (i32.ne (i32.load (i32.const 28)) (i32.const 8)) (then unreachable))
89            (if (i32.ne (i32.load (i32.const 32)) (i32.const 12)) (then unreachable))
90            (if (i32.ne (i32.load (i32.const 36)) (i32.const 16)) (then unreachable))
91
92            ;; Sentinel value
93            (i32.const 42)))
94
95    ;; Instantiate the libc module to get the table
96    (core instance $libc (instantiate $libc))
97    ;; Get access to `thread.new-indirect` that uses the table from libc
98    (core type $start-func-ty (func (param i32)))
99    (alias core export $libc "__indirect_function_table" (core table $indirect-function-table))
100
101    (core func $thread-new-indirect
102        (canon thread.new-indirect $start-func-ty (table $indirect-function-table)))
103    (core func $thread-yield (canon thread.yield))
104    (core func $thread-index (canon thread.index))
105    (core func $thread-yield-to-suspended (canon thread.yield-to-suspended))
106    (core func $thread-unsuspend (canon thread.unsuspend))
107    (core func $thread-suspend-to-suspended (canon thread.suspend-to-suspended))
108    (core func $thread-suspend (canon thread.suspend))
109
110    ;; Instantiate the main module
111    (core instance $i (
112        instantiate $m
113            (with "" (instance
114                (export "thread.new-indirect" (func $thread-new-indirect))
115                (export "thread.index" (func $thread-index))
116                (export "thread.yield-to-suspended" (func $thread-yield-to-suspended))
117                (export "thread.yield" (func $thread-yield))
118                (export "thread.suspend-to-suspended" (func $thread-suspend-to-suspended))
119                (export "thread.suspend" (func $thread-suspend))
120                (export "thread.unsuspend" (func $thread-unsuspend))))
121            (with "libc" (instance $libc))))
122
123    ;; Export the main entry point
124    (func (export "run") async (result u32) (canon lift (core func $i "run"))))
125
126(assert_return (invoke "run") (u32.const 42))
127