use crate::ThreadAllocator;
use mnemosyne_arena::HasSegmentPool;
#[doc(hidden)]
#[repr(C)]
pub struct LocalAllocatorSlot<B: HasSegmentPool> {
allocator: core::cell::UnsafeCell<ThreadAllocator<B>>,
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> {
pub const fn new() -> Self {
Self {
allocator: core::cell::UnsafeCell::new(ThreadAllocator::new()),
os_key: core::cell::Cell::new(u32::MAX),
#[cfg(nightly_tls_active)]
exit_armed: core::cell::Cell::new(false),
}
}
#[inline(always)]
pub fn with_allocator<R>(&self, f: impl FnOnce(&mut ThreadAllocator<B>) -> R) -> Option<R> {
let alloc = unsafe { &mut *self.allocator.get() };
if alloc.is_allocating {
return None;
}
alloc.is_allocating = true;
let result = f(alloc);
alloc.is_allocating = false;
Some(result)
}
#[inline(always)]
pub unsafe fn with_allocator_unguarded<R>(
&self,
f: impl FnOnce(&mut ThreadAllocator<B>) -> R,
) -> Option<R> {
let alloc = unsafe { &mut *self.allocator.get() };
if alloc.is_allocating {
return None;
}
Some(f(alloc))
}
#[inline(always)]
pub fn allocator_ptr(&self) -> *mut core::ffi::c_void {
self.allocator.get().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 {
crate::tls::os_helpers::set_teb_tls_slot(key, core::ptr::null_mut());
}
crate::tls::os_helpers::set_os_tls_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()));
}
pub trait LocalAllocatorSelector<B: HasSegmentPool>: HasSegmentPool {
fn with_allocator<R>(f: impl FnOnce(&mut ThreadAllocator<B>) -> R) -> Option<R>;
unsafe fn with_allocator_unguarded<R>(
f: impl FnOnce(&mut ThreadAllocator<B>) -> R,
) -> Option<R>;
fn get_allocator_ptr() -> *mut core::ffi::c_void;
fn get_allocator_ptr_raw() -> *mut core::ffi::c_void;
fn with_allocator_for_policy<P: mnemosyne_core::AllocPolicy, R>(
f: impl FnOnce(&mut ThreadAllocator<B>) -> R,
) -> Option<R>;
unsafe fn with_allocator_unguarded_for_policy<P: mnemosyne_core::AllocPolicy, R>(
f: impl FnOnce(&mut ThreadAllocator<B>) -> R,
) -> Option<R>;
fn get_allocator_ptr_for_policy<P: mnemosyne_core::AllocPolicy>() -> *mut core::ffi::c_void;
fn get_allocator_ptr_raw_for_policy<P: mnemosyne_core::AllocPolicy>() -> *mut core::ffi::c_void;
fn get_allocator_ptr_raw_for_encryption<const ENCRYPTED: bool>() -> *mut core::ffi::c_void;
}