From b6cd515b6399720d35a2b2c01065e05e356954e6 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 19 Jun 2025 11:36:25 +1000 Subject: [PATCH 1/2] Add cross-platform `Mutex` and `RwLock` to `wgpu-types` --- Cargo.lock | 3 + Cargo.toml | 2 + wgpu-types/Cargo.toml | 21 ++++ wgpu-types/src/lib.rs | 1 + wgpu-types/src/sync.rs | 243 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 wgpu-types/src/sync.rs diff --git a/Cargo.lock b/Cargo.lock index bc57905953d..b904d76b447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5100,10 +5100,13 @@ version = "25.0.0" dependencies = [ "bitflags 2.9.1", "bytemuck", + "cfg-if", "js-sys", "log", + "parking_lot", "serde", "serde_json", + "spin", "thiserror 2.0.12", "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index e9f0254aed7..c50d2372863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,6 +178,8 @@ serde_json = "1.0.118" serde = { version = "1.0.219", default-features = false } shell-words = "1" smallvec = "1.9" +# NOTE: `crossbeam-deque` currently relies on this version of spin +spin = { version = "0.9.8", default-features = false } spirv = "0.3" static_assertions = "1.1" strum = { version = "0.27", default-features = false, features = ["derive"] } diff --git a/wgpu-types/Cargo.toml b/wgpu-types/Cargo.toml index 921f319592a..d78e81469b0 100644 --- a/wgpu-types/Cargo.toml +++ b/wgpu-types/Cargo.toml @@ -48,6 +48,20 @@ trace = ["std"] # Enable web-specific dependencies for wasm. web = ["dep:js-sys", "dep:web-sys"] +# Enables the `parking_lot` set of locking primitives. +# This is the recommended implementation and will be used in preference to +# any other implementation. +# Will fallback to a `RefCell` based implementation which is `!Sync` when no +# alternative feature is enabled. +parking_lot = ["dep:parking_lot"] + +# Enables the `spin` set of locking primitives. +# This is generally only useful for `no_std` targets, and will be unused if +# either `std` or `parking_lot` are available. +# Will fallback to a `RefCell` based implementation which is `!Sync` when no +# alternative feature is enabled. +spin = ["dep:spin"] + [dependencies] bitflags = { workspace = true, features = ["serde"] } bytemuck = { workspace = true, features = ["derive"] } @@ -57,6 +71,13 @@ serde = { workspace = true, default-features = false, features = [ "alloc", "derive", ], optional = true } +cfg-if.workspace = true +spin = { workspace = true, features = [ + "rwlock", + "mutex", + "spin_mutex", +], optional = true } +parking_lot = { workspace = true, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = { workspace = true, optional = true, default-features = false } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 3ba3bbe2683..4852596669d 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -39,6 +39,7 @@ mod env; mod features; pub mod instance; pub mod math; +pub mod sync; mod transfers; pub use counters::*; diff --git a/wgpu-types/src/sync.rs b/wgpu-types/src/sync.rs new file mode 100644 index 00000000000..45ce1054c82 --- /dev/null +++ b/wgpu-types/src/sync.rs @@ -0,0 +1,243 @@ +//! Provides [`Mutex`] and [`RwLock`] types with an appropriate implementation chosen +//! from: +//! +//! 1. [`parking_lot`] (default) +//! 2. [`std`] +//! 3. [`spin`] +//! 4. [`RefCell`](core::cell::RefCell) (fallback) +//! +//! These are ordered by priority. +//! For example if `parking_lot` and `std` are both enabled, `parking_lot` will +//! be used as the implementation. +//! +//! Generally you should use `parking_lot` for the optimal performance, at the +//! expense of reduced target compatibility. +//! In contrast, `spin` provides the best compatibility (e.g., `no_std`) in exchange +//! for potentially worse performance. +//! If no implementation is chosen, [`RefCell`](core::cell::RefCell) will be used +//! as a fallback. +//! Note that the fallback implementation is _not_ [`Sync`] and will [spin](core::hint::spin_loop) +//! when a lock is contested. +//! +//! [`parking_lot`]: https://docs.rs/parking_lot/ +//! [`std`]: https://docs.rs/std/ +//! [`spin`]: https://docs.rs/std/ + +use core::{fmt, ops}; + +cfg_if::cfg_if! { + if #[cfg(feature = "parking_lot")] { + use parking_lot as implementation; + } else if #[cfg(feature = "std")] { + use std::sync as implementation; + } else if #[cfg(feature = "spin")] { + use spin as implementation; + } else { + mod implementation { + pub(super) use core::cell::RefCell as Mutex; + pub(super) use core::cell::RefMut as MutexGuard; + + pub(super) use core::cell::RefCell as RwLock; + pub(super) use core::cell::Ref as RwLockReadGuard; + pub(super) use core::cell::RefMut as RwLockWriteGuard; + + /// Repeatedly invoke `f` until [`Option::Some`] is returned. + /// This method [spins](core::hint::spin_loop), busy-waiting the current + /// thread. + pub(super) fn spin_unwrap(mut f: impl FnMut() -> Option) -> T { + 'spin: loop { + match (f)() { + Some(value) => break 'spin value, + None => core::hint::spin_loop(), + } + } + } + } + } +} + +/// A plain wrapper around [`implementation::Mutex`]. +/// +/// This is just like [`implementation::Mutex`], but slight inconsistencies +/// between the different implementation APIs are smoothed-over. +pub struct Mutex(implementation::Mutex); + +/// A guard produced by locking [`Mutex`]. +/// +/// This is just a wrapper around a [`implementation::MutexGuard`]. +pub struct MutexGuard<'a, T>(implementation::MutexGuard<'a, T>); + +impl Mutex { + /// Create a new [`Mutex`]. + pub fn new(value: T) -> Mutex { + Mutex(implementation::Mutex::new(value)) + } + + /// Lock the provided [`Mutex`], allowing reading and/or writing. + pub fn lock(&self) -> MutexGuard { + let lock; + + cfg_if::cfg_if! { + if #[cfg(feature = "parking_lot")] { + lock = self.0.lock(); + } else if #[cfg(feature = "std")] { + lock = self.0.lock().unwrap_or_else(std::sync::PoisonError::into_inner); + } else if #[cfg(feature = "spin")] { + lock = self.0.lock(); + } else { + lock = implementation::spin_unwrap(|| self.0.try_borrow_mut().ok()); + } + } + + MutexGuard(lock) + } + + /// Consume the provided [`Mutex`], returning the inner value. + pub fn into_inner(self) -> T { + let inner = self.0.into_inner(); + + #[cfg(all(feature = "std", not(feature = "parking_lot")))] + let inner = inner.unwrap_or_else(std::sync::PoisonError::into_inner); + + inner + } +} + +impl<'a, T> ops::Deref for MutexGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl<'a, T> ops::DerefMut for MutexGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// A plain wrapper around [`implementation::RwLock`]. +/// +/// This is just like [`implementation::RwLock`], but slight inconsistencies +/// between the different implementation APIs are smoothed-over. +pub struct RwLock(implementation::RwLock); + +/// A read guard produced by locking [`RwLock`] as a reader. +/// +/// This is just a wrapper around a [`implementation::RwLockReadGuard`]. +pub struct RwLockReadGuard<'a, T> { + guard: implementation::RwLockReadGuard<'a, T>, +} + +/// A write guard produced by locking [`RwLock`] as a writer. +/// +/// This is just a wrapper around a [`implementation::RwLockWriteGuard`]. +pub struct RwLockWriteGuard<'a, T> { + guard: implementation::RwLockWriteGuard<'a, T>, + /// Allows for a safe `downgrade` method without `parking_lot` + #[cfg(not(feature = "parking_lot"))] + lock: &'a RwLock, +} + +impl RwLock { + /// Create a new [`RwLock`]. + pub fn new(value: T) -> RwLock { + RwLock(implementation::RwLock::new(value)) + } + + /// Read from the provided [`RwLock`]. + pub fn read(&self) -> RwLockReadGuard { + let guard; + + cfg_if::cfg_if! { + if #[cfg(feature = "parking_lot")] { + guard = self.0.read(); + } else if #[cfg(feature = "std")] { + guard = self.0.read().unwrap_or_else(std::sync::PoisonError::into_inner); + } else if #[cfg(feature = "spin")] { + guard = self.0.read(); + } else { + guard = implementation::spin_unwrap(|| self.0.try_borrow().ok()); + } + } + + RwLockReadGuard { guard } + } + + /// Write to the provided [`RwLock`]. + pub fn write(&self) -> RwLockWriteGuard { + let guard; + + cfg_if::cfg_if! { + if #[cfg(feature = "parking_lot")] { + guard = self.0.write(); + } else if #[cfg(feature = "std")] { + guard = self.0.write().unwrap_or_else(std::sync::PoisonError::into_inner); + } else if #[cfg(feature = "spin")] { + guard = self.0.write(); + } else { + guard = implementation::spin_unwrap(|| self.0.try_borrow_mut().ok()); + } + } + + RwLockWriteGuard { + guard, + #[cfg(not(feature = "parking_lot"))] + lock: self, + } + } +} + +impl<'a, T> RwLockWriteGuard<'a, T> { + /// Downgrade a [write guard](RwLockWriteGuard) into a [read guard](RwLockReadGuard). + pub fn downgrade(this: Self) -> RwLockReadGuard<'a, T> { + cfg_if::cfg_if! { + if #[cfg(feature = "parking_lot")] { + RwLockReadGuard { guard: implementation::RwLockWriteGuard::downgrade(this.guard) } + } else { + let RwLockWriteGuard { guard, lock } = this; + + // FIXME(https://github.com/rust-lang/rust/issues/128203): Replace with `RwLockWriteGuard::downgrade` once stable. + // This implementation allows for a different thread to "steal" the lock in-between the drop and the read. + // Ideally, `downgrade` should hold the lock the entire time, maintaining uninterrupted custody. + drop(guard); + lock.read() + } + } + } +} + +impl fmt::Debug for RwLock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'a, T> ops::Deref for RwLockReadGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.guard.deref() + } +} + +impl<'a, T> ops::Deref for RwLockWriteGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.guard.deref() + } +} + +impl<'a, T> ops::DerefMut for RwLockWriteGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.guard.deref_mut() + } +} From d5e343bfaa5dd582399899c0c53e35c139c4b9b9 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 19 Jun 2025 12:32:50 +1000 Subject: [PATCH 2/2] Address Clippy Lints --- wgpu-types/src/sync.rs | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/wgpu-types/src/sync.rs b/wgpu-types/src/sync.rs index 45ce1054c82..4c788ae7091 100644 --- a/wgpu-types/src/sync.rs +++ b/wgpu-types/src/sync.rs @@ -75,17 +75,15 @@ impl Mutex { /// Lock the provided [`Mutex`], allowing reading and/or writing. pub fn lock(&self) -> MutexGuard { - let lock; - cfg_if::cfg_if! { if #[cfg(feature = "parking_lot")] { - lock = self.0.lock(); + let lock = self.0.lock(); } else if #[cfg(feature = "std")] { - lock = self.0.lock().unwrap_or_else(std::sync::PoisonError::into_inner); + let lock = self.0.lock().unwrap_or_else(std::sync::PoisonError::into_inner); } else if #[cfg(feature = "spin")] { - lock = self.0.lock(); + let lock = self.0.lock(); } else { - lock = implementation::spin_unwrap(|| self.0.try_borrow_mut().ok()); + let lock = implementation::spin_unwrap(|| self.0.try_borrow_mut().ok()); } } @@ -103,7 +101,7 @@ impl Mutex { } } -impl<'a, T> ops::Deref for MutexGuard<'a, T> { +impl ops::Deref for MutexGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -111,7 +109,7 @@ impl<'a, T> ops::Deref for MutexGuard<'a, T> { } } -impl<'a, T> ops::DerefMut for MutexGuard<'a, T> { +impl ops::DerefMut for MutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.0.deref_mut() } @@ -154,17 +152,15 @@ impl RwLock { /// Read from the provided [`RwLock`]. pub fn read(&self) -> RwLockReadGuard { - let guard; - cfg_if::cfg_if! { if #[cfg(feature = "parking_lot")] { - guard = self.0.read(); + let guard = self.0.read(); } else if #[cfg(feature = "std")] { - guard = self.0.read().unwrap_or_else(std::sync::PoisonError::into_inner); + let guard = self.0.read().unwrap_or_else(std::sync::PoisonError::into_inner); } else if #[cfg(feature = "spin")] { - guard = self.0.read(); + let guard = self.0.read(); } else { - guard = implementation::spin_unwrap(|| self.0.try_borrow().ok()); + let guard = implementation::spin_unwrap(|| self.0.try_borrow().ok()); } } @@ -173,17 +169,15 @@ impl RwLock { /// Write to the provided [`RwLock`]. pub fn write(&self) -> RwLockWriteGuard { - let guard; - cfg_if::cfg_if! { if #[cfg(feature = "parking_lot")] { - guard = self.0.write(); + let guard = self.0.write(); } else if #[cfg(feature = "std")] { - guard = self.0.write().unwrap_or_else(std::sync::PoisonError::into_inner); + let guard = self.0.write().unwrap_or_else(std::sync::PoisonError::into_inner); } else if #[cfg(feature = "spin")] { - guard = self.0.write(); + let guard = self.0.write(); } else { - guard = implementation::spin_unwrap(|| self.0.try_borrow_mut().ok()); + let guard = implementation::spin_unwrap(|| self.0.try_borrow_mut().ok()); } } @@ -220,7 +214,7 @@ impl fmt::Debug for RwLock { } } -impl<'a, T> ops::Deref for RwLockReadGuard<'a, T> { +impl ops::Deref for RwLockReadGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -228,7 +222,7 @@ impl<'a, T> ops::Deref for RwLockReadGuard<'a, T> { } } -impl<'a, T> ops::Deref for RwLockWriteGuard<'a, T> { +impl ops::Deref for RwLockWriteGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -236,7 +230,7 @@ impl<'a, T> ops::Deref for RwLockWriteGuard<'a, T> { } } -impl<'a, T> ops::DerefMut for RwLockWriteGuard<'a, T> { +impl ops::DerefMut for RwLockWriteGuard<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.guard.deref_mut() }