about summary refs log tree commit diff
path: root/library/std/src/sys/wasm/mutex_atomics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src/sys/wasm/mutex_atomics.rs')
-rw-r--r--library/std/src/sys/wasm/mutex_atomics.rs147
1 files changed, 147 insertions, 0 deletions
diff --git a/library/std/src/sys/wasm/mutex_atomics.rs b/library/std/src/sys/wasm/mutex_atomics.rs
new file mode 100644
index 00000000000..268a53bb564
--- /dev/null
+++ b/library/std/src/sys/wasm/mutex_atomics.rs
@@ -0,0 +1,147 @@
+use crate::arch::wasm32;
+use crate::cell::UnsafeCell;
+use crate::mem;
+use crate::sync::atomic::{AtomicU32, AtomicUsize, Ordering::SeqCst};
+use crate::sys::thread;
+
+pub struct Mutex {
+    locked: AtomicUsize,
+}
+
+// Mutexes have a pretty simple implementation where they contain an `i32`
+// internally that is 0 when unlocked and 1 when the mutex is locked.
+// Acquisition has a fast path where it attempts to cmpxchg the 0 to a 1, and
+// if it fails it then waits for a notification. Releasing a lock is then done
+// by swapping in 0 and then notifying any waiters, if present.
+
+impl Mutex {
+    pub const fn new() -> Mutex {
+        Mutex { locked: AtomicUsize::new(0) }
+    }
+
+    #[inline]
+    pub unsafe fn init(&mut self) {
+        // nothing to do
+    }
+
+    pub unsafe fn lock(&self) {
+        while !self.try_lock() {
+            let val = wasm32::i32_atomic_wait(
+                self.ptr(),
+                1,  // we expect our mutex is locked
+                -1, // wait infinitely
+            );
+            // we should have either woke up (0) or got a not-equal due to a
+            // race (1). We should never time out (2)
+            debug_assert!(val == 0 || val == 1);
+        }
+    }
+
+    pub unsafe fn unlock(&self) {
+        let prev = self.locked.swap(0, SeqCst);
+        debug_assert_eq!(prev, 1);
+        wasm32::atomic_notify(self.ptr(), 1); // wake up one waiter, if any
+    }
+
+    #[inline]
+    pub unsafe fn try_lock(&self) -> bool {
+        self.locked.compare_exchange(0, 1, SeqCst, SeqCst).is_ok()
+    }
+
+    #[inline]
+    pub unsafe fn destroy(&self) {
+        // nothing to do
+    }
+
+    #[inline]
+    fn ptr(&self) -> *mut i32 {
+        assert_eq!(mem::size_of::<usize>(), mem::size_of::<i32>());
+        self.locked.as_mut_ptr() as *mut i32
+    }
+}
+
+pub struct ReentrantMutex {
+    owner: AtomicU32,
+    recursions: UnsafeCell<u32>,
+}
+
+unsafe impl Send for ReentrantMutex {}
+unsafe impl Sync for ReentrantMutex {}
+
+// Reentrant mutexes are similarly implemented to mutexs above except that
+// instead of "1" meaning unlocked we use the id of a thread to represent
+// whether it has locked a mutex. That way we have an atomic counter which
+// always holds the id of the thread that currently holds the lock (or 0 if the
+// lock is unlocked).
+//
+// Once a thread acquires a lock recursively, which it detects by looking at
+// the value that's already there, it will update a local `recursions` counter
+// in a nonatomic fashion (as we hold the lock). The lock is then fully
+// released when this recursion counter reaches 0.
+
+impl ReentrantMutex {
+    pub const unsafe fn uninitialized() -> ReentrantMutex {
+        ReentrantMutex { owner: AtomicU32::new(0), recursions: UnsafeCell::new(0) }
+    }
+
+    pub unsafe fn init(&self) {
+        // nothing to do...
+    }
+
+    pub unsafe fn lock(&self) {
+        let me = thread::my_id();
+        while let Err(owner) = self._try_lock(me) {
+            let val = wasm32::i32_atomic_wait(self.ptr(), owner as i32, -1);
+            debug_assert!(val == 0 || val == 1);
+        }
+    }
+
+    #[inline]
+    pub unsafe fn try_lock(&self) -> bool {
+        self._try_lock(thread::my_id()).is_ok()
+    }
+
+    #[inline]
+    unsafe fn _try_lock(&self, id: u32) -> Result<(), u32> {
+        let id = id.checked_add(1).unwrap(); // make sure `id` isn't 0
+        match self.owner.compare_exchange(0, id, SeqCst, SeqCst) {
+            // we transitioned from unlocked to locked
+            Ok(_) => {
+                debug_assert_eq!(*self.recursions.get(), 0);
+                Ok(())
+            }
+
+            // we currently own this lock, so let's update our count and return
+            // true.
+            Err(n) if n == id => {
+                *self.recursions.get() += 1;
+                Ok(())
+            }
+
+            // Someone else owns the lock, let our caller take care of it
+            Err(other) => Err(other),
+        }
+    }
+
+    pub unsafe fn unlock(&self) {
+        // If we didn't ever recursively lock the lock then we fully unlock the
+        // mutex and wake up a waiter, if any. Otherwise we decrement our
+        // recursive counter and let some one else take care of the zero.
+        match *self.recursions.get() {
+            0 => {
+                self.owner.swap(0, SeqCst);
+                wasm32::atomic_notify(self.ptr() as *mut i32, 1); // wake up one waiter, if any
+            }
+            ref mut n => *n -= 1,
+        }
+    }
+
+    pub unsafe fn destroy(&self) {
+        // nothing to do...
+    }
+
+    #[inline]
+    fn ptr(&self) -> *mut i32 {
+        self.owner.as_mut_ptr() as *mut i32
+    }
+}