use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, Ordering};
const NO_KEY: u32 = u32::MAX;
#[inline(always)]
pub fn get_or_init_key(atomic_key: &AtomicU32) -> Option<u32> {
let key = atomic_key.load(Ordering::Relaxed);
if key == NO_KEY {
return init_key(atomic_key);
}
Some(key)
}
#[inline(always)]
fn publish_key(atomic_key: &AtomicU32, key: u32, free_fn: impl FnOnce(u32)) -> Option<u32> {
match atomic_key.compare_exchange(NO_KEY, key, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => Some(key),
Err(existing) => {
free_fn(key);
(existing != NO_KEY).then_some(existing)
}
}
}
#[cold]
#[inline(never)]
fn init_key(atomic_key: &AtomicU32) -> Option<u32> {
unsafe {
#[cfg(windows)]
{
unsafe extern "system" {
fn TlsAlloc() -> u32;
fn TlsFree(dwTlsIndex: u32) -> i32;
}
let key = TlsAlloc();
if key == NO_KEY {
return None;
}
publish_key(atomic_key, key, |k| {
TlsFree(k);
})
}
#[cfg(not(windows))]
{
unsafe extern "C" {
fn pthread_key_create(
key: *mut u32,
destructor: Option<unsafe extern "C" fn(*mut c_void)>,
) -> i32;
fn pthread_key_delete(key: u32) -> i32;
}
let mut key = 0u32;
if pthread_key_create(&mut key, None) != 0 {
return None;
}
publish_key(atomic_key, key, |k| {
pthread_key_delete(k);
})
}
}
}
#[inline(always)]
pub fn read_value(key: u32) -> *mut c_void {
unsafe {
#[cfg(windows)]
{
unsafe extern "system" {
fn TlsGetValue(dwTlsIndex: u32) -> *mut c_void;
}
TlsGetValue(key)
}
#[cfg(not(windows))]
{
unsafe extern "C" {
fn pthread_getspecific(key: u32) -> *mut c_void;
}
pthread_getspecific(key)
}
}
}
#[inline(always)]
pub fn write_value(key: u32, value: *mut c_void) {
write_value_ffi(key, value);
}
#[inline(always)]
fn write_value_ffi(key: u32, value: *mut c_void) {
unsafe {
#[cfg(windows)]
{
unsafe extern "system" {
fn TlsSetValue(dwTlsIndex: u32, lpTlsValue: *mut c_void) -> i32;
}
if TlsSetValue(key, value) == 0 {
crate::abort::abort_on_corruption("OS TLS slot write failed (TlsSetValue)");
}
}
#[cfg(not(windows))]
{
unsafe extern "C" {
fn pthread_setspecific(key: u32, value: *const c_void) -> i32;
}
if pthread_setspecific(key, value) != 0 {
crate::abort::abort_on_corruption("OS TLS slot write failed (pthread_setspecific)");
}
}
}
}
#[cfg(all(windows, target_arch = "x86_64", not(miri)))]
#[inline(always)]
unsafe fn read_teb_self() -> *mut u8 {
let teb: *mut u8;
unsafe {
core::arch::asm!(
"mov {}, gs:[0x30]",
out(reg) teb,
options(nostack, preserves_flags, readonly)
);
}
teb
}
#[cfg(all(windows, target_arch = "x86_64", not(miri)))]
#[inline(always)]
unsafe fn teb_expansion_slots() -> *mut *mut c_void {
unsafe { *(read_teb_self().add(0x1780) as *mut *mut *mut c_void) }
}
#[cfg(all(windows, target_arch = "x86_64", not(miri)))]
#[inline(always)]
pub unsafe fn read_teb_slot(index: u32) -> *mut c_void {
unsafe {
if index < 64 {
let val: *mut c_void;
core::arch::asm!(
"mov {}, gs:[0x1480 + {} * 8]",
out(reg) val,
in(reg) index as usize,
options(nostack, preserves_flags, readonly)
);
val
} else {
let expansion_slots = teb_expansion_slots();
if expansion_slots.is_null() {
core::ptr::null_mut()
} else {
*expansion_slots.add(index as usize - 64)
}
}
}
}
#[cfg(all(windows, target_arch = "x86_64", not(miri)))]
#[inline(always)]
pub unsafe fn write_teb_slot(index: u32, value: *mut c_void) {
unsafe {
if index < 64 {
core::arch::asm!(
"mov gs:[0x1480 + {} * 8], {}",
in(reg) index as usize,
in(reg) value,
options(nostack, preserves_flags)
);
} else {
let expansion_slots = teb_expansion_slots();
if expansion_slots.is_null() {
write_value(index, value);
} else {
*expansion_slots.add(index as usize - 64) = value;
}
}
}
}