Skip to main content

cubecl_environment/sync/
base.rs

1#[cfg(not(feature = "std"))]
2use spin::{Mutex as MutexImported, MutexGuard, Once as OnceImported, RwLock as RwLockImported};
3#[cfg(feature = "std")]
4use std::sync::{
5    Mutex as MutexImported, MutexGuard, OnceLock as OnceImported, RwLock as RwLockImported,
6};
7
8#[cfg(not(feature = "std"))]
9pub use spin::{LazyLock, RwLockReadGuard, RwLockWriteGuard};
10#[cfg(feature = "std")]
11pub use std::sync::{LazyLock, RwLockReadGuard, RwLockWriteGuard};
12
13/// A spin-based one-time initialization cell, identical on every target.
14///
15/// Prefer [`SyncOnceCell`] for plain lazy initialization; use this when the
16/// fallible [`spin::Once::try_call_once`] API is needed.
17pub use spin::Once;
18
19#[cfg(target_has_atomic = "ptr")]
20pub use alloc::sync::Arc;
21#[cfg(not(target_has_atomic = "ptr"))]
22pub use portable_atomic_util::Arc;
23
24#[cfg(target_has_atomic = "ptr")]
25pub use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicUsize, Ordering};
26#[cfg(not(target_has_atomic = "ptr"))]
27pub use portable_atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicUsize, Ordering};
28
29// No `AtomicU64`: `target_has_atomic = "ptr"` says nothing about 64-bit
30// atomics, and a 32-bit target can have pointer-width CAS without them
31// (thumbv7m). Its one user is behind `stream_local`, which is `std`, and every
32// std target has them — so it takes `core`'s directly rather than making this
33// shim carry a width it cannot provide on both arms.
34
35/// A mutual exclusion primitive useful for protecting shared data
36///
37/// This mutex will block threads waiting for the lock to become available. The
38/// mutex can also be statically initialized or created via a [`Mutex::new`]
39///
40/// [Mutex] wrapper to make `spin::Mutex` API compatible with `std::sync::Mutex` to swap
41#[derive(Debug, Default)]
42pub struct Mutex<T> {
43    inner: MutexImported<T>,
44}
45
46impl<T> Mutex<T> {
47    /// Creates a new mutex in an unlocked state ready for use.
48    #[inline(always)]
49    pub const fn new(value: T) -> Self {
50        Self {
51            inner: MutexImported::new(value),
52        }
53    }
54
55    /// Locks the mutex blocking the current thread until it is able to do so.
56    ///
57    /// Locking cannot fail, so no `Result` is returned. A poisoned lock is
58    /// recovered rather than reported: a panic under a guard must not turn
59    /// every later lock into a panic, matching the unwind behavior of the spin
60    /// implementation used off-std.
61    #[inline(always)]
62    pub fn lock(&self) -> MutexGuard<'_, T> {
63        #[cfg(not(feature = "std"))]
64        {
65            self.inner.lock()
66        }
67
68        #[cfg(feature = "std")]
69        {
70            self.inner
71                .lock()
72                .unwrap_or_else(|poisoned| poisoned.into_inner())
73        }
74    }
75
76    /// Consumes the mutex, returning the value it protected. A poisoned lock
77    /// is recovered, as in [`lock`](Self::lock).
78    #[inline(always)]
79    pub fn into_inner(self) -> T {
80        #[cfg(not(feature = "std"))]
81        {
82            self.inner.into_inner()
83        }
84
85        #[cfg(feature = "std")]
86        {
87            self.inner
88                .into_inner()
89                .unwrap_or_else(|poisoned| poisoned.into_inner())
90        }
91    }
92}
93
94/// A reader-writer lock which is exclusively locked for writing or shared for reading.
95/// This reader-writer lock will block threads waiting for the lock to become available.
96/// The lock can also be statically initialized or created via a [`RwLock::new`]
97/// [`RwLock`] wrapper to make `spin::RwLock` API compatible with `std::sync::RwLock` to swap
98#[derive(Debug)]
99pub struct RwLock<T> {
100    inner: RwLockImported<T>,
101}
102
103impl<T> RwLock<T> {
104    /// Creates a new reader-writer lock in an unlocked state ready for use.
105    #[inline(always)]
106    pub const fn new(value: T) -> Self {
107        Self {
108            inner: RwLockImported::new(value),
109        }
110    }
111
112    /// Locks this rwlock with shared read access, blocking the current thread
113    /// until it can be acquired.
114    ///
115    /// Poisoning is recovered, never reported; see [`Mutex::lock`].
116    #[inline(always)]
117    pub fn read(&self) -> RwLockReadGuard<'_, T> {
118        #[cfg(not(feature = "std"))]
119        {
120            self.inner.read()
121        }
122        #[cfg(feature = "std")]
123        {
124            self.inner
125                .read()
126                .unwrap_or_else(|poisoned| poisoned.into_inner())
127        }
128    }
129
130    /// Locks this rwlock with exclusive write access, blocking the current thread
131    /// until it can be acquired.
132    ///
133    /// Poisoning is recovered, never reported; see [`Mutex::lock`].
134    #[inline(always)]
135    pub fn write(&self) -> RwLockWriteGuard<'_, T> {
136        #[cfg(not(feature = "std"))]
137        {
138            self.inner.write()
139        }
140
141        #[cfg(feature = "std")]
142        {
143            self.inner
144                .write()
145                .unwrap_or_else(|poisoned| poisoned.into_inner())
146        }
147    }
148}
149
150/// An opaque thread identifier.
151///
152/// This is a stub when no std is available to swap with `std::thread::ThreadId`.
153/// There is no way to obtain one on such targets, so it exists only to keep
154/// thread-keyed types nameable.
155#[allow(dead_code)]
156#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
157pub struct ThreadId(core::num::NonZeroU64);
158
159/// A cell that provides lazy one-time initialization that implements [Sync] and [Send].
160///
161/// This module is a stub when no std is available to swap with [`std::sync::OnceLock`].
162pub struct SyncOnceCell<T>(OnceImported<T>);
163
164impl<T> Default for SyncOnceCell<T> {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170impl<T> SyncOnceCell<T> {
171    /// Create a new once.
172    #[inline(always)]
173    pub fn new() -> Self {
174        Self(OnceImported::new())
175    }
176
177    /// Initialize the cell with a value.
178    #[inline(always)]
179    pub fn initialized(value: T) -> Self {
180        #[cfg(not(feature = "std"))]
181        {
182            let cell = OnceImported::initialized(value);
183            Self(cell)
184        }
185
186        #[cfg(feature = "std")]
187        {
188            let cell = OnceImported::new();
189            // Infallible: the cell was just created, so it is empty. Ignoring
190            // the `Err` is what keeps `T: Debug` off this whole impl.
191            let _ = cell.set(value);
192
193            Self(cell)
194        }
195    }
196
197    /// Gets the contents of the cell, initializing it with `f` if the cell
198    /// was empty.
199    #[inline(always)]
200    pub fn get_or_init<F>(&self, f: F) -> &T
201    where
202        F: FnOnce() -> T,
203    {
204        #[cfg(not(feature = "std"))]
205        {
206            self.0.call_once(f)
207        }
208
209        #[cfg(feature = "std")]
210        {
211            self.0.get_or_init(f)
212        }
213    }
214}
215
216#[cfg(all(test, feature = "std"))]
217mod tests {
218    use super::*;
219
220    /// Regression: a panic under a guard must not poison the lock — one
221    /// failed autotune must not take down every later kernel launch.
222    #[test]
223    fn poisoned_mutex_recovers() {
224        let mutex = Mutex::new(0u32);
225
226        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
227            let _guard = mutex.lock();
228            panic!("poison the lock");
229        }))
230        .unwrap_err();
231
232        *mutex.lock() += 1;
233        assert_eq!(*mutex.lock(), 1);
234    }
235
236    #[test]
237    fn poisoned_rwlock_recovers() {
238        let lock = RwLock::new(0u32);
239
240        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
241            let _guard = lock.write();
242            panic!("poison the lock");
243        }))
244        .unwrap_err();
245
246        assert_eq!(*lock.read(), 0);
247    }
248}