#[cfg(all(feature = "fast-barrier", target_os = "windows"))]
pub use windows::{light_barrier, strong_barrier};
#[cfg(all(feature = "fast-barrier", target_os = "linux"))]
pub use linux::{light_barrier, strong_barrier};
#[cfg(all(feature = "fast-barrier", target_os = "macos"))]
pub use macos::{light_barrier, strong_barrier};
#[cfg(any(
not(feature = "fast-barrier"),
all(
not(target_os = "linux"),
not(target_os = "windows"),
not(target_os = "macos")
)
))]
pub use fallback::{light_barrier, strong_barrier};
#[cfg(all(feature = "fast-barrier", target_os = "windows"))]
mod windows {
use core::sync::atomic::{compiler_fence, Ordering};
use winapi::um::processthreadsapi;
pub fn strong_barrier() {
unsafe {
processthreadsapi::FlushProcessWriteBuffers();
}
}
pub fn light_barrier() {
compiler_fence(Ordering::SeqCst);
}
}
#[cfg(all(feature = "fast-barrier", target_os = "linux"))]
mod linux {
use crate::lazy::Lazy;
use core::sync::atomic::{compiler_fence, fence, Ordering};
pub fn strong_barrier() {
match STRATEGY.get() {
Strategy::Membarrier => membarrier::barrier(),
Strategy::Fallback => fence(Ordering::SeqCst),
}
}
pub fn light_barrier() {
match STRATEGY.get() {
Strategy::Membarrier => compiler_fence(Ordering::SeqCst),
Strategy::Fallback => fence(Ordering::SeqCst),
}
}
enum Strategy {
Membarrier,
Fallback,
}
static STRATEGY: Lazy<Strategy> = Lazy::new(|| {
if membarrier::is_supported() {
Strategy::Membarrier
} else {
Strategy::Fallback
}
});
mod membarrier {
#[repr(i32)]
#[allow(dead_code, non_camel_case_types)]
enum membarrier_cmd {
MEMBARRIER_CMD_QUERY = 0,
MEMBARRIER_CMD_GLOBAL = 1,
MEMBARRIER_CMD_GLOBAL_EXPEDITED = 1 << 1,
MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED = 1 << 2,
MEMBARRIER_CMD_PRIVATE_EXPEDITED = 1 << 3,
MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = 1 << 4,
MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE = 1 << 5,
MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE = 1 << 6,
}
fn sys_membarrier(cmd: membarrier_cmd) -> libc::c_long {
unsafe { libc::syscall(libc::SYS_membarrier, cmd as libc::c_int, 0 as libc::c_int) }
}
pub fn is_supported() -> bool {
let ret = sys_membarrier(membarrier_cmd::MEMBARRIER_CMD_QUERY);
if ret < 0
|| ret & membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED as libc::c_long == 0
|| ret & membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED as libc::c_long
== 0
{
return false;
}
if sys_membarrier(membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED) < 0 {
return false;
}
true
}
macro_rules! fatal_assert {
($cond:expr) => {
if !$cond {
#[allow(unused_unsafe)]
unsafe {
libc::abort();
}
}
};
}
pub fn barrier() {
fatal_assert!(sys_membarrier(membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED) >= 0);
}
}
}
#[cfg(all(feature = "fast-barrier", target_os = "macos"))]
mod macos {
use core::ptr::null_mut;
use core::sync::atomic::{compiler_fence, Ordering};
use once_cell::sync::Lazy;
use std::sync::{Mutex, MutexGuard};
struct Ptr(*mut libc::c_void);
unsafe impl Send for Ptr {}
unsafe impl Sync for Ptr {}
static DUMMY_PAGE: Lazy<Mutex<Ptr>> = Lazy::new(|| Mutex::new(Ptr(null_mut())));
unsafe fn populate_dummy_page() -> MutexGuard<'static, Ptr> {
let mut dummy_ptr = DUMMY_PAGE.lock().unwrap();
if dummy_ptr.0.is_null() {
let new_ptr = libc::mmap(
null_mut(),
1,
libc::PROT_READ,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
);
assert!(new_ptr != libc::MAP_FAILED);
assert!(libc::mlock(new_ptr, 1) >= 0);
*dummy_ptr = Ptr(new_ptr);
}
dummy_ptr
}
pub fn strong_barrier() {
unsafe {
let dummy_page = populate_dummy_page();
assert!(libc::mprotect(dummy_page.0, 1, libc::PROT_READ | libc::PROT_WRITE) >= 0);
assert!(libc::mprotect(dummy_page.0, 1, libc::PROT_READ) >= 0);
}
}
pub fn light_barrier() {
compiler_fence(Ordering::SeqCst);
}
}
#[cfg(any(
not(feature = "fast-barrier"),
all(
not(target_os = "linux"),
not(target_os = "windows"),
not(target_os = "macos")
)
))]
mod fallback {
use core::sync::atomic::{fence, Ordering};
pub fn strong_barrier() {
fence(Ordering::SeqCst);
}
pub fn light_barrier() {
fence(Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::{light_barrier, strong_barrier};
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn mixed_test() {
const ITER: usize = 10000;
const DIV_BY_STRONG: usize = 2;
const THREADS: [usize; 6] = [2, 4, 8, 16, 32, 64];
for threads in &THREADS {
let barrier = Arc::new(Barrier::new(threads + 1));
for _ in 0..*threads {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
for i in 0..ITER {
if i % DIV_BY_STRONG == 0 {
strong_barrier();
} else {
light_barrier();
}
}
barrier.wait();
});
}
barrier.wait();
barrier.wait();
}
}
}