From 5871216e33c5ee8ce05b579f1ecf5521a9ada8a9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 22 Jul 2026 10:00:06 -0700 Subject: [PATCH] std: Implement futex on wasip3 targets, update target spec This commit is in preparation for eventual [tier 2 status] for the `wasm32-wasip3` target. Initially this target will not have support for threads but it's expected to come ~later this year. The first step in supporting this is switching internal `#[cfg]` in the standard library to "ok this target has threads", and this commit is the update for synchronization primitives. All synchronization primitives on the `wasm32-wasip3` now use a `futex`-based implementation, and the implementation of the futex itself is located in wasi-libc (see WebAssembly/wasi-libc/834). Other WASI targets can eventually all use this implementation as well, but `wasi-libc`'s implementation of these symbols will need to percolate, so those targets aren't changed yet. For `wasm32-wasip3`, however, any supporting `wasi-libc` will have these symbols. This then additionally fixes the target to actually build with a modern LLVM by passing a necessary flag to `wasm-ld`. This flag isn't supported until LLVM 23, but the `wasm32-wasip3` target isn't fully supported until LLVM 23 anyway (hence its Tier 3 status currently). [tier 2 status]: https://github.com/rust-lang/compiler-team/issues/1001 --- .../src/spec/targets/wasm32_wasip3.rs | 16 ++++- library/std/src/os/mod.rs | 2 +- library/std/src/sys/pal/wasi/mod.rs | 21 +++++- .../std/src/sys/pal/wasi/wasilibc_futex.rs | 68 +++++++++++++++++++ library/std/src/sys/sync/condvar/mod.rs | 1 + library/std/src/sys/sync/mutex/mod.rs | 1 + library/std/src/sys/sync/once/mod.rs | 1 + library/std/src/sys/sync/rwlock/mod.rs | 3 +- .../std/src/sys/sync/thread_parking/mod.rs | 1 + 9 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 library/std/src/sys/pal/wasi/wasilibc_futex.rs diff --git a/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs b/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs index 3fb1d11ef60da..9e981cd73f5a7 100644 --- a/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs +++ b/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs @@ -8,7 +8,7 @@ //! all component-model-level imports anyway. Over time the imports of the //! standard library will change to WASIp3. -use crate::spec::{Env, Target}; +use crate::spec::{Cc, Env, LinkerFlavor, Target, add_link_args}; pub(crate) fn target() -> Target { // As of now WASIp3 is a lightly edited wasip2 target, so start with that @@ -22,5 +22,19 @@ pub(crate) fn target() -> Target { std: Some(true), }; target.options.env = Env::P3; + + // The `--cooperative-threading` flag to the linker dictates the ABI that's + // being used on this target which is to store the stack pointer in a + // component model intrinsic location, for example, rather than a wasm + // global. + // + // Note that this is only specified for `Cc::No`, because when `clang` is + // being used as a linker it'll already pass this. + add_link_args( + &mut target.pre_link_args, + LinkerFlavor::WasmLld(Cc::No), + &["--cooperative-threading"], + ); + target } diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index a7ffd87d84d93..8068f92bcc93c 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -54,7 +54,7 @@ cfg_select! { pub mod linux; } target_family = "wasm" => { - #[cfg(any(target_env = "p1", target_env = "p2"))] + #[cfg(any(target_env = "p1", target_env = "p2", target_env = "p3"))] pub mod wasi; #[cfg(target_env = "p2")] pub mod wasip2; diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index 8ea26faca7e70..9069d0f0064a7 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -7,13 +7,28 @@ use crate::io; pub mod conf; -#[allow(unused)] -#[path = "../wasm/atomics/futex.rs"] -pub mod futex; pub mod stack_overflow; #[path = "../unix/time.rs"] pub mod time; +// The wasi-libc based futex is new enough that it's not present in older +// wasi-libc builds. For now that means it's only required on wasip3 (which +// requires a newer wasi-libc anyway). In the future this'll probably switch to +// unconditionally using `wasilibc_futex` as the implementation for all WASI +// targets (and switching all synchronization primitives to the futex version). +cfg_select! { + target_env = "p3" => { + pub mod wasilibc_futex; + pub use wasilibc_futex as futex; + } + target_feature = "atomics" => { + #[allow(unused)] + #[path = "../wasm/atomics/futex.rs"] + pub mod futex; + } + _ => {} +} + #[cfg(not(target_env = "p1"))] mod cabi_realloc; diff --git a/library/std/src/sys/pal/wasi/wasilibc_futex.rs b/library/std/src/sys/pal/wasi/wasilibc_futex.rs new file mode 100644 index 0000000000000..b83e75acac3ae --- /dev/null +++ b/library/std/src/sys/pal/wasi/wasilibc_futex.rs @@ -0,0 +1,68 @@ +//! A futex implementation based on the primitives provided by `wasi-libc`. +//! +//! This is currently only used on wasip3 targets, but in the future once +//! `wasi-libc`'s implementation of these symbols have percolated further it'll +//! be possible to use this on all WASI targets. The `wasi-libc` implementation +//! of these symbols differs depending on the target and configuration: +//! +//! * `wasm32-wasip{1,2}` - this will abort if blocking actually happens +//! * `wasm32-wasip1-threads` - this uses wasm `memory.atomic.*` instructions +//! * `wasm32-wasip3` - depending on libc configuration (`ENABLE_COOP_THREADS`) +//! this either aborts (coop threads disables) on blocking or does the +//! coop-thread-thing to manage threads. +//! +//! Regardless this module is effectively delegating to `wasi-libc` to determine +//! how to do thread management. + +use libc::c_int; + +use crate::ptr; +use crate::sync::atomic::Atomic; +use crate::time::Duration; + +const __WASILIBC_FUTEX_WAKE_ALL: c_int = -1; + +unsafe extern "C" { + fn __wasilibc_futex_wait( + addr: *mut c_int, + val: c_int, + clock: libc::clockid_t, + at: *const libc::timespec, + flags: libc::c_uint, + ) -> c_int; + fn __wasilibc_futex_wake(addr: *const c_int, count: c_int, flags: libc::c_uint) -> c_int; +} + +pub type Futex = Atomic; +pub type Primitive = u32; + +pub type SmallFutex = Atomic; +pub type SmallPrimitive = u32; + +pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { + let timespec = timeout.and_then(|t| { + Some(libc::timespec { + tv_sec: t.as_secs().try_into().ok()?, + tv_nsec: t.subsec_nanos().try_into().ok()?, + }) + }); + unsafe { + __wasilibc_futex_wait( + futex.as_ptr().cast(), + expected.cast_signed(), + libc::CLOCK_REALTIME, + timespec.as_ref().map(ptr::from_ref).unwrap_or(ptr::null()), + 0, + ) == 0 + } +} + +pub fn futex_wake(futex: &Atomic) -> bool { + unsafe { __wasilibc_futex_wake(futex.as_ptr().cast(), 1, 0) == 1 } +} + +pub fn futex_wake_all(futex: &Atomic) { + unsafe { + __wasilibc_futex_wake(futex.as_ptr().cast(), __WASILIBC_FUTEX_WAKE_ALL, 0); + } +} diff --git a/library/std/src/sys/sync/condvar/mod.rs b/library/std/src/sys/sync/condvar/mod.rs index 83cf0ae629851..0e8a4ed9ece86 100644 --- a/library/std/src/sys/sync/condvar/mod.rs +++ b/library/std/src/sys/sync/condvar/mod.rs @@ -10,6 +10,7 @@ cfg_select! { target_os = "fuchsia", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", + all(target_os = "wasi", target_env = "p3"), ) => { mod futex; pub use futex::Condvar; diff --git a/library/std/src/sys/sync/mutex/mod.rs b/library/std/src/sys/sync/mutex/mod.rs index e3d6ad1129c83..9ec251b7e4049 100644 --- a/library/std/src/sys/sync/mutex/mod.rs +++ b/library/std/src/sys/sync/mutex/mod.rs @@ -9,6 +9,7 @@ cfg_select! { target_os = "dragonfly", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", + all(target_os = "wasi", target_env = "p3"), ) => { mod futex; pub use futex::Mutex; diff --git a/library/std/src/sys/sync/once/mod.rs b/library/std/src/sys/sync/once/mod.rs index 5796c6d207dba..3bdb6c9f85ebb 100644 --- a/library/std/src/sys/sync/once/mod.rs +++ b/library/std/src/sys/sync/once/mod.rs @@ -19,6 +19,7 @@ cfg_select! { target_os = "dragonfly", target_os = "fuchsia", target_os = "hermit", + all(target_os = "wasi", target_env = "p3"), ) => { mod futex; pub use futex::{Once, OnceState}; diff --git a/library/std/src/sys/sync/rwlock/mod.rs b/library/std/src/sys/sync/rwlock/mod.rs index 8603fca2da5b5..9991f290e46d1 100644 --- a/library/std/src/sys/sync/rwlock/mod.rs +++ b/library/std/src/sys/sync/rwlock/mod.rs @@ -9,7 +9,8 @@ cfg_select! { target_os = "fuchsia", all(target_family = "wasm", target_feature = "atomics"), target_os = "hermit", - target_os = "motor", + target_os = "motor", + all(target_os = "wasi", target_env = "p3"), ) => { mod futex; pub use futex::RwLock; diff --git a/library/std/src/sys/sync/thread_parking/mod.rs b/library/std/src/sys/sync/thread_parking/mod.rs index 9d5a0a996b115..fe4c830ce38bb 100644 --- a/library/std/src/sys/sync/thread_parking/mod.rs +++ b/library/std/src/sys/sync/thread_parking/mod.rs @@ -10,6 +10,7 @@ cfg_select! { target_os = "fuchsia", target_os = "motor", target_os = "hermit", + all(target_os = "wasi", target_env = "p3"), ) => { mod futex; pub use futex::Parker;