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(¤t);
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