aarch64_std/sync/
mutex.rs

1use super::poison::*;
2use core::{
3    arch::asm,
4    cell::UnsafeCell,
5    marker::PhantomData,
6    ops::{Deref, DerefMut},
7};
8
9/// A mutual exclusion primitive useful for protecting shared data
10///
11/// This mutex will block threads waiting for the lock to become available. The
12/// mutex can be created via a [`new`] constructor. Each mutex has a type parameter
13/// which represents the data that it is protecting. The data can only be accessed
14/// through the RAII guards returned from [`lock`] and [`try_lock`], which
15/// guarantees that the data is only ever accessed when the mutex is locked.
16///
17/// # Poisoning
18///
19/// Standard library mutexes can become poisoned when the holder panics. However, this crate's
20/// mutexes do not become poisoned as there's currently no reliable way to detect panics. Poisoning
21/// may be added in the future if it becomes possible.
22#[derive(Debug, Default)]
23pub struct Mutex<T: ?Sized> {
24    is_locked: u32,
25    inner: UnsafeCell<T>,
26}
27
28impl<T> Mutex<T> {
29    /// Creates a new mutex in an unlocked state ready for use.
30    pub const fn new(t: T) -> Self {
31        Mutex {
32            is_locked: 0,
33            inner: UnsafeCell::new(t),
34        }
35    }
36}
37
38unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
39unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
40
41impl<T: ?Sized> Mutex<T> {
42    /// Acquires a mutex, blocking the current thread until it is able to do so.
43    ///
44    /// This function will block the local thread until it is available to acquire
45    /// the mutex. Upon returning, the thread is the only thread with the lock
46    /// held. An RAII guard is returned to allow scoped unlock of the lock. When
47    /// the guard goes out of scope, the mutex will be unlocked.
48    ///
49    /// The exact behavior on locking a mutex in the thread which already holds
50    /// the lock is left unspecified. However, this function will not return on
51    /// the second call (it might panic or deadlock, for example).
52    ///
53    /// # Errors
54    ///
55    /// Currently this function cannot fail. The standard library's Mutex may fail if there is a
56    /// panic while the lock is held, but without the standard library we currently have no good
57    /// way to detect panics. Poisoning may be added at a later time.
58    ///
59    /// # Panics
60    ///
61    /// This function might panic when called if the lock is already held by
62    /// the current thread.
63    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
64        self.lock_impl(true)
65    }
66
67    pub(crate) fn lock_impl(&self, _yield_on_fail: bool) -> LockResult<MutexGuard<'_, T>> {
68        aarch64_cpu::asm::sevl();
69        loop {
70            aarch64_cpu::asm::wfe();
71            match self.try_lock() {
72                Ok(g) => return Ok(g),
73                Err(TryLockError::WouldBlock) => {
74                    #[cfg(feature = "alloc")]
75                    if _yield_on_fail {
76                        crate::thread::yield_now();
77                    }
78                    continue;
79                }
80            }
81        }
82    }
83
84    /// Attempts to acquire this lock.
85    ///
86    /// If the lock could not be acquired at this time, then [`Err`] is returned.
87    /// Otherwise, an RAII guard is returned. The lock will be unlocked when the
88    /// guard is dropped.
89    ///
90    /// This function does not block.
91    ///
92    /// # Errors
93    ///
94    /// If the mutex could not be acquired because it is already locked, then
95    /// this call will return the [`WouldBlock`] error.
96    pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
97        let mut result: u32;
98        unsafe {
99            asm!(
100                "ldaxr {result:w}, [{is_locked_addr}]",
101                "cmp {result:w}, 0",
102                "bne 1f",
103                "mov {tmp:w}, 1",
104                "stlxr {result:w}, {tmp:w}, [{is_locked_addr}]",
105                "1:",
106                is_locked_addr = in(reg) &self.is_locked as *const u32 as u64,
107                tmp = out(reg) _,
108                result = out(reg) result,
109                options(nostack),
110            );
111        }
112        if result == 0 {
113            Ok(MutexGuard {
114                lock: self,
115                _make_unsend: PhantomData,
116            })
117        } else {
118            Err(TryLockError::WouldBlock)
119        }
120    }
121
122    /// Consumes this mutex, returning the underlying data.
123    ///
124    /// # Errors
125    ///
126    /// If another user of this mutex panicked while holding the mutex, then
127    /// this call will return an error instead.
128    pub fn into_inner(self) -> LockResult<T>
129    where
130        T: Sized,
131    {
132        Ok(self.inner.into_inner())
133    }
134
135    /// Returns a mutable reference to the underlying data.
136    ///
137    /// Since this call borrows the `Mutex` mutably, no actual locking needs to
138    /// take place -- the mutable borrow statically guarantees no locks exist.
139    ///
140    /// # Errors
141    ///
142    /// If another user of this mutex panicked while holding the mutex, then
143    /// this call will return an error instead.
144    pub fn get_mut(&mut self) -> LockResult<&mut T> {
145        Ok(self.inner.get_mut())
146    }
147}
148
149/// An RAII implementation of a "scoped lock" of a mutex. When this structure is
150/// dropped (falls out of scope), the lock will be unlocked.
151///
152/// The data protected by the mutex can be accessed through this guard via its
153/// [`Deref`] and [`DerefMut`] implementations.
154///
155/// This structure is created by the [`lock`] and [`try_lock`] methods on
156/// [`Mutex`].
157///
158/// [`lock`]: Mutex::lock
159/// [`try_lock`]: Mutex::try_lock
160#[derive(Debug)]
161pub struct MutexGuard<'a, T: ?Sized + 'a> {
162    lock: &'a Mutex<T>,
163    _make_unsend: PhantomData<*const u8>,
164}
165
166unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
167
168impl<T: ?Sized> Deref for MutexGuard<'_, T> {
169    type Target = T;
170
171    fn deref(&self) -> &T {
172        unsafe { &*self.lock.inner.get() }
173    }
174}
175
176impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
177    fn deref_mut(&mut self) -> &mut T {
178        unsafe { &mut *self.lock.inner.get() }
179    }
180}
181
182impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
183    fn drop(&mut self) {
184        // TODO: poison the lock if there's a way to find out if we're panicking
185        unsafe {
186            asm!(
187                "stlr {1:w}, [{0}]",
188                "sev",
189                in(reg) &self.lock.is_locked as *const u32 as *mut u32,
190                in(reg) 0u32,
191                options(nostack),
192            );
193        }
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_no_contention() {
203        let n = Mutex::new(1);
204
205        {
206            let mut guard = n.lock().unwrap();
207            assert!(n.try_lock().is_err());
208            *guard += 1;
209            assert_eq!(*guard, 2);
210        }
211
212        {
213            let mut guard = n.lock().unwrap();
214            assert!(n.try_lock().is_err());
215            *guard += 1;
216            assert_eq!(*guard, 3);
217        }
218
219        {
220            let mut guard = n.try_lock().unwrap();
221            *guard += 1;
222            assert_eq!(*guard, 4);
223        }
224    }
225}