1 // Copyright 2016 Amanieu d'Antras
2 //
3 // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 // http://opensource.org/licenses/MIT>, at your option. This file may not be
6 // copied, modified, or distributed except according to those terms.
7 
8 use core::cmp;
9 use core::hint;
10 use core::num::Wrapping;
11 use core::ops;
12 use core::ptr;
13 use core::sync::atomic::{AtomicUsize, Ordering};
14 
15 use bytemuck::NoUninit;
16 
17 // We use an AtomicUsize instead of an AtomicBool because it performs better
18 // on architectures that don't have byte-sized atomics.
19 //
20 // We give each spinlock its own cache line to avoid false sharing.
21 #[repr(align(64))]
22 struct SpinLock(AtomicUsize);
23 
24 impl SpinLock {
lock(&self)25     fn lock(&self) {
26         while self
27             .0
28             .compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
29             .is_err()
30         {
31             while self.0.load(Ordering::Relaxed) != 0 {
32                 hint::spin_loop();
33             }
34         }
35     }
36 
unlock(&self)37     fn unlock(&self) {
38         self.0.store(0, Ordering::Release);
39     }
40 }
41 
42 // A big array of spinlocks which we use to guard atomic accesses. A spinlock is
43 // chosen based on a hash of the address of the atomic object, which helps to
44 // reduce contention compared to a single global lock.
45 macro_rules! array {
46     (@accum (0, $($_es:expr),*) -> ($($body:tt)*))
47         => {array!(@as_expr [$($body)*])};
48     (@accum (1, $($es:expr),*) -> ($($body:tt)*))
49         => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)*))};
50     (@accum (2, $($es:expr),*) -> ($($body:tt)*))
51         => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)* $($es,)*))};
52     (@accum (4, $($es:expr),*) -> ($($body:tt)*))
53         => {array!(@accum (2, $($es,)* $($es),*) -> ($($body)*))};
54     (@accum (8, $($es:expr),*) -> ($($body:tt)*))
55         => {array!(@accum (4, $($es,)* $($es),*) -> ($($body)*))};
56     (@accum (16, $($es:expr),*) -> ($($body:tt)*))
57         => {array!(@accum (8, $($es,)* $($es),*) -> ($($body)*))};
58     (@accum (32, $($es:expr),*) -> ($($body:tt)*))
59         => {array!(@accum (16, $($es,)* $($es),*) -> ($($body)*))};
60     (@accum (64, $($es:expr),*) -> ($($body:tt)*))
61         => {array!(@accum (32, $($es,)* $($es),*) -> ($($body)*))};
62 
63     (@as_expr $e:expr) => {$e};
64 
65     [$e:expr; $n:tt] => { array!(@accum ($n, $e) -> ()) };
66 }
67 static SPINLOCKS: [SpinLock; 64] = array![SpinLock(AtomicUsize::new(0)); 64];
68 
69 // Spinlock pointer hashing function from compiler-rt
70 #[inline]
lock_for_addr(addr: usize) -> &'static SpinLock71 fn lock_for_addr(addr: usize) -> &'static SpinLock {
72     // Disregard the lowest 4 bits.  We want all values that may be part of the
73     // same memory operation to hash to the same value and therefore use the same
74     // lock.
75     let mut hash = addr >> 4;
76     // Use the next bits as the basis for the hash
77     let low = hash & (SPINLOCKS.len() - 1);
78     // Now use the high(er) set of bits to perturb the hash, so that we don't
79     // get collisions from atomic fields in a single object
80     hash >>= 16;
81     hash ^= low;
82     // Return a pointer to the lock to use
83     &SPINLOCKS[hash & (SPINLOCKS.len() - 1)]
84 }
85 
86 #[inline]
lock(addr: usize) -> LockGuard87 fn lock(addr: usize) -> LockGuard {
88     let lock = lock_for_addr(addr);
89     lock.lock();
90     LockGuard(lock)
91 }
92 
93 struct LockGuard(&'static SpinLock);
94 impl Drop for LockGuard {
95     #[inline]
drop(&mut self)96     fn drop(&mut self) {
97         self.0.unlock();
98     }
99 }
100 
101 #[inline]
atomic_load<T>(dst: *mut T) -> T102 pub unsafe fn atomic_load<T>(dst: *mut T) -> T {
103     let _l = lock(dst as usize);
104     ptr::read(dst)
105 }
106 
107 #[inline]
atomic_store<T>(dst: *mut T, val: T)108 pub unsafe fn atomic_store<T>(dst: *mut T, val: T) {
109     let _l = lock(dst as usize);
110     ptr::write(dst, val);
111 }
112 
113 #[inline]
atomic_swap<T>(dst: *mut T, val: T) -> T114 pub unsafe fn atomic_swap<T>(dst: *mut T, val: T) -> T {
115     let _l = lock(dst as usize);
116     ptr::replace(dst, val)
117 }
118 
119 #[inline]
atomic_compare_exchange<T: NoUninit>( dst: *mut T, current: T, new: T, ) -> Result<T, T>120 pub unsafe fn atomic_compare_exchange<T: NoUninit>(
121     dst: *mut T,
122     current: T,
123     new: T,
124 ) -> Result<T, T> {
125     let _l = lock(dst as usize);
126     let result = ptr::read(dst);
127     // compare_exchange compares with memcmp instead of Eq
128     let a = bytemuck::bytes_of(&result);
129     let b = bytemuck::bytes_of(&current);
130     if a == b {
131         ptr::write(dst, new);
132         Ok(result)
133     } else {
134         Err(result)
135     }
136 }
137 
138 #[inline]
atomic_add<T: Copy>(dst: *mut T, val: T) -> T where Wrapping<T>: ops::Add<Output = Wrapping<T>>,139 pub unsafe fn atomic_add<T: Copy>(dst: *mut T, val: T) -> T
140 where
141     Wrapping<T>: ops::Add<Output = Wrapping<T>>,
142 {
143     let _l = lock(dst as usize);
144     let result = ptr::read(dst);
145     ptr::write(dst, (Wrapping(result) + Wrapping(val)).0);
146     result
147 }
148 
149 #[inline]
atomic_sub<T: Copy>(dst: *mut T, val: T) -> T where Wrapping<T>: ops::Sub<Output = Wrapping<T>>,150 pub unsafe fn atomic_sub<T: Copy>(dst: *mut T, val: T) -> T
151 where
152     Wrapping<T>: ops::Sub<Output = Wrapping<T>>,
153 {
154     let _l = lock(dst as usize);
155     let result = ptr::read(dst);
156     ptr::write(dst, (Wrapping(result) - Wrapping(val)).0);
157     result
158 }
159 
160 #[inline]
atomic_and<T: Copy + ops::BitAnd<Output = T>>(dst: *mut T, val: T) -> T161 pub unsafe fn atomic_and<T: Copy + ops::BitAnd<Output = T>>(dst: *mut T, val: T) -> T {
162     let _l = lock(dst as usize);
163     let result = ptr::read(dst);
164     ptr::write(dst, result & val);
165     result
166 }
167 
168 #[inline]
atomic_or<T: Copy + ops::BitOr<Output = T>>(dst: *mut T, val: T) -> T169 pub unsafe fn atomic_or<T: Copy + ops::BitOr<Output = T>>(dst: *mut T, val: T) -> T {
170     let _l = lock(dst as usize);
171     let result = ptr::read(dst);
172     ptr::write(dst, result | val);
173     result
174 }
175 
176 #[inline]
atomic_xor<T: Copy + ops::BitXor<Output = T>>(dst: *mut T, val: T) -> T177 pub unsafe fn atomic_xor<T: Copy + ops::BitXor<Output = T>>(dst: *mut T, val: T) -> T {
178     let _l = lock(dst as usize);
179     let result = ptr::read(dst);
180     ptr::write(dst, result ^ val);
181     result
182 }
183 
184 #[inline]
atomic_min<T: Copy + cmp::Ord>(dst: *mut T, val: T) -> T185 pub unsafe fn atomic_min<T: Copy + cmp::Ord>(dst: *mut T, val: T) -> T {
186     let _l = lock(dst as usize);
187     let result = ptr::read(dst);
188     ptr::write(dst, cmp::min(result, val));
189     result
190 }
191 
192 #[inline]
atomic_max<T: Copy + cmp::Ord>(dst: *mut T, val: T) -> T193 pub unsafe fn atomic_max<T: Copy + cmp::Ord>(dst: *mut T, val: T) -> T {
194     let _l = lock(dst as usize);
195     let result = ptr::read(dst);
196     ptr::write(dst, cmp::max(result, val));
197     result
198 }
199