#![cfg(target_os = "macos")]
use std::{marker::PhantomData, ptr::NonNull, sync::atomic::Ordering::*};
use libc::pthread_key_t;
use crate::util::sync::Atomic;
const KEY_UNINIT: pthread_key_t = 0;
pub(crate) struct PThreadKey<T: 'static> {
value: AtomicPThreadKey,
marker: PhantomData<&'static T>,
}
impl<T> PThreadKey<T> {
#[inline]
pub const fn new() -> Self {
Self { value: AtomicPThreadKey::new(KEY_UNINIT), marker: PhantomData }
}
#[inline]
pub fn get(&self) -> Option<NonNull<T>> {
match self.value.load(Relaxed) {
KEY_UNINIT => None,
key => unsafe {
cfg_if::cfg_if! {
if #[cfg(all(
not(miri),
any(target_arch = "x86_64", target_arch = "aarch64"),
))] {
let thread_local = fast::get_thread_local(key as usize);
#[cfg(test)]
assert_eq!(thread_local, libc::pthread_getspecific(key));
} else {
let thread_local = libc::pthread_getspecific(key);
}
}
NonNull::new(thread_local.cast())
},
}
}
#[inline]
pub fn set<D>(&self, ptr: *const T, _: D) -> bool
where
D: FnOnce(NonNull<T>) + Copy,
{
assert_eq!(std::mem::size_of::<D>(), 0);
unsafe extern "C" fn dtor<T, D>(ptr: *mut libc::c_void)
where
T: 'static,
D: FnOnce(NonNull<T>) + Copy,
{
let dtor: D = unsafe { std::mem::zeroed() };
if let Some(ptr) = NonNull::new(ptr) {
dtor(ptr.cast());
}
}
let shared_key = &self.value;
let mut local_key = shared_key.load(Relaxed);
if local_key == KEY_UNINIT {
if unsafe { libc::pthread_key_create(&mut local_key, Some(dtor::<T, D>)) } == 0 {
if let Err(their_key) =
shared_key.compare_exchange(KEY_UNINIT, local_key, Relaxed, Relaxed)
{
unsafe { libc::pthread_key_delete(local_key) };
local_key = their_key;
}
} else {
local_key = shared_key.load(Relaxed);
if local_key == KEY_UNINIT {
return false;
}
}
}
unsafe { libc::pthread_setspecific(local_key, ptr.cast()) == 0 }
}
}
pub(crate) type AtomicPThreadKey = Atomic<pthread_key_t>;
pub(crate) mod fast {
#[inline]
#[cfg(all(not(miri), not(feature = "dyn_thread_local"), target_arch = "x86_64"))]
pub fn get_static_thread_local<T>() -> *const T {
unsafe {
let result;
std::arch::asm!(
"mov {}, gs:[88]",
out(reg) result,
options(pure, readonly, nostack, preserves_flags),
);
result
}
}
#[inline]
#[cfg(all(not(miri), not(feature = "dyn_thread_local"), target_arch = "x86_64"))]
pub unsafe fn set_static_thread_local<T>(ptr: *const T) {
unsafe {
std::arch::asm!(
"mov gs:[88], {}",
in(reg) ptr,
options(nostack, preserves_flags),
);
}
}
#[inline]
#[cfg(all(not(miri), any(target_arch = "x86_64", target_arch = "aarch64")))]
pub unsafe fn get_thread_local(key: usize) -> *mut libc::c_void {
#[cfg(target_arch = "x86_64")]
{
let result;
std::arch::asm!(
"mov {}, gs:[8 * {1}]",
out(reg) result,
in(reg) key,
options(pure, readonly, nostack, preserves_flags),
);
result
}
#[cfg(target_arch = "aarch64")]
{
let result: *const *mut libc::c_void;
std::arch::asm!(
"mrs {0}, tpidrro_el0",
"and {0}, {0}, #-8",
out(reg) result,
options(pure, nomem, nostack, preserves_flags),
);
*result.add(key)
}
}
}