├── .github └── workflows │ └── main.yml ├── README.md ├── test ├── README.md ├── build.sh ├── scripts │ ├── build-c.sh │ └── build-wat.sh └── testsuite │ ├── .gitignore │ ├── manifest.json │ ├── thread_spawn-simple.c │ ├── wasi_thread_spawn.S │ ├── wasi_threads_exit_main_block.json │ ├── wasi_threads_exit_main_block.wat │ ├── wasi_threads_exit_main_busy.json │ ├── wasi_threads_exit_main_busy.wat │ ├── wasi_threads_exit_main_wasi.json │ ├── wasi_threads_exit_main_wasi.wat │ ├── wasi_threads_exit_main_wasi_read.json │ ├── wasi_threads_exit_main_wasi_read.wat │ ├── wasi_threads_exit_nonmain_block.json │ ├── wasi_threads_exit_nonmain_block.wat │ ├── wasi_threads_exit_nonmain_busy.json │ ├── wasi_threads_exit_nonmain_busy.wat │ ├── wasi_threads_exit_nonmain_wasi.json │ ├── wasi_threads_exit_nonmain_wasi.wat │ ├── wasi_threads_exit_nonmain_wasi_read.json │ ├── wasi_threads_exit_nonmain_wasi_read.wat │ ├── wasi_threads_noop.wat │ ├── wasi_threads_return_main_block.wat │ ├── wasi_threads_return_main_busy.wat │ ├── wasi_threads_return_main_wasi.wat │ ├── wasi_threads_return_main_wasi_read.wat │ ├── wasi_threads_spawn.json │ └── wasi_threads_spawn.wat ├── wasi-threads.abi.md └── wasi-threads.wit.md /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | abi-up-to-date: 10 | name: Check ABI files are up-to-date 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: WebAssembly/wit-abi-up-to-date@v6 15 | with: 16 | wit-abi-tag: wit-abi-0.6.0 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `wasi-threads` 2 | 3 | A proposed [WebAssembly System Interface](https://github.com/WebAssembly/WASI) 4 | API to add native thread support. 5 | 6 | > __NOTE__: this proposal is considered a legacy proposal, retained for engines 7 | > that can only support WASI v0.1 (`preview1`). After much debate, future work 8 | > on threads will happen in the [shared-everything-threads] proposal which adds 9 | > component model [built-ins] for thread spawning, among other things. The goal 10 | > is that WASI v0.2 and following will use [shared-everything-threads] (once 11 | > fully implemented) and this proposal can eventually be removed. In the 12 | > meantime, users experimenting with this proposal can continue to get help with 13 | > questions and bugs by opening issues on this repository and tagging various 14 | > maintainers who plan to continue supporting WASI v0.1 (e.g., @loganek, @yamt, 15 | > @wenyongh). 16 | 17 | [shared-everything-threads]: https://github.com/WebAssembly/shared-everything-threads 18 | [built-ins]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#-threads 19 | 20 | ### Current Phase 21 | 22 | Phase 1 23 | 24 | ### Champions 25 | 26 | - [Alexandru Ene](https://github.com/AlexEne) 27 | 28 | ### Phase 4 Advancement Criteria 29 | 30 | _TODO before entering Phase 2._ 31 | 32 | ## Table of Contents 33 | 34 | - [Introduction](#introduction) 35 | - [Goals](#goals) 36 | - [Non-goals](#non-goals) 37 | - [API walk-through](#api-walk-through) 38 | - [Use case: support various languages](#use-case-support-various-languages) 39 | - [Use case: support thread-local storage](#use-case-support-thread-local-storage) 40 | - [Detailed design discussion](#detailed-design-discussion) 41 | - [Design choice: thread IDs](#design-choice-thread-ids) 42 | - [Design choice: termination](#design-choice-termination) 43 | - [Design choice: pthreads](#design-choice-pthreads) 44 | - [Design choice: instance-per-thread](#design-choice-instance-per-thread) 45 | - [Considered alternatives](#considered-alternatives) 46 | - [Alternative: WebAssembly threads](#alternative-webassembly-threads) 47 | - [Alternative: wasi-parallel](#alternative-wasi-parallel) 48 | - [Stakeholder Interest & Feedback](#stakeholder-interest--feedback) 49 | - [References & acknowledgements](#references--acknowledgements) 50 | 51 | ### Introduction 52 | This proposal looks to provide a standard API for thread creation. This is a 53 | WASI-level proposal that augments the WebAssembly-level [threads proposal]. That 54 | WebAssembly-level proposal provides the primitives necessary for shared memory, 55 | atomic operations, and wait/notify. This WASI-level proposal solely provides a 56 | mechanism for spawning threads. Any other thread-like operations (thread 57 | joining, locking, etc.) will use primitives from the WebAssembly-level proposal. 58 | 59 | Some background: browsers already have a mechanism for spawning threads — 60 | [Web Workers] — and the WebAssembly-level proposal avoided specifying how 61 | thread spawning should occur. This allows other uses of WebAssembly — 62 | i.e., outside the browser — to specify their own mechanism for spawning 63 | threads. 64 | 65 | [threads proposal]: https://github.com/WebAssembly/threads 66 | [Web Workers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers 67 | 68 | 69 | ### Goals 70 | - __`pthreads` support__: the goal of this proposal is to add the missing 71 | functions that are required to implement a subset of `pthreads` API. It does 72 | not aim to be identical to the `pthreads` API, but one must be able to create 73 | threads that operate on a shared Wasm memory while using the WebAssembly 74 | atomic instructions to synchronize on memory access. 75 | 76 | - __library reuse__: standardizing this API would allow re-use of existing 77 | libraries and remove friction when porting projects from native 78 | execution contexts to WebAssembly and WASI environments (outside the 79 | browsers). 80 | 81 | - __future-compatible__: a possible future direction for WebAssembly is towards 82 | supporting multiple threads per instance. We aim to expose an API that would 83 | be compatible with this future direction. 84 | 85 | - __browser polyfills__: for browsers, we aim to provide a way to polyfill this 86 | API using Web Workers providing similar functionality to what exists in 87 | browsers today. 88 | 89 | 90 | 91 | ### Non-goals 92 | - __full POSIX compatibity__: this API will not be 100% compatible with all 93 | functions and options described by POSIX threads standard. 94 | 95 | - __modify core WebAssembly__: the current proposal is limited to the WASI APIs 96 | signatures and behavior and does not propose changes to the Wasm instruction 97 | set. 98 | 99 | 100 | 101 | ### API walk-through 102 | 103 | The API consists of a single function. In pseudo-code: 104 | 105 | ```C 106 | status wasi_thread_spawn(thread_start_arg* start_arg); 107 | ``` 108 | 109 | where the `status` is a unique non-negative integer thread ID (TID) of the new 110 | thread (see [Design choice: thread IDs](#design-choice-thread-ids)) or a 111 | negative number representing an error if the host failed to spawn the thread. 112 | The host implementing `wasi_thread_spawn` will call a predetermined function 113 | export (`wasi_thread_start`) in a new WebAssembly instance. Any necessary 114 | locking/signaling/thread-local storage will be implemented using existing 115 | instructions available in WebAssembly. Ideally, users will never use 116 | `wasi_thread_spawn` directly but rather compile their threaded code from a 117 | language that supports threads (see below). 118 | 119 | #### Use case: support various languages 120 | 121 | Using this API, it should be possible to implement threads in languages like: 122 | - __C__, using the `pthreads` library (see the current work in [wasi-libc]) 123 | - __Rust__, as a part of the `std` library (in the future, e.g., [here]) 124 | 125 | The API should be able to support even more languages, but supporting these 126 | initially is a good starting point. 127 | 128 | [wasi-libc]: https://github.com/WebAssembly/wasi-libc 129 | [here]: https://github.com/rust-lang/rust/blob/7308c22c6a8d77e82187e290e1f7459870e48d12/library/std/src/sys/wasm/atomics/thread.rs 130 | 131 | #### Use case: support thread-local storage 132 | 133 | For languages that implement thread-local storage (TLS), the start argument can 134 | contain a language-specific structure with the address and (potentially) the 135 | length of a TLS memory region. The host WebAssembly engine will treat this 136 | argument as an opaque pointer — it should not introspect these 137 | language-specific details. In C, e.g., the start function should be a static 138 | trampoline-like wrapper (exported as `wasi_thread_start`) that reads the actual 139 | user start function out of the start argument and calls this after doing some 140 | TLS bookkeeping (this is not much different than how C starts threads natively). 141 | 142 | 143 | 144 | ### Detailed design discussion 145 | 146 | Threads are tricky to implement. This proposal relies on a specific convention 147 | in order to work correctly. When instantiating a module which is expected to run 148 | with `wasi-threads`, the WASI host must first allocate shared memories to 149 | satisfy the module's imports. 150 | 151 | Upon a call to `wasi_thread_spawn`, the WASI host must: 152 | 153 | 1. instantiate the module again — this child instance will be used for the 154 | new thread 155 | 2. in the child instance, import all of the same WebAssembly objects, 156 | including the above mentioned shared memories, as the parent 157 | 3. optionally, spawn a new host-level thread (other spawning mechanisms are 158 | possible) 159 | 4. calculate a positive, non-duplicate thread ID, `tid`, and return it to the 160 | caller; any error in the previous steps is indicated by returning a negative 161 | error code. 162 | 5. in the new thread, call the child instance's exported entry function with the 163 | thread ID and the start argument: `wasi_thread_start(tid, start_arg)` 164 | 165 | A WASI host that implements the above should be able to spawn threads for a 166 | variety of languages. 167 | 168 | #### Design choice: thread IDs 169 | 170 | When `wasi_thread_spawn` successfully spawns a thread, it returns a thread ID 171 | (TID) — 32-bit integer with several restrictions. TIDs are managed and 172 | provided by the WASI host. To avoid leaking information, the host may choose to 173 | return arbitrary TIDs (as opposed to leaking OS TIDs). 174 | 175 | Valid TIDs fall in the range $[1, 2^{29})$. Some considerations apply: 176 | - `0` is reserved for compatibility reasons with existing libraries (e.g., 177 | wasi-libc) and must not be returned by `wasi_thread_spawn` 178 | - the uppermost three bits of a valid TID must always be `0`. The most 179 | significant bit is the sign bit and recall that `wasi_thread_spawn` uses 180 | negative values to indicate errors. The remaining bits are reserved for 181 | compatibility with existing language implementations. 182 | 183 | #### Design choice: termination 184 | 185 | A `wasi-threads` module initially executes a single thread — the main 186 | thread. As `wasi_thread_spawn` is called, more threads begin to execute. Threads 187 | terminate in the following ways: 188 | 189 | - __upon return__ from `wasi_thread_start`, and other threads continue to 190 | execute 191 | - __upon a trap__ in any thread; all threads are immediately terminated 192 | - __upon a `proc_exit` call__ in any thread; all threads are immediately 193 | terminated. 194 | 195 | #### Design choice: pthreads 196 | 197 | One of the goals of this API is to be able to support `pthreads` for C compiled 198 | to WebAssembly. Given a WASI host that implements `thread_spawn` as described 199 | above, what responsibility would the C language have (i.e., `libc`) to properly 200 | implement `pthreads`? 201 | 202 | `pthread_create` must not only call WASI's `wasi_thread_spawn` but is also 203 | responsible for setting up the new thread's stack, TLS/TSD space, and updating 204 | the `pthread_t` structure. This could be implemented by the following steps 205 | (ignoring error conditions): 206 | 1. configure a `struct start_args` with the user's `void *(*start_func)(void *)` 207 | and `void *start_arg` (as done natively) but also with `pthread_t *thread` 208 | 2. call `malloc` (instead of `mmap`) to allocate TLS/TSD in the shared 209 | WebAssembly memory 210 | 3. define a static, exported `wasi_thread_start` function that takes as 211 | parameters `int tid` and `void *start_args` 212 | 4. in `pthread_create`, call `wasi_thread_spawn` with the configured 213 | `start_args` and use `atomic.wait` to wait for the `start_args->thread->tid` 214 | value to change (note that for web polyfills this may not be necessary since 215 | creation of web workers is not synchronous) 216 | 5. now in the child thread: once the WASI host creates the new thread instance 217 | and calls `wasi_thread_start`, then a) set `args->thread->tid` to the 218 | host-provided `tid`, b) set the `__wasilibc_pthread_self` global to point to 219 | `args->thread` (this is used by `pthread_self`, e.g.), c) use `atomic.notify` 220 | to inform the parent thread that the child now has a `tid`, d) start 221 | executing the user's `start_func` with the user's `start_arg` — at this 222 | point the new instance is executing separately in its own thread 223 | 6. back in the parent thread: once it has been notified that the child has 224 | recorded its TID, it can safely return with the `pthread_t` structure 225 | properly filled out. 226 | 227 | `pthread_join` has a similar `wait`/`notify` implementation, but in reverse: the 228 | parent thread can `wait` on the `thread->return` address to change and the child 229 | thread can `notify` it of this once the user's start function finishes (i.e., at 230 | the end of the `wasi_thread_start` wrapper). 231 | 232 | The remainder of the `pthreads` API can be split up into what can be implemented 233 | and what can safely be skipped until some later date. 234 | 235 | ##### What can easily be implemented 236 | 237 | - `pthread_self` can use the `__wasilibc_pthread_self` global to return the 238 | address to the current thread's `pthread_t` structure; this relies on each 239 | thread mapping to a new instance (and thus a new set of globals) &mdash see 240 | discussion below on "instance per thread." 241 | - `pthread_detach` can be implemented by using the flags already present in the 242 | `pthread_t` structure. 243 | - `pthread_mutex_*`, `pthread_rwlock_*`, `pthread_cond_*`, `sem_*` can all be 244 | implemented using existing operations in the WebAssembly [threads proposal]. 245 | - thread-specific data (TSD), i.e., functions using `pthread_key_t`, can be 246 | implemented using the memory region allocated for the thread in WebAssembly 247 | shared memory. 248 | 249 | ##### What can be skipped 250 | - `pthread_yield` is a [deprecated] `pthreads` function; `sched_yield` is the 251 | right one to use. Since it is unclear how WASI's scheduling should interact 252 | with the host's, this can be deferred until someone has a use case for it. 253 | - `pthread_cancel` allows a parent thread to cancel a child thread; in 254 | particular, asynchronous cancellation is difficult (impossible?) to implement 255 | without a WebAssembly mechanism to interrupt the child thread and it 256 | complicates the entire implementation. It can be left for later. 257 | 258 | [deprecated]: https://man7.org/linux/man-pages/man3/pthread_yield.3.html 259 | 260 | ##### What _has_ been implemented 261 | 262 | `wasi-libc` contains an implementation of `pthreads` using 263 | `wasi-threads`. Various WebAssembly engines support the proposal, including: 264 | Wasmtime, WAMR, Wasmer, toywasm. 265 | 266 | #### Design choice: instance-per-thread 267 | 268 | A thread spawning mechanism for WebAssembly could be implemented in various 269 | ways: the way chosen here, a cloned "instance-per-thread," is one option. The 270 | other major option is to share the instance among many threads, as described in 271 | the [Weakening WebAssembly] paper. Sharing an instance among many threads, as 272 | described there, would require: 273 | - WebAssembly objects (memories, tables, globals, functions) to allow a 274 | `shared` attribute 275 | - the WebAssembly specification to grow a `fork` instruction 276 | 277 | [Weakening WebAssembly]: https://www.researchgate.net/publication/336447205_Weakening_WebAssembly 278 | 279 | The "instance-per-thread" approach was chosen here because a) it matches the 280 | thread instantiation model of the browser (also "instance-per-thread") and b) 281 | the WebAssembly specification changes required for the other approach may take 282 | some time to materialize. In the meantime, this proposal allows threaded 283 | WebAssembly to progress. If in the future the WebAssembly specification were to 284 | add a "many-threads-per-instance" mechanism, the hope is that the API here 285 | should not need to change significantly, though it is unclear how much the 286 | changes might be. 287 | 288 | The "instance-per-thread" approach chosen here does have its disadvantages: 289 | * higher memory consumption (each instance is cloned) 290 | * breaking behavior on non-standard functions such as `dlopen()` that require to 291 | modify the function table 292 | * potential breaking behaviour of existing binaries once a new instruction gets 293 | added. This is a low risk because `shared` attributes do not yet exist on 294 | globals/tables/etc. having the `shared` attribute in a future WebAssembly spec 295 | version is not a likely approach. Most likely, no attributes would be 296 | interpreted as `local`/`private` as that would keep the existing behavior for 297 | binaries. 298 | 299 | 300 | 301 | ### Considered alternatives 302 | 303 | #### Alternative: WebAssembly threads 304 | 305 | Instead of exposing threads at the WASI level, thread spawning could be 306 | specified in the WebAssembly specification. This is the approach described in 307 | the [Weakening WebAssembly] paper. See the [Design choice: 308 | instance-per-thread](#design-choice-instance-per-thread) discussion above for 309 | more details. 310 | 311 | #### Alternative: wasi-parallel 312 | 313 | [wasi-parallel] is another WASI proposal which provides a parallel "for" 314 | construct, similar to what, e.g., [OpenMP](https://www.openmp.org/) provides. 315 | [wasi-parallel] spawns `N` threads at a time (though they may not all run 316 | concurrently); this API spawns a single thread at a time. 317 | 318 | [wasi-parallel]: https://github.com/WebAssembly/wasi-parallel/blob/main/docs/Explainer.md 319 | 320 | 321 | 322 | ### Stakeholder Interest & Feedback 323 | 324 | TODO before entering Phase 3. 325 | 326 | 328 | 329 | 330 | ### References & acknowledgements 331 | 332 | Many thanks for valuable feedback and advice from (alphabetical order): 333 | * [Amanieu d'Antras](https://github.com/Amanieu) 334 | * [Andrew Brown](https://github.com/abrown) 335 | * [Conrad Watt](https://github.com/conrad-watt) 336 | * [George Kulakowski](https://github.com/kulakowski-wasm) 337 | * [Nathaniel McCallum](https://github.com/npmccallum) 338 | * [Petr Penzin](https://github.com/penzn) 339 | * [Sam Clegg](https://github.com/sbc100) 340 | * [Wang Xin](https://github.com/xwang98) 341 | * [Wenyong Huang](https://github.com/wenyongh) 342 | * Xu Jun 343 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Running tests 2 | 3 | ```bash 4 | git clone -b prod/testsuite-all https://github.com/WebAssembly/wasi-testsuite 5 | cd wasi-testsuite/ 6 | ``` 7 | 8 | To execute the `wasi-threads` tests using the Wasmtime 9 | runtime: 10 | ```bash 11 | TEST_RUNTIME_EXE="wasmtime --wasm-features=threads --wasi-modules=experimental-wasi-threads" python3 test-runner/wasi_test_runner.py \ 12 | -r adapters/wasmtime.py \ 13 | -t tests/proposals/wasi-threads/ 14 | ``` 15 | 16 | To execute the `wasi-threads` tests using the WAMR 17 | runtime: 18 | ```bash 19 | TEST_RUNTIME_EXE="iwasm" python3 test-runner/wasi_test_runner.py \ 20 | -r adapters/wasm-micro-runtime.py \ 21 | -t tests/proposals/wasi-threads/ 22 | ``` 23 | 24 | See https://github.com/WebAssembly/wasi-testsuite for details. 25 | -------------------------------------------------------------------------------- /test/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | ./scripts/build-wat.sh 6 | ./scripts/build-c.sh 7 | -------------------------------------------------------------------------------- /test/scripts/build-c.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CC=${CC:=clang} 4 | 5 | for input in testsuite/*.c; do 6 | output="testsuite/$(basename $input .c).wasm" 7 | 8 | if [ "$input" -nt "$output" ]; then 9 | echo "Compiling $input" 10 | $CC "$input" testsuite/wasi_thread_spawn.S -o "$output" 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /test/scripts/build-wat.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | WAT2WASM=${WAT2WASM:-wat2wasm} 4 | for wat in testsuite/*.wat; do 5 | ${WAT2WASM} --enable-threads -o ${wat%%.wat}.wasm ${wat} 6 | done 7 | -------------------------------------------------------------------------------- /test/testsuite/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm -------------------------------------------------------------------------------- /test/testsuite/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WASI threads proposal" 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/thread_spawn-simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static const int64_t SECOND = 1000 * 1000 * 1000; 6 | 7 | typedef struct 8 | { 9 | char *stack; 10 | int th_ready; 11 | int th_continue; 12 | int th_done; 13 | int failed; 14 | int tid; 15 | int value; 16 | } shared_t; 17 | 18 | void __wasi_thread_start_C(int thread_id, int *start_arg) 19 | { 20 | shared_t *data = (shared_t *)start_arg; 21 | 22 | __atomic_store_n(&data->th_ready, 1, __ATOMIC_SEQ_CST); 23 | __builtin_wasm_memory_atomic_notify(&data->th_ready, 1); 24 | 25 | // so we can have all the threads alive at the same time 26 | if (__builtin_wasm_memory_atomic_wait32(&data->th_continue, 0, SECOND) == 2) 27 | { 28 | data->failed = 1; 29 | return; 30 | } 31 | 32 | assert(data->value == 52); 33 | 34 | data->value += 8; 35 | data->tid = thread_id; 36 | 37 | __atomic_store_n(&data->th_done, 1, __ATOMIC_SEQ_CST); 38 | __builtin_wasm_memory_atomic_notify(&data->th_done, 1); 39 | } 40 | 41 | int main(int argc, char **argv) 42 | { 43 | shared_t data[3] = {0}; 44 | int tid[3]; 45 | int data_count = sizeof(data) / sizeof(data[0]); 46 | int i, j; 47 | 48 | for (i = 0; i < data_count; i++) 49 | { 50 | data[i].stack = malloc(128); 51 | data[i].value = 52; 52 | tid[i] = __wasi_thread_spawn(&data[i]); 53 | assert(tid[i] > 0); 54 | assert(__builtin_wasm_memory_atomic_wait32(&data[i].th_ready, 0, 55 | SECOND) != 2); // not a timeout 56 | } 57 | 58 | for (i = 0; i < data_count; i++) 59 | { 60 | __atomic_store_n(&data[i].th_continue, 1, __ATOMIC_SEQ_CST); 61 | __builtin_wasm_memory_atomic_notify(&data[i].th_continue, 1); 62 | } 63 | 64 | for (i = 0; i < data_count; i++) 65 | { 66 | assert(__builtin_wasm_memory_atomic_wait32(&data[i].th_done, 0, 67 | SECOND) != 2); // not a timeout 68 | assert(data[i].tid == tid[i]); 69 | assert(data[i].value == 60); 70 | 71 | for (j = 0; j < i; j++) 72 | { 73 | assert(data[i].tid != data[j].tid); 74 | } 75 | 76 | assert(data[i].failed == 0); 77 | free(data[i].stack); 78 | } 79 | 80 | return 0; 81 | } -------------------------------------------------------------------------------- /test/testsuite/wasi_thread_spawn.S: -------------------------------------------------------------------------------- 1 | # A copy of the wasi-libc implementation: 2 | # https://github.com/WebAssembly/wasi-libc/blob/main/libc-top-half/musl/src/thread/wasm32/wasi_thread_start.s 3 | .text 4 | 5 | .export_name wasi_thread_start, wasi_thread_start 6 | 7 | .globaltype __stack_pointer, i32 8 | .functype __wasi_thread_start_C (i32, i32) -> () 9 | 10 | .hidden wasi_thread_start 11 | .globl wasi_thread_start 12 | .type wasi_thread_start,@function 13 | 14 | wasi_thread_start: 15 | .functype wasi_thread_start (i32, i32) -> () 16 | 17 | # Set up the minimum C environment. 18 | # Note: offsetof(start_arg, stack) == 0 19 | local.get 1 # start_arg 20 | i32.load 0 # stack 21 | global.set __stack_pointer 22 | 23 | # Make the C function do the rest of work. 24 | local.get 0 # tid 25 | local.get 1 # start_arg 26 | call __wasi_thread_start_C 27 | 28 | end_function -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_block.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_block.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread calls proc_exit, it should terminate 2 | ;; a thread blocking in `memory.atomic.wait32` opcode. 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: notify/wait 6 | 7 | (module 8 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 9 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 10 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 11 | (func (export "wasi_thread_start") (param i32 i32) 12 | ;; infinite wait 13 | i32.const 0 14 | i32.const 0 15 | i64.const -1 16 | memory.atomic.wait32 17 | unreachable 18 | ) 19 | (func (export "_start") 20 | ;; spawn a thread 21 | i32.const 0 22 | call $thread_spawn 23 | ;; check error 24 | i32.const 0 25 | i32.le_s 26 | if 27 | unreachable 28 | end 29 | ;; wait 500ms to ensure the other thread block 30 | i32.const 0 31 | i32.const 0 32 | i64.const 500_000_000 33 | memory.atomic.wait32 34 | ;; assert a timeout 35 | i32.const 2 36 | i32.ne 37 | if 38 | unreachable 39 | end 40 | ;; exit 41 | i32.const 99 42 | call $proc_exit 43 | unreachable 44 | ) 45 | ) 46 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_busy.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_busy.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread calls proc_exit, it should terminate 2 | ;; a busy-looping thread. 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | 7 | (module 8 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 9 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 10 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 11 | (func (export "wasi_thread_start") (param i32 i32) 12 | ;; infinite loop 13 | loop 14 | br 0 15 | end 16 | unreachable 17 | ) 18 | (func (export "_start") 19 | ;; spawn a thread 20 | i32.const 0 21 | call $thread_spawn 22 | ;; check error 23 | i32.const 0 24 | i32.le_s 25 | if 26 | unreachable 27 | end 28 | ;; wait 500ms to ensure the other thread to enter the busy loop 29 | i32.const 0 30 | i32.const 0 31 | i64.const 500_000_000 32 | memory.atomic.wait32 33 | ;; assert a timeout 34 | i32.const 2 35 | i32.ne 36 | if 37 | unreachable 38 | end 39 | ;; exit 40 | i32.const 99 41 | call $proc_exit 42 | unreachable 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_wasi.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_wasi.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread calls proc_exit, it should terminate 2 | ;; a thread blocking in a WASI call. (poll_oneoff) 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | ;; 0x100: poll_oneoff subscription 7 | ;; 0x200: poll_oneoff event 8 | ;; 0x300: poll_oneoff return value 9 | 10 | (module 11 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 12 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 13 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 14 | (func $poll_oneoff (import "wasi_snapshot_preview1" "poll_oneoff") (param i32 i32 i32 i32) (result i32)) 15 | (func (export "wasi_thread_start") (param i32 i32) 16 | ;; long enough block 17 | ;; clock_realtime, !abstime (zeros) 18 | i32.const 0x118 ;; 0x100 + offsetof(subscription, timeout) 19 | i64.const 1_000_000_000 ;; 1s 20 | i64.store 21 | i32.const 0x100 ;; subscription 22 | i32.const 0x200 ;; event (out) 23 | i32.const 1 ;; nsubscriptions 24 | i32.const 0x300 ;; retp (out) 25 | call $poll_oneoff 26 | unreachable 27 | ) 28 | (func (export "_start") 29 | ;; spawn a thread 30 | i32.const 0 31 | call $thread_spawn 32 | ;; check error 33 | i32.const 0 34 | i32.le_s 35 | if 36 | unreachable 37 | end 38 | ;; wait 500ms to ensure the other thread block 39 | i32.const 0 40 | i32.const 0 41 | i64.const 500_000_000 42 | memory.atomic.wait32 43 | ;; assert a timeout 44 | i32.const 2 45 | i32.ne 46 | if 47 | unreachable 48 | end 49 | ;; exit 50 | i32.const 99 51 | call $proc_exit 52 | unreachable 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_wasi_read.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_main_wasi_read.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread calls proc_exit, it should terminate 2 | ;; a thread blocking in a WASI call. (fd_read) 3 | ;; 4 | ;; assumption: read from FD 0 blocks. 5 | ;; 6 | ;; linear memory usage: 7 | ;; 0: wait 8 | ;; 100: fd_read iovec 9 | ;; 200: buffer 10 | ;; 300: result 11 | 12 | (module 13 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 14 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 15 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 16 | (func $fd_read (import "wasi_snapshot_preview1" "fd_read") (param i32 i32 i32 i32) (result i32)) 17 | (func (export "wasi_thread_start") (param i32 i32) 18 | ;; read from FD 0 19 | i32.const 100 ;; iov_base 20 | i32.const 200 ;; buffer 21 | i32.store 22 | i32.const 104 ;; iov_len 23 | i32.const 1 24 | i32.store 25 | i32.const 0 ;; fd 0 26 | i32.const 100 ;; iov_base 27 | i32.const 1 ;; iov count 28 | i32.const 300 ;; retp (out) 29 | call $fd_read 30 | unreachable 31 | ) 32 | (func (export "_start") 33 | ;; spawn a thread 34 | i32.const 0 35 | call $thread_spawn 36 | ;; check error 37 | i32.const 0 38 | i32.le_s 39 | if 40 | unreachable 41 | end 42 | ;; wait 500ms to ensure the other thread block 43 | i32.const 0 44 | i32.const 0 45 | i64.const 500_000_000 46 | memory.atomic.wait32 47 | ;; assert a timeout 48 | i32.const 2 49 | i32.ne 50 | if 51 | unreachable 52 | end 53 | ;; exit 54 | i32.const 99 55 | call $proc_exit 56 | unreachable 57 | ) 58 | ) 59 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_block.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_block.wat: -------------------------------------------------------------------------------- 1 | ;; When a non-main thread calls proc_exit, it should terminate 2 | ;; the main thread blocking in `memory.atomic.wait32` opcode. 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | 7 | (module 8 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 9 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 10 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 11 | (func (export "wasi_thread_start") (param i32 i32) 12 | ;; wait 500ms to ensure the other thread block 13 | i32.const 0 14 | i32.const 0 15 | i64.const 500_000_000 16 | memory.atomic.wait32 17 | ;; assert a timeout 18 | i32.const 2 19 | i32.ne 20 | if 21 | unreachable 22 | end 23 | ;; exit 24 | i32.const 99 25 | call $proc_exit 26 | unreachable 27 | ) 28 | (func (export "_start") 29 | ;; spawn a thread 30 | i32.const 0 31 | call $thread_spawn 32 | ;; check error 33 | i32.const 0 34 | i32.le_s 35 | if 36 | unreachable 37 | end 38 | ;; infinite wait 39 | i32.const 0 40 | i32.const 0 41 | i64.const -1 42 | memory.atomic.wait32 43 | unreachable 44 | ) 45 | ) 46 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_busy.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_busy.wat: -------------------------------------------------------------------------------- 1 | ;; When a non-main thread calls proc_exit, it should terminate 2 | ;; the main thread which is busy-looping. 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | 7 | (module 8 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 9 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 10 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 11 | (func (export "wasi_thread_start") (param i32 i32) 12 | ;; wait 500ms to ensure the other thread to enter the busy loop 13 | i32.const 0 14 | i32.const 0 15 | i64.const 500_000_000 16 | memory.atomic.wait32 17 | ;; assert a timeout 18 | i32.const 2 19 | i32.ne 20 | if 21 | unreachable 22 | end 23 | ;; exit 24 | i32.const 99 25 | call $proc_exit 26 | unreachable 27 | ) 28 | (func (export "_start") 29 | ;; spawn a thread 30 | i32.const 0 31 | call $thread_spawn 32 | ;; check error 33 | i32.const 0 34 | i32.le_s 35 | if 36 | unreachable 37 | end 38 | ;; infinite loop 39 | loop 40 | br 0 41 | end 42 | unreachable 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_wasi.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_wasi.wat: -------------------------------------------------------------------------------- 1 | ;; When a non-main thread calls proc_exit, it should terminate 2 | ;; the main thread which is blocking in a WASI call. (poll_oneoff) 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | ;; 0x100: poll_oneoff subscription 7 | ;; 0x200: poll_oneoff event 8 | ;; 0x300: poll_oneoff return value 9 | 10 | (module 11 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 12 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 13 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 14 | (func $poll_oneoff (import "wasi_snapshot_preview1" "poll_oneoff") (param i32 i32 i32 i32) (result i32)) 15 | (func (export "wasi_thread_start") (param i32 i32) 16 | ;; wait 500ms to ensure the other thread block 17 | i32.const 0 18 | i32.const 0 19 | i64.const 500_000_000 20 | memory.atomic.wait32 21 | ;; assert a timeout 22 | i32.const 2 23 | i32.ne 24 | if 25 | unreachable 26 | end 27 | ;; exit 28 | i32.const 99 29 | call $proc_exit 30 | unreachable 31 | ) 32 | (func (export "_start") 33 | ;; spawn a thread 34 | i32.const 0 35 | call $thread_spawn 36 | ;; check error 37 | i32.const 0 38 | i32.le_s 39 | if 40 | unreachable 41 | end 42 | ;; long enough block 43 | ;; clock_realtime, !abstime (zeros) 44 | i32.const 0x118 ;; 0x100 + offsetof(subscription, timeout) 45 | i64.const 1_000_000_000 ;; 1s 46 | i64.store 47 | i32.const 0x100 ;; subscription 48 | i32.const 0x200 ;; event (out) 49 | i32.const 1 ;; nsubscriptions 50 | i32.const 0x300 ;; retp (out) 51 | call $poll_oneoff 52 | unreachable 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_wasi_read.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 99 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_exit_nonmain_wasi_read.wat: -------------------------------------------------------------------------------- 1 | ;; When a non-main thread calls proc_exit, it should terminate 2 | ;; the main thread which is blocking in a WASI call. (fd_read) 3 | ;; 4 | ;; assumption: read from FD 0 blocks. 5 | ;; 6 | ;; linear memory usage: 7 | ;; 0: wait 8 | ;; 100: fd_read iovec 9 | ;; 200: buffer 10 | ;; 300: result 11 | 12 | (module 13 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 14 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 15 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 16 | (func $fd_read (import "wasi_snapshot_preview1" "fd_read") (param i32 i32 i32 i32) (result i32)) 17 | (func (export "wasi_thread_start") (param i32 i32) 18 | ;; wait 500ms to ensure the other thread block 19 | i32.const 0 20 | i32.const 0 21 | i64.const 500_000_000 22 | memory.atomic.wait32 23 | ;; assert a timeout 24 | i32.const 2 25 | i32.ne 26 | if 27 | unreachable 28 | end 29 | ;; exit 30 | i32.const 99 31 | call $proc_exit 32 | unreachable 33 | ) 34 | (func (export "_start") 35 | ;; spawn a thread 36 | i32.const 0 37 | call $thread_spawn 38 | ;; check error 39 | i32.const 0 40 | i32.le_s 41 | if 42 | unreachable 43 | end 44 | ;; read from FD 0 45 | i32.const 100 ;; iov_base 46 | i32.const 200 ;; buffer 47 | i32.store 48 | i32.const 104 ;; iov_len 49 | i32.const 1 50 | i32.store 51 | i32.const 0 ;; fd 0 52 | i32.const 100 ;; iov_base 53 | i32.const 1 ;; iov count 54 | i32.const 300 ;; retp (out) 55 | call $fd_read 56 | unreachable 57 | ) 58 | ) 59 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_noop.wat: -------------------------------------------------------------------------------- 1 | ;; Minimum valid command for wasi-threads. 2 | 3 | (module 4 | (memory (export "memory") (import "foo" "bar") 0 0 shared) 5 | (func (export "_start")) 6 | ) 7 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_return_main_block.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread returns from _start, it should terminate 2 | ;; a thread blocking in `memory.atomic.wait32` opcode. 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: notify/wait 6 | 7 | (module 8 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 9 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 10 | (func (export "wasi_thread_start") (param i32 i32) 11 | ;; infinite wait 12 | i32.const 0 13 | i32.const 0 14 | i64.const -1 15 | memory.atomic.wait32 16 | unreachable 17 | ) 18 | (func (export "_start") 19 | ;; spawn a thread 20 | i32.const 0 21 | call $thread_spawn 22 | ;; check error 23 | i32.const 0 24 | i32.le_s 25 | if 26 | unreachable 27 | end 28 | ;; wait 500ms to ensure the other thread block 29 | i32.const 0 30 | i32.const 0 31 | i64.const 500_000_000 32 | memory.atomic.wait32 33 | ;; assert a timeout 34 | i32.const 2 35 | i32.ne 36 | if 37 | unreachable 38 | end 39 | ;; note: return from _start is the same as proc_exit(0). 40 | ) 41 | ) 42 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_return_main_busy.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread returns from _start, it should terminate 2 | ;; a busy-looping thread. 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | 7 | (module 8 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 9 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 10 | (func (export "wasi_thread_start") (param i32 i32) 11 | ;; infinite loop 12 | loop 13 | br 0 14 | end 15 | unreachable 16 | ) 17 | (func (export "_start") 18 | ;; spawn a thread 19 | i32.const 0 20 | call $thread_spawn 21 | ;; check error 22 | i32.const 0 23 | i32.le_s 24 | if 25 | unreachable 26 | end 27 | ;; wait 500ms to ensure the other thread to enter the busy loop 28 | i32.const 0 29 | i32.const 0 30 | i64.const 500_000_000 31 | memory.atomic.wait32 32 | ;; assert a timeout 33 | i32.const 2 34 | i32.ne 35 | if 36 | unreachable 37 | end 38 | ;; note: return from _start is the same as proc_exit(0). 39 | ) 40 | ) 41 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_return_main_wasi.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread returns from _start, it should terminate 2 | ;; a thread blocking in a WASI call. (poll_oneoff) 3 | ;; 4 | ;; linear memory usage: 5 | ;; 0: wait 6 | ;; 0x100: poll_oneoff subscription 7 | ;; 0x200: poll_oneoff event 8 | ;; 0x300: poll_oneoff return value 9 | 10 | (module 11 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 12 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 13 | (func $poll_oneoff (import "wasi_snapshot_preview1" "poll_oneoff") (param i32 i32 i32 i32) (result i32)) 14 | (func (export "wasi_thread_start") (param i32 i32) 15 | ;; long enough block 16 | ;; clock_realtime, !abstime (zeros) 17 | i32.const 0x118 ;; 0x100 + offsetof(subscription, timeout) 18 | i64.const 1_000_000_000 ;; 1s 19 | i64.store 20 | i32.const 0x100 ;; subscription 21 | i32.const 0x200 ;; event (out) 22 | i32.const 1 ;; nsubscriptions 23 | i32.const 0x300 ;; retp (out) 24 | call $poll_oneoff 25 | unreachable 26 | ) 27 | (func (export "_start") 28 | ;; spawn a thread 29 | i32.const 0 30 | call $thread_spawn 31 | ;; check error 32 | i32.const 0 33 | i32.le_s 34 | if 35 | unreachable 36 | end 37 | ;; wait 500ms to ensure the other thread block 38 | i32.const 0 39 | i32.const 0 40 | i64.const 500_000_000 41 | memory.atomic.wait32 42 | ;; assert a timeout 43 | i32.const 2 44 | i32.ne 45 | if 46 | unreachable 47 | end 48 | ;; note: return from _start is the same as proc_exit(0). 49 | ) 50 | ) 51 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_return_main_wasi_read.wat: -------------------------------------------------------------------------------- 1 | ;; When the main thread returns from _start, it should terminate 2 | ;; a thread blocking in a WASI call. (fd_read) 3 | ;; 4 | ;; assumption: read from FD 0 blocks. 5 | ;; 6 | ;; linear memory usage: 7 | ;; 0: wait 8 | ;; 100: fd_read iovec 9 | ;; 200: buffer 10 | ;; 300: result 11 | 12 | (module 13 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 14 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 15 | (func $fd_read (import "wasi_snapshot_preview1" "fd_read") (param i32 i32 i32 i32) (result i32)) 16 | (func (export "wasi_thread_start") (param i32 i32) 17 | ;; read from FD 0 18 | i32.const 100 ;; iov_base 19 | i32.const 200 ;; buffer 20 | i32.store 21 | i32.const 104 ;; iov_len 22 | i32.const 1 23 | i32.store 24 | i32.const 0 ;; fd 0 25 | i32.const 100 ;; iov_base 26 | i32.const 1 ;; iov count 27 | i32.const 300 ;; retp (out) 28 | call $fd_read 29 | unreachable 30 | ) 31 | (func (export "_start") 32 | ;; spawn a thread 33 | i32.const 0 34 | call $thread_spawn 35 | ;; check error 36 | i32.const 0 37 | i32.le_s 38 | if 39 | unreachable 40 | end 41 | ;; wait 500ms to ensure the other thread block 42 | i32.const 0 43 | i32.const 0 44 | i64.const 500_000_000 45 | memory.atomic.wait32 46 | ;; assert a timeout 47 | i32.const 2 48 | i32.ne 49 | if 50 | unreachable 51 | end 52 | ;; note: return from _start is the same as proc_exit(0). 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_spawn.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit_code": 22 3 | } 4 | -------------------------------------------------------------------------------- /test/testsuite/wasi_threads_spawn.wat: -------------------------------------------------------------------------------- 1 | ;; Create a thread with thread-spawn and perform a few sanity checks. 2 | 3 | ;; linear memory usage: 4 | ;; 0: notify/wait 5 | ;; 4: tid 6 | ;; 8: user_arg 7 | 8 | (module 9 | (memory (export "memory") (import "foo" "bar") 1 1 shared) 10 | (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) 11 | (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) 12 | (func (export "wasi_thread_start") (param $tid i32) (param $user_arg i32) 13 | ;; store tid 14 | i32.const 4 15 | local.get $tid 16 | i32.store 17 | ;; store user pointer 18 | i32.const 8 19 | local.get $user_arg 20 | i32.store 21 | ;; store fence 22 | atomic.fence 23 | ;; notify the main 24 | i32.const 0 25 | i32.const 1 26 | i32.atomic.store 27 | i32.const 0 28 | i32.const 1 29 | memory.atomic.notify 30 | drop 31 | ;; returning from wasi_thread_start terminates only this thread 32 | ) 33 | (func (export "_start") (local $tid i32) 34 | ;; spawn a thread 35 | i32.const 12345 ;; user pointer 36 | call $thread_spawn 37 | ;; check error 38 | local.tee $tid ;; save the tid to check later 39 | i32.const 0 40 | i32.le_s 41 | if 42 | unreachable 43 | end 44 | ;; wait for the spawned thread to run 45 | i32.const 0 46 | i32.const 0 47 | i64.const -1 48 | memory.atomic.wait32 49 | ;; assert it was not a timeout 50 | i32.const 2 51 | i32.eq 52 | if 53 | unreachable 54 | end 55 | ;; load fence 56 | atomic.fence 57 | ;; check the tid 58 | local.get $tid 59 | i32.const 4 60 | i32.load 61 | i32.ne 62 | if 63 | unreachable 64 | end 65 | ;; check the user pointer 66 | i32.const 8 67 | i32.load 68 | i32.const 12345 69 | i32.ne 70 | if 71 | unreachable 72 | end 73 | ;; exit 74 | i32.const 22 75 | call $proc_exit 76 | unreachable 77 | ) 78 | ) 79 | -------------------------------------------------------------------------------- /wasi-threads.abi.md: -------------------------------------------------------------------------------- 1 | # Types 2 | 3 | ## `thread-spawn-result`: `s32` 4 | 5 | The result of the `thread-spawn()` function. 6 | If spawning the thread was successful, the value is positive 7 | and represents a unique thread identifier. Otherwise, the 8 | value is negative and it represents error code. 9 | 10 | Size: 4, Alignment: 4 11 | 12 | ## `start-arg`: `u32` 13 | 14 | A reference to data passed to the start function (`wasi_thread_start()`) called by the newly spawned thread. 15 | 16 | Size: 4, Alignment: 4 17 | 18 | # Functions 19 | 20 | ---- 21 | 22 | #### `thread-spawn` 23 | 24 | Creates a new thread. 25 | ##### Params 26 | 27 | - `start-arg`: [`start-arg`](#start_arg) 28 | ##### Results 29 | 30 | - [`thread-spawn-result`](#thread_spawn_result) 31 | 32 | -------------------------------------------------------------------------------- /wasi-threads.wit.md: -------------------------------------------------------------------------------- 1 | # WASI threads API 2 | 3 | WASI threads is an API for thread creation. 4 | 5 | Its goal is to provide functions that allow implementation of a subset of `pthreads` API, but it doesn't aim to be 100% compatible with POSIX threads standard. 6 | 7 | 8 | ## thread-id 9 | 10 | ```wit 11 | /// The result of the `thread-spawn()` function. 12 | /// If spawning the thread was successful, the value is positive 13 | /// and represents a unique thread identifier. Otherwise, the 14 | /// value is negative and it represents error code. 15 | type thread-spawn-result = s32 16 | ``` 17 | 18 | ## start-arg 19 | 20 | ```wit 21 | /// A reference to data passed to the start function (`wasi_thread_start()`) called by the newly spawned thread. 22 | type start-arg = u32 23 | ``` 24 | 25 | ## thread_spawn 26 | 27 | ```wit 28 | /// Creates a new thread. 29 | thread-spawn: func( 30 | /// A value being passed to a start function (`wasi_thread_start()`). 31 | start-arg: start-arg, 32 | ) -> thread-spawn-result 33 | ``` 34 | --------------------------------------------------------------------------------