/rust/registry/src/index.crates.io-1949cf8c6b5b557f/seize-0.5.1/src/raw/membarrier.rs
Line | Count | Source |
1 | | //! Memory barriers optimized for RCU, inspired by <https://github.com/jeehoonkang/membarrier-rs>. |
2 | | //! |
3 | | //! # Semantics |
4 | | //! |
5 | | //! There is a total order over all memory barriers provided by this module: |
6 | | //! - Light store barriers, created by a pair of [`light_store`] and |
7 | | //! [`light_barrier`]. |
8 | | //! - Light load barriers, created by a pair of [`light_barrier`] and |
9 | | //! [`light_load`]. |
10 | | //! - Sequentially consistent barriers, or cumulative light barriers. |
11 | | //! - Heavy barriers, created by [`heavy`]. |
12 | | //! |
13 | | //! If thread A issues barrier X and thread B issues barrier Y and X occurs |
14 | | //! before Y in the total order, X is ordered before Y with respect to coherence |
15 | | //! only if either X or Y is a heavy barrier. In other words, there is no way to |
16 | | //! establish an ordering between light barriers without the presence of a heavy |
17 | | //! barrier. |
18 | | #![allow(dead_code)] |
19 | | |
20 | | #[cfg(all(target_os = "linux", feature = "fast-barrier", not(miri)))] |
21 | | pub use linux::*; |
22 | | |
23 | | #[cfg(all(target_os = "windows", feature = "fast-barrier", not(miri)))] |
24 | | pub use windows::*; |
25 | | |
26 | | #[cfg(any( |
27 | | not(feature = "fast-barrier"), |
28 | | not(any(target_os = "windows", target_os = "linux")), |
29 | | miri |
30 | | ))] |
31 | | pub use default::*; |
32 | | |
33 | | #[cfg(any( |
34 | | not(feature = "fast-barrier"), |
35 | | not(any(target_os = "windows", target_os = "linux")), |
36 | | miri |
37 | | ))] |
38 | | mod default { |
39 | | use core::sync::atomic::{fence, Ordering}; |
40 | | |
41 | | pub fn detect() {} |
42 | | |
43 | | /// The ordering for a store operation that synchronizes with heavy |
44 | | /// barriers. |
45 | | /// |
46 | | /// Must be followed by a light barrier. |
47 | | #[inline] |
48 | | pub fn light_store() -> Ordering { |
49 | | // Synchronize with `SeqCst` heavy barriers. |
50 | | Ordering::SeqCst |
51 | | } |
52 | | |
53 | | /// Issues a light memory barrier for a preceding store or subsequent load |
54 | | /// operation. |
55 | | #[inline] |
56 | | pub fn light_barrier() { |
57 | | // This is a no-op due to strong loads and stores. |
58 | | } |
59 | | |
60 | | /// The ordering for a load operation that synchronizes with heavy barriers. |
61 | | #[inline] |
62 | | pub fn light_load() -> Ordering { |
63 | | // Participate in the total order established by light and heavy `SeqCst` |
64 | | // barriers. |
65 | | Ordering::SeqCst |
66 | | } |
67 | | |
68 | | /// Issues a heavy memory barrier for slow path that synchronizes with light |
69 | | /// stores. |
70 | | #[inline] |
71 | | pub fn heavy() { |
72 | | // Synchronize with `SeqCst` light stores. |
73 | | fence(Ordering::SeqCst); |
74 | | } |
75 | | } |
76 | | |
77 | | #[cfg(all(target_os = "linux", feature = "fast-barrier", not(miri)))] |
78 | | mod linux { |
79 | | use std::sync::atomic::{self, AtomicU8, Ordering}; |
80 | | |
81 | | /// The ordering for a store operation that synchronizes with heavy |
82 | | /// barriers. |
83 | | /// |
84 | | /// Must be followed by a light barrier. |
85 | | #[inline] |
86 | 380k | pub fn light_store() -> Ordering { |
87 | 380k | match STRATEGY.load(Ordering::Relaxed) { |
88 | 0 | FALLBACK => Ordering::SeqCst, |
89 | 380k | _ => Ordering::Relaxed, |
90 | | } |
91 | 380k | } seize::raw::membarrier::linux::light_store Line | Count | Source | 86 | 380k | pub fn light_store() -> Ordering { | 87 | 380k | match STRATEGY.load(Ordering::Relaxed) { | 88 | 0 | FALLBACK => Ordering::SeqCst, | 89 | 380k | _ => Ordering::Relaxed, | 90 | | } | 91 | 380k | } |
Unexecuted instantiation: seize::raw::membarrier::linux::light_store |
92 | | |
93 | | /// Issues a light memory barrier for a preceding store or subsequent load |
94 | | /// operation. |
95 | | #[inline] |
96 | 380k | pub fn light_barrier() { |
97 | 380k | atomic::compiler_fence(atomic::Ordering::SeqCst) |
98 | 380k | } seize::raw::membarrier::linux::light_barrier Line | Count | Source | 96 | 380k | pub fn light_barrier() { | 97 | 380k | atomic::compiler_fence(atomic::Ordering::SeqCst) | 98 | 380k | } |
Unexecuted instantiation: seize::raw::membarrier::linux::light_barrier |
99 | | |
100 | | /// The ordering for a load operation that synchronizes with heavy barriers. |
101 | | #[inline] |
102 | 0 | pub fn light_load() -> Ordering { |
103 | | // There is no difference between `Acquire` and `SeqCst` loads on most |
104 | | // platforms, so checking the strategy is not worth it. |
105 | 0 | Ordering::SeqCst |
106 | 0 | } |
107 | | |
108 | | /// Issues a heavy memory barrier for slow path. |
109 | | #[inline] |
110 | 0 | pub fn heavy() { |
111 | | // Issue a private expedited membarrier using the `sys_membarrier()` system |
112 | | // call, if supported; otherwise, fall back to `mprotect()`-based |
113 | | // process-wide memory barrier. |
114 | 0 | match STRATEGY.load(Ordering::Relaxed) { |
115 | 0 | MEMBARRIER => membarrier::barrier(), |
116 | 0 | MPROTECT => mprotect::barrier(), |
117 | 0 | _ => atomic::fence(atomic::Ordering::SeqCst), |
118 | | } |
119 | 0 | } Unexecuted instantiation: seize::raw::membarrier::linux::heavy Unexecuted instantiation: seize::raw::membarrier::linux::heavy |
120 | | |
121 | | /// Use the `membarrier` system call. |
122 | | const MEMBARRIER: u8 = 0; |
123 | | |
124 | | /// Use the `mprotect`-based trick. |
125 | | const MPROTECT: u8 = 1; |
126 | | |
127 | | /// Use `SeqCst` fences. |
128 | | const FALLBACK: u8 = 2; |
129 | | |
130 | | /// The right strategy to use on the current machine. |
131 | | static STRATEGY: AtomicU8 = AtomicU8::new(FALLBACK); |
132 | | |
133 | | /// Perform runtime detection for a membarrier strategy. |
134 | 89 | pub fn detect() { |
135 | 89 | if membarrier::is_supported() { |
136 | 89 | STRATEGY.store(MEMBARRIER, Ordering::Relaxed); |
137 | 89 | } else if mprotect::is_supported() { |
138 | 0 | STRATEGY.store(MPROTECT, Ordering::Relaxed); |
139 | 0 | } |
140 | 89 | } |
141 | | |
142 | | macro_rules! fatal_assert { |
143 | | ($cond:expr) => { |
144 | | if !$cond { |
145 | | #[allow(unused_unsafe)] |
146 | | unsafe { |
147 | | libc::abort(); |
148 | | } |
149 | | } |
150 | | }; |
151 | | } |
152 | | |
153 | | mod membarrier { |
154 | | /// Commands for the membarrier system call. |
155 | | /// |
156 | | /// # Caveat |
157 | | /// |
158 | | /// We're defining it here because, unfortunately, the `libc` crate |
159 | | /// currently doesn't expose `membarrier_cmd` for us. You can |
160 | | /// find the numbers in the [Linux source code](https://github.com/torvalds/linux/blob/master/include/uapi/linux/membarrier.h). |
161 | | /// |
162 | | /// This enum should really be `#[repr(libc::c_int)]`, but Rust |
163 | | /// currently doesn't allow it. |
164 | | #[repr(i32)] |
165 | | #[allow(dead_code, non_camel_case_types)] |
166 | | enum membarrier_cmd { |
167 | | MEMBARRIER_CMD_QUERY = 0, |
168 | | MEMBARRIER_CMD_GLOBAL = (1 << 0), |
169 | | MEMBARRIER_CMD_GLOBAL_EXPEDITED = (1 << 1), |
170 | | MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED = (1 << 2), |
171 | | MEMBARRIER_CMD_PRIVATE_EXPEDITED = (1 << 3), |
172 | | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = (1 << 4), |
173 | | MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE = (1 << 5), |
174 | | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE = (1 << 6), |
175 | | } |
176 | | |
177 | | /// Call the `sys_membarrier` system call. |
178 | | #[inline] |
179 | 178 | fn sys_membarrier(cmd: membarrier_cmd) -> libc::c_long { |
180 | 178 | unsafe { libc::syscall(libc::SYS_membarrier, cmd as libc::c_int, 0 as libc::c_int) } |
181 | 178 | } Unexecuted instantiation: seize::raw::membarrier::linux::membarrier::sys_membarrier seize::raw::membarrier::linux::membarrier::sys_membarrier Line | Count | Source | 179 | 178 | fn sys_membarrier(cmd: membarrier_cmd) -> libc::c_long { | 180 | 178 | unsafe { libc::syscall(libc::SYS_membarrier, cmd as libc::c_int, 0 as libc::c_int) } | 181 | 178 | } |
|
182 | | |
183 | | /// Returns `true` if the `sys_membarrier` call is available. |
184 | 89 | pub fn is_supported() -> bool { |
185 | | // Queries which membarrier commands are supported. Checks if private expedited |
186 | | // membarrier is supported. |
187 | 89 | let ret = sys_membarrier(membarrier_cmd::MEMBARRIER_CMD_QUERY); |
188 | 89 | if ret < 0 |
189 | 89 | || ret & membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED as libc::c_long == 0 |
190 | 89 | || ret & membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED as libc::c_long |
191 | 89 | == 0 |
192 | | { |
193 | 0 | return false; |
194 | 89 | } |
195 | | |
196 | | // Registers the current process as a user of private expedited membarrier. |
197 | 89 | if sys_membarrier(membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED) < 0 { |
198 | 0 | return false; |
199 | 89 | } |
200 | | |
201 | 89 | true |
202 | 89 | } |
203 | | |
204 | | /// Executes a heavy `sys_membarrier`-based barrier. |
205 | | #[inline] |
206 | 0 | pub fn barrier() { |
207 | 0 | fatal_assert!(sys_membarrier(membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED) >= 0); |
208 | 0 | } Unexecuted instantiation: seize::raw::membarrier::linux::membarrier::barrier Unexecuted instantiation: seize::raw::membarrier::linux::membarrier::barrier |
209 | | } |
210 | | |
211 | | mod mprotect { |
212 | | use std::cell::UnsafeCell; |
213 | | use std::mem::MaybeUninit; |
214 | | use std::ptr; |
215 | | use std::sync::{atomic, OnceLock}; |
216 | | |
217 | | struct Barrier { |
218 | | lock: UnsafeCell<libc::pthread_mutex_t>, |
219 | | page: u64, |
220 | | page_size: libc::size_t, |
221 | | } |
222 | | |
223 | | unsafe impl Sync for Barrier {} |
224 | | |
225 | | impl Barrier { |
226 | | /// Issues a process-wide barrier by changing access protections of |
227 | | /// a single mmap-ed page. This method is not as fast as |
228 | | /// the `sys_membarrier()` call, but works very |
229 | | /// similarly. |
230 | | #[inline] |
231 | 0 | fn barrier(&self) { |
232 | 0 | let page = self.page as *mut libc::c_void; |
233 | | |
234 | | unsafe { |
235 | | // Lock the mutex. |
236 | 0 | fatal_assert!(libc::pthread_mutex_lock(self.lock.get()) == 0); |
237 | | |
238 | | // Set the page access protections to read + write. |
239 | 0 | fatal_assert!( |
240 | 0 | libc::mprotect(page, self.page_size, libc::PROT_READ | libc::PROT_WRITE,) |
241 | 0 | == 0 |
242 | | ); |
243 | | |
244 | | // Ensure that the page is dirty before we change the protection so that we |
245 | | // prevent the OS from skipping the global TLB flush. |
246 | 0 | let atomic_usize = &*(page as *const atomic::AtomicUsize); |
247 | 0 | atomic_usize.fetch_add(1, atomic::Ordering::SeqCst); |
248 | | |
249 | | // Set the page access protections to none. |
250 | | // |
251 | | // Changing a page protection from read + write to none causes the OS to issue |
252 | | // an interrupt to flush TLBs on all processors. This also results in flushing |
253 | | // the processor buffers. |
254 | 0 | fatal_assert!(libc::mprotect(page, self.page_size, libc::PROT_NONE) == 0); |
255 | | |
256 | | // Unlock the mutex. |
257 | 0 | fatal_assert!(libc::pthread_mutex_unlock(self.lock.get()) == 0); |
258 | | } |
259 | 0 | } Unexecuted instantiation: <seize::raw::membarrier::linux::mprotect::Barrier>::barrier Unexecuted instantiation: <seize::raw::membarrier::linux::mprotect::Barrier>::barrier |
260 | | } |
261 | | |
262 | | /// An alternative solution to `sys_membarrier` that works on older |
263 | | /// Linux kernels and x86/x86-64 systems. |
264 | | static BARRIER: OnceLock<Barrier> = OnceLock::new(); |
265 | | |
266 | | /// Returns `true` if the `mprotect`-based trick is supported. |
267 | 0 | pub fn is_supported() -> bool { |
268 | 0 | cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") |
269 | 0 | } |
270 | | |
271 | | /// Executes a heavy `mprotect`-based barrier. |
272 | | #[inline] |
273 | 0 | pub fn barrier() { |
274 | 0 | let barrier = BARRIER.get_or_init(|| { |
275 | | unsafe { |
276 | | // Find out the page size on the current system. |
277 | 0 | let page_size = libc::sysconf(libc::_SC_PAGESIZE); |
278 | 0 | fatal_assert!(page_size > 0); |
279 | 0 | let page_size = page_size as libc::size_t; |
280 | | |
281 | | // Create a dummy page. |
282 | 0 | let page = libc::mmap( |
283 | 0 | ptr::null_mut(), |
284 | 0 | page_size, |
285 | | libc::PROT_NONE, |
286 | 0 | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, |
287 | 0 | -1 as libc::c_int, |
288 | 0 | 0 as libc::off_t, |
289 | | ); |
290 | 0 | fatal_assert!(page != libc::MAP_FAILED); |
291 | 0 | fatal_assert!(page as libc::size_t % page_size == 0); |
292 | | |
293 | | // Locking the page ensures that it stays in memory during the two mprotect |
294 | | // calls in `Barrier::barrier()`. If the page was unmapped between those calls, |
295 | | // they would not have the expected effect of generating IPI. |
296 | 0 | libc::mlock(page, page_size as libc::size_t); |
297 | | |
298 | | // Initialize the mutex. |
299 | 0 | let lock = UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER); |
300 | 0 | let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit(); |
301 | 0 | fatal_assert!(libc::pthread_mutexattr_init(attr.as_mut_ptr()) == 0); |
302 | 0 | let mut attr = attr.assume_init(); |
303 | 0 | fatal_assert!( |
304 | 0 | libc::pthread_mutexattr_settype(&mut attr, libc::PTHREAD_MUTEX_NORMAL) == 0 |
305 | | ); |
306 | 0 | fatal_assert!(libc::pthread_mutex_init(lock.get(), &attr) == 0); |
307 | 0 | fatal_assert!(libc::pthread_mutexattr_destroy(&mut attr) == 0); |
308 | | |
309 | 0 | let page = page as u64; |
310 | | |
311 | 0 | Barrier { |
312 | 0 | lock, |
313 | 0 | page, |
314 | 0 | page_size, |
315 | 0 | } |
316 | | } |
317 | 0 | }); Unexecuted instantiation: seize::raw::membarrier::linux::mprotect::barrier::{closure#0}Unexecuted instantiation: seize::raw::membarrier::linux::mprotect::barrier::{closure#0} |
318 | | |
319 | 0 | barrier.barrier(); |
320 | 0 | } Unexecuted instantiation: seize::raw::membarrier::linux::mprotect::barrier Unexecuted instantiation: seize::raw::membarrier::linux::mprotect::barrier |
321 | | } |
322 | | } |
323 | | |
324 | | #[cfg(all(target_os = "windows", feature = "fast-barrier", not(miri)))] |
325 | | mod windows { |
326 | | use core::sync::atomic::{self, Ordering}; |
327 | | use windows_sys; |
328 | | |
329 | | pub fn detect() {} |
330 | | |
331 | | /// The ordering for a store operation that synchronizes with heavy |
332 | | /// barriers. |
333 | | /// |
334 | | /// Must be followed by a light barrier. |
335 | | #[inline] |
336 | | pub fn light_store() -> Ordering { |
337 | | Ordering::Relaxed |
338 | | } |
339 | | |
340 | | /// Issues a light memory barrier for a preceding store or subsequent load |
341 | | /// operation. |
342 | | #[inline] |
343 | | pub fn light_barrier() { |
344 | | atomic::compiler_fence(atomic::Ordering::SeqCst) |
345 | | } |
346 | | |
347 | | /// The ordering for a load operation that synchronizes with heavy barriers. |
348 | | #[inline] |
349 | | pub fn light_load() -> Ordering { |
350 | | Ordering::Relaxed |
351 | | } |
352 | | |
353 | | /// Issues a heavy memory barrier for slow path that synchronizes with light |
354 | | /// stores. |
355 | | #[inline] |
356 | | pub fn heavy() { |
357 | | // Invoke the `FlushProcessWriteBuffers()` system call. |
358 | | unsafe { windows_sys::Win32::System::Threading::FlushProcessWriteBuffers() } |
359 | | } |
360 | | } |