use super::*;
#[derive(Clone, Copy, PartialEq)]
#[repr(C)]
pub struct ULockApi {
pub ulock_wait: ULockWaitFn,
pub ulock_wake: ULockWakeFn,
}
#[cfg(all(
target_os = "macos",
target_arch = "aarch64",
not(feature = "weak-aarch64-macos")
))]
mod imp {
use super::*;
pub(super) static API: ULockApi = ULockApi {
ulock_wait: crate::__ulock_wait,
ulock_wake: crate::__ulock_wake,
};
#[inline]
pub(super) unsafe fn ulock_wait(
op: u32,
addr: *mut c_void,
val: u64,
micros: u32,
) -> Result<c_int, ApiUnsupported> {
Ok(crate::__ulock_wait(op, addr, val, micros))
}
#[inline]
pub(super) unsafe fn ulock_wake(
op: u32,
addr: *mut c_void,
val: u64,
) -> Result<c_int, ApiUnsupported> {
Ok(crate::__ulock_wake(op, addr, val))
}
#[inline]
pub(super) fn get() -> Result<&'static ULockApi, ApiUnsupported> {
Ok(&API)
}
}
#[cfg(not(all(
target_os = "macos",
target_arch = "aarch64",
not(feature = "weak-aarch64-macos")
)))]
mod imp {
use super::*;
use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering::*};
#[repr(C)]
struct MaybeULockApi {
ulock_wait: AtomicUsize,
ulock_wake: AtomicUsize,
}
const NOT_INIT: u8 = 0;
const BAD_INIT: u8 = 1;
const GOOD_INIT: u8 = 2;
static INIT_STATE: AtomicU8 = AtomicU8::new(NOT_INIT);
static API: MaybeULockApi = MaybeULockApi {
ulock_wait: AtomicUsize::new(0),
ulock_wake: AtomicUsize::new(0),
};
#[inline]
pub(super) unsafe fn ulock_wait(
op: u32,
addr: *mut c_void,
val: u64,
micros: u32,
) -> Result<cty::c_int, ApiUnsupported> {
let func: Option<ULockWaitFn> = core::mem::transmute(API.ulock_wait.load(Relaxed));
if let Some(func) = func {
return Ok((func)(op, addr, val, micros));
}
ulock_wait_outline(op, addr, val, micros)
}
#[cold]
unsafe fn ulock_wait_outline(
op: u32,
addr: *mut c_void,
val: u64,
micros: u32,
) -> Result<cty::c_int, ApiUnsupported> {
maybe_init().and_then(|_| {
let func: ULockWaitFn = core::mem::transmute(API.ulock_wait.load(Relaxed));
Ok(func(op, addr, val, micros))
})
}
#[inline]
pub(super) unsafe fn ulock_wake(
op: u32,
addr: *mut c_void,
val: u64,
) -> Result<cty::c_int, ApiUnsupported> {
let func: Option<ULockWakeFn> = core::mem::transmute(API.ulock_wake.load(Relaxed));
if let Some(func) = func {
return Ok((func)(op, addr, val));
}
ulock_wake_outline(op, addr, val)
}
#[cold]
unsafe fn ulock_wake_outline(
op: u32,
addr: *mut c_void,
val: u64,
) -> Result<cty::c_int, ApiUnsupported> {
maybe_init().and_then(|_| {
let func: ULockWakeFn = core::mem::transmute(API.ulock_wake.load(Relaxed));
Ok(func(op, addr, val))
})
}
#[inline]
pub(super) fn get() -> Result<&'static super::ULockApi, ApiUnsupported> {
maybe_init()?;
Ok(unsafe { &*(&API as *const _ as *const super::ULockApi) })
}
#[inline]
fn maybe_init() -> Result<(), ApiUnsupported> {
match INIT_STATE.load(Acquire) {
BAD_INIT => return Err(ApiUnsupported(())),
GOOD_INIT => {}
NOT_INIT => init()?,
v => {
debug_assert!(false, "unknown state (please report this bug): {}", v);
unsafe { core::hint::unreachable_unchecked() };
}
}
debug_assert_eq!(INIT_STATE.load(Acquire), GOOD_INIT);
debug_assert!(API.ulock_wait.load(Relaxed) != 0);
debug_assert!(API.ulock_wake.load(Relaxed) != 0);
Ok(())
}
fn init() -> Result<(), ApiUnsupported> {
let ulock_wait = unsafe { load_sym("__ulock_wait\0") as usize };
let ulock_wake = unsafe { load_sym("__ulock_wake\0") as usize };
if ulock_wait == 0 || ulock_wake == 0 {
let old = match INIT_STATE.compare_exchange(NOT_INIT, BAD_INIT, AcqRel, Acquire) {
Ok(v) => v,
Err(v) => v,
};
return match old {
NOT_INIT => Err(ApiUnsupported(())),
GOOD_INIT => Ok(()),
BAD_INIT => Err(ApiUnsupported(())),
v => {
debug_assert!(false, "unknown state (please report this bug): {}", v);
unsafe { core::hint::unreachable_unchecked() };
}
};
}
let old = API
.ulock_wait
.compare_exchange(0, ulock_wait, Relaxed, Relaxed);
debug_assert!(old.is_ok() || old == Err(ulock_wait));
let old = API
.ulock_wake
.compare_exchange(0, ulock_wake, Relaxed, Relaxed);
debug_assert!(old.is_ok() || old == Err(ulock_wake));
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
let old = INIT_STATE.swap(GOOD_INIT, SeqCst);
debug_assert_ne!(old, BAD_INIT);
} else {
INIT_STATE.store(GOOD_INIT, Release);
}
Ok(())
}
#[inline]
unsafe fn load_sym(s: &str) -> *mut c_void {
const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;
extern "C" {
fn dlsym(h: *mut c_void, s: *const cty::c_char) -> *mut c_void;
}
debug_assert_eq!(s.as_bytes()[s.len() - 1], b'\0');
dlsym(RTLD_DEFAULT, s.as_ptr().cast())
}
#[used]
#[cfg(link_section = "__DATA,__mod_init_func")]
static CTOR: extern "C" fn() = init_function;
#[allow(dead_code)]
extern "C" fn init_function() {
drop(init());
}
}
#[inline]
pub unsafe fn ulock_wait(
op: u32,
addr: *mut c_void,
val: u64,
micros: u32,
) -> Result<cty::c_int, ApiUnsupported> {
imp::ulock_wait(op, addr, val, micros)
}
#[inline]
pub unsafe fn ulock_wake(
op: u32,
addr: *mut c_void,
val: u64,
) -> Result<cty::c_int, ApiUnsupported> {
imp::ulock_wake(op, addr, val)
}
impl ULockApi {
#[inline]
pub fn get() -> Result<&'static Self, ApiUnsupported> {
imp::get()
}
}
#[derive(Clone, PartialEq, Copy)]
pub struct ApiUnsupported(());
impl core::fmt::Display for ApiUnsupported {
#[cold]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("ulock api unsupported")
}
}
impl core::fmt::Debug for ApiUnsupported {
#[cold]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("ulock api unsupported")
}
}