use core::ffi::{c_void, CStr};
use core::mem;
use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU8, AtomicU64, Ordering};
const UL_COMPARE_AND_WAIT: u32 = 1;
const UL_COMPARE_AND_WAIT64: u32 = 5;
const ULF_NO_ERRNO: u32 = 0x0100_0000;
const UNINIT: u8 = 0;
const FAILED: u8 = 1;
const OS_SYNC: u8 = 2;
const ULOCK64: u8 = 3;
const ULOCK32: u8 = 4;
#[repr(C)]
#[derive(Clone, Copy)]
enum FutexImpl {
OsSync {
os_sync_wait_on_address: unsafe extern "C" fn(*mut c_void, u64, usize, u32) -> i32,
os_sync_wake_by_address_any: unsafe extern "C" fn(*mut c_void, usize, u32) -> i32,
},
Ulock64 {
__ulock_wait2: unsafe extern "C" fn(u32, *mut c_void, u64, u64, u64) -> i32,
__ulock_wake: unsafe extern "C" fn(u32, *mut c_void, u64) -> i32,
},
Ulock32 {
__ulock_wait: unsafe extern "C" fn(u32, *mut c_void, u64, u32) -> i32,
__ulock_wake: unsafe extern "C" fn(u32, *mut c_void, u64) -> i32,
},
}
struct LazyFutexImpl {
impt: AtomicU8,
wait: AtomicPtr<c_void>,
wake: AtomicPtr<c_void>,
}
impl LazyFutexImpl {
#[inline]
fn dlsym(&self, ty: u8, wait_name: &CStr, wake_name: &CStr) -> bool {
let wait = unsafe { libc::dlsym(libc::RTLD_DEFAULT, wait_name.as_ptr()) };
if wait.is_null() { return false; }
self.wait.store(wait, Ordering::Relaxed);
let wake = unsafe { libc::dlsym(libc::RTLD_DEFAULT, wake_name.as_ptr()) };
if wake.is_null() { return false; }
self.wake.store(wake, Ordering::Relaxed);
self.impt.store(ty, Ordering::Release);
true
}
#[inline]
extern "C" fn fail() -> ! {
panic!("lien: no futex available (macOS < 10.12?)")
}
#[inline]
fn get(&self) -> FutexImpl {
loop {
match self.impt.load(Ordering::Acquire) {
UNINIT => {
if !(self.dlsym(OS_SYNC, c"os_sync_wait_on_address", c"os_sync_wake_by_address_any")
|| self.dlsym(ULOCK64, c"__ulock_wait2", c"__ulock_wake")
|| self.dlsym(ULOCK32, c"__ulock_wait", c"__ulock_wake"))
{
self.impt.store(FAILED, Ordering::Release);
Self::fail()
}
}
FAILED => Self::fail(),
OS_SYNC => return FutexImpl::OsSync {
os_sync_wait_on_address: unsafe { mem::transmute(self.wait.load(Ordering::Relaxed)) },
os_sync_wake_by_address_any: unsafe { mem::transmute(self.wake.load(Ordering::Relaxed)) },
},
ULOCK64 => return FutexImpl::Ulock64 {
__ulock_wait2: unsafe { mem::transmute(self.wait.load(Ordering::Relaxed)) },
__ulock_wake: unsafe { mem::transmute(self.wake.load(Ordering::Relaxed)) },
},
ULOCK32 => return FutexImpl::Ulock32 {
__ulock_wait: unsafe { mem::transmute(self.wait.load(Ordering::Relaxed)) },
__ulock_wake: unsafe { mem::transmute(self.wake.load(Ordering::Relaxed)) },
},
_ => unreachable!(),
}
}
}
}
static IMPL: LazyFutexImpl = LazyFutexImpl {
impt: AtomicU8::new(UNINIT),
wait: AtomicPtr::new(ptr::null_mut()),
wake: AtomicPtr::new(ptr::null_mut()),
};
#[inline]
pub fn wait(atom: &AtomicU64) {
match IMPL.get() {
FutexImpl::OsSync { os_sync_wait_on_address, .. } => {
loop {
match atom.load(Ordering::Acquire) {
0 => break,
v => unsafe { os_sync_wait_on_address(atom.as_ptr().cast(), v, mem::size_of::<u64>(), 0); },
}
}
}
FutexImpl::Ulock64 { __ulock_wait2, .. } => {
loop {
match atom.load(Ordering::Acquire) {
0 => break,
v => unsafe { __ulock_wait2(UL_COMPARE_AND_WAIT64 | ULF_NO_ERRNO, atom.as_ptr().cast(), v, 0, 0); },
}
}
}
FutexImpl::Ulock32 { __ulock_wait, .. } => {
super::wait_hi_lo(atom, |ptr, expected| unsafe {
__ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, ptr as *mut c_void, expected as u64, 0);
});
}
}
}
#[inline]
pub fn wake(ptr: *const u64, old: u64) {
match IMPL.get() {
FutexImpl::OsSync { os_sync_wake_by_address_any, .. } => {
if old != 1 { return; }
unsafe { os_sync_wake_by_address_any(ptr as *mut c_void, mem::size_of::<u64>(), 0); }
}
FutexImpl::Ulock64 { __ulock_wake, .. } => {
if old != 1 { return; }
unsafe { __ulock_wake(UL_COMPARE_AND_WAIT64 | ULF_NO_ERRNO, ptr as *mut c_void, 0); }
}
FutexImpl::Ulock32 { __ulock_wake, .. } => {
super::wake_hi_lo(ptr, old, |p| unsafe {
__ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, p as *mut c_void, 0);
});
}
}
}