Coverage Report

Created: 2026-03-31 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}