-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WASI: Implement experimental threading support #16207
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
This flag allows the user to force export the memory to the host environment. This is useful when the memory is imported from the host but must also be exported. This is (currently) required to pass the memory validation for runtimes when using threads. In this future this may become an error instead.
When the user enabled the linker-feature 'shared-memory' we do not force a singlethreaded build. The linker already verifies all other CPU features required for threads are enabled. This is true for both WASI and freestanding.
Implements std's `Futex` for the WebAssembly target using Wasm's `atomics` instruction set. When the `atomics` cpu feature is disabled we emit a compile-error.
This implements a first version to spawn a WASI-thread. For a new thread to be created, we calculate the size required to store TLS, the new stack, and metadata. This size is then allocated using a user-provided allocator. After a new thread is spawn, the HOST will call into our bootstrap procedure. This bootstrap procedure will then initialize the TLS segment and set the newly spawned thread's TID. It will also set the stack pointer to the newly created stack to ensure we do not clobber the main thread's stack. When bootstrapping the thread is completed, we will call the user's function on this new thread.
We now store the original allocator that was used to allocate the memory required for the thread. This allocator can then be used in any cleanup functionality to ensure the memory is freed correctly. Secondly, we now use a function to set the stack pointer instead of generating a function using global assembly. This is a lot cleaner and more readable.
We now reset the Thread ID to 0 and wake up the main thread listening for the thread to finish. We use inline assembly as we cannot use the stack to set the thread ID as it could possibly clobber any of the memory. Currently, we leak the memory that was allocated for the thread. We need to implement a way where we can clean up the memory without using the stack (as the stack is stored inside this same memory).
When `join` detects a thread has completed, it will free the allocated memory of the thread. For this we must first copy the allocator. This is required as the allocated memory holds a reference to the original allocator. If we free the memory, we would end up with UB as the allocator would free itself.
When a thread is detached from the main thread, we automatically cleanup any allocated memory. For this we first reset the stack-pointer to the original stack-pointer of the main-thread so we can safely clear the memory which also contains the thread's stack.
When targeting WebAssembly, we default to building a single-threaded build as threads are still experimental. The user however can enable a multi- threaded build by specifying '-fno-single-threaded'. It's a compile-error to enable this flag, but not also enable shared-memory.
Hello @andrewrk @Luukdegram ! Thanks for the great work on wasi thread support!!! I'm new to zig, and try to build to target I use zig 0.13.0, and I try to enable multithread, but I got I try to asio, and here's my build.zig:
|
@albertxavier100 WASI threading doesn't work with the libc, only with native Zig code. |
Why is this the case? It looks wasm-libc has support for it: https://github.com/WebAssembly/wasi-libc?tab=readme-ov-file#building-in-pthread-support |
This PR implements threading support for WASI. Note that WASI-threads is still an experimental feature and not all runtimes support it yet. The main goal of this PR is to not only add support for threads but also to gather feedback to improve the feature upstream.
Currently, the WASI-Threads proposal defines a single API entry which is
wasi.thread-spawn
, allowing us to pass a pointer to a context for later usage. This will ask the host to create an OS thread. Upon thread creation, an exported functionwasi_thread_start
will be called by the host environment. Providing us with a thread ID, and the original pointer we passed tothread-spawn
. Although the host is responsible for creating and initializing the OS thread, it is up to the WebAssembly module to set up and initialize the memory for the thread. For this, we create enough memory to store:Instance
which holds metadata required to bootstrap the thread.Upon thread creation, we then initialize the TLS segment and set the
__tls_base
pointer to this new TLS segment so loads and stores to TLS work correctly. We also set the stack pointer to the new stack that we created upon the call tospawn
.For WASI we ask the user to provide an allocator to ensure allocators are aware of any memory allocated by spawning a thread. Without this, it would be easy for the user to overwrite the memory we allocate during
spawn
as we can only grow pages sequentially and Wasm provides no way to tell what page is reserved and not. This also allows us to free the memory duringjoin
so the user's allocator can re-use memory that was previously allocated as there's currently no way in WebAssembly to free memory.Unfortunately, this meant having to use a 'hack' during
detach
to free the memory (resetting the stack pointer upon thread exit to ensure we can free the memory using the allocator without using the stack that is being freed). The other option is to leak memory when a user usesdetach
. In the future, we can use memory.discard to handle freeing this memory and maybe get rid of the allocator from the API.The next steps outside this PR are:
v7.0.0
For those wanting to play around with this, here's a test program with CLI invocation to both Zig and Wasmtime:
Build
thread.zig
and run on Wasmtime:Output: