pub use crate::selector_traits::{LocalAllocatorSelector, PolicySlotSelection};
use crate::ThreadAllocator;
use mnemosyne_arena::HasSegmentPool;
#[doc(hidden)]
#[repr(C)]
pub struct LocalAllocatorSlot<B: HasSegmentPool> {
allocator: core::cell::UnsafeCell<ThreadAllocator<B>>,
is_allocating: core::cell::Cell<bool>,
pub(crate) os_key: core::cell::Cell<u32>,
#[cfg(nightly_tls_active)]
pub(crate) exit_armed: core::cell::Cell<bool>,
}
impl<B: HasSegmentPool> Default for LocalAllocatorSlot<B> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<B: HasSegmentPool> LocalAllocatorSlot<B> {
const SLOT_ALLOCATOR_AT_OFFSET_ZERO: () = assert!(
core::mem::offset_of!(Self, allocator) == 0,
"LocalAllocatorSlot::allocator must stay at offset 0: the TLS cache holds one pointer that is both the slot address and the segment owner token"
);
#[inline(always)]
unsafe fn gate<'a>(ptr: *mut core::ffi::c_void) -> &'a core::cell::Cell<bool> {
const { Self::SLOT_ALLOCATOR_AT_OFFSET_ZERO };
let slot = ptr.cast::<Self>();
let gate = unsafe { &raw const (*slot).is_allocating };
unsafe { &*gate }
}
#[inline(always)]
pub unsafe fn is_allocating(ptr: *mut core::ffi::c_void) -> bool {
unsafe { Self::gate(ptr) }.get()
}
#[inline(always)]
pub unsafe fn set_allocating(ptr: *mut core::ffi::c_void, value: bool) {
unsafe { Self::gate(ptr) }.set(value);
}
#[inline(always)]
pub unsafe fn allocator_mut<'a>(ptr: *mut core::ffi::c_void) -> &'a mut ThreadAllocator<B> {
const { Self::SLOT_ALLOCATOR_AT_OFFSET_ZERO };
unsafe { &mut *ptr.cast::<ThreadAllocator<B>>() }
}
pub const fn new() -> Self {
Self {
allocator: core::cell::UnsafeCell::new(ThreadAllocator::new()),
is_allocating: core::cell::Cell::new(false),
os_key: core::cell::Cell::new(u32::MAX),
#[cfg(nightly_tls_active)]
exit_armed: core::cell::Cell::new(false),
}
}
#[inline(always)]
pub unsafe fn with_allocator<R>(
ptr: *mut core::ffi::c_void,
f: impl FnOnce(&mut ThreadAllocator<B>) -> R,
) -> Option<R> {
if unsafe { Self::is_allocating(ptr) } {
return None;
}
unsafe { Self::set_allocating(ptr, true) };
let alloc = unsafe { Self::allocator_mut(ptr) };
let result = f(alloc);
unsafe { Self::set_allocating(ptr, false) };
Some(result)
}
#[inline(always)]
pub unsafe fn with_allocator_unguarded<R>(
ptr: *mut core::ffi::c_void,
f: impl FnOnce(&mut ThreadAllocator<B>) -> R,
) -> Option<R> {
if unsafe { Self::is_allocating(ptr) } {
return None;
}
let alloc = unsafe { Self::allocator_mut(ptr) };
Some(f(alloc))
}
#[inline(always)]
pub fn allocator_ptr(&self) -> *mut core::ffi::c_void {
const { Self::SLOT_ALLOCATOR_AT_OFFSET_ZERO };
(self as *const Self as *mut Self).cast()
}
#[cfg(nightly_tls_active)]
#[inline(always)]
pub fn cache_ptr(&self) -> *mut ThreadAllocator<B> {
self.allocator.get()
}
}
impl<B: HasSegmentPool> Drop for LocalAllocatorSlot<B> {
#[inline]
fn drop(&mut self) {
let key = self.os_key.get();
if key != u32::MAX {
#[cfg(all(windows, target_arch = "x86_64", not(miri)))]
unsafe {
mnemosyne_core::os_tls::write_teb_slot(key, core::ptr::null_mut());
}
mnemosyne_core::os_tls::write_value(key, core::ptr::null_mut());
}
}
}
#[cfg(nightly_tls_active)]
#[doc(hidden)]
pub struct ThreadExitReclaim<B: HasSegmentPool> {
cache: core::cell::Cell<*mut ThreadAllocator<B>>,
}
#[cfg(nightly_tls_active)]
impl<B: HasSegmentPool> ThreadExitReclaim<B> {
pub const fn new() -> Self {
Self {
cache: core::cell::Cell::new(core::ptr::null_mut()),
}
}
#[inline(always)]
pub fn bind(&self, cache: *mut ThreadAllocator<B>) {
self.cache.set(cache);
}
}
#[cfg(nightly_tls_active)]
impl<B: HasSegmentPool> Default for ThreadExitReclaim<B> {
fn default() -> Self {
Self::new()
}
}
#[cfg(nightly_tls_active)]
impl<B: HasSegmentPool> Drop for ThreadExitReclaim<B> {
fn drop(&mut self) {
let cache = self.cache.get();
if !cache.is_null() {
unsafe {
(*cache).reclaim_owned_segments();
}
}
}
}
#[cfg(nightly_tls_active)]
#[inline(always)]
pub fn arm_thread_exit<B: HasSegmentPool>(
slot: &LocalAllocatorSlot<B>,
guard: &'static std::thread::LocalKey<ThreadExitReclaim<B>>,
) {
if !slot.exit_armed.get() {
cold_arm_thread_exit(slot, guard);
}
}
#[cfg(nightly_tls_active)]
#[cold]
#[inline(never)]
fn cold_arm_thread_exit<B: HasSegmentPool>(
slot: &LocalAllocatorSlot<B>,
guard: &'static std::thread::LocalKey<ThreadExitReclaim<B>>,
) {
slot.exit_armed.set(true);
guard.with(|sentinel| sentinel.bind(slot.cache_ptr()));
}