use crate::backend::{Mutex, TlsCallback};
use crate::reserve::{self, ReserveHeader, ReserveType, RESERVE_ALIGN};
use crate::Backend;
use crate::__internal::{UsizeExt, SMALL_CLASSES, SMALL_MAX};
use core::alloc::Layout;
use core::cell::{Cell, UnsafeCell};
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use core::{mem, ptr};
mod large;
mod small;
static ARENAS: [AtomicPtr<()>; 16] = [
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
];
struct ArenaCell(Cell<*mut ()>);
impl ArenaCell {
const fn new() -> Self {
Self(Cell::new(ptr::null_mut()))
}
#[inline]
fn get<B: Backend>(&self) -> *mut Arena<B> {
self.0.get() as *mut Arena<B>
}
#[inline]
fn set<B: Backend>(&self, ptr: *mut Arena<B>) {
self.0.set(ptr as *mut ())
}
}
#[inline]
fn tls_arena<B: Backend, T, F>(with: F) -> T
where
F: FnOnce(&ArenaCell) -> T,
{
#[thread_local]
static TLS_ARENA: ArenaCell = ArenaCell::new();
#[thread_local]
static CALLBACK: TlsCallback = TlsCallback::new();
#[cold]
fn slow<B: Backend>() {
CALLBACK.func.set(Some(|| {
let arena = TLS_ARENA.get::<B>();
if !arena.is_null() {
for x in ARENAS.iter() {
if x.compare_exchange(
ptr::null_mut(),
arena as *mut (),
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
return;
}
}
let guard = unsafe { (*arena).lock.lock() };
unsafe { Arena::release(arena, guard) };
}
}));
unsafe { B::tls_attach(&CALLBACK) };
}
if CALLBACK.func.get().is_none() {
slow::<B>();
}
with(&TLS_ARENA)
}
pub const MAX: usize = 256 * 1024;
#[repr(C)]
struct Page {
r: ReserveHeader,
class: isize,
}
#[repr(C)]
struct Arena<B: Backend> {
page: small::Page,
rc: UnsafeCell<usize>,
vacant: [UnsafeCell<*const small::Page>; SMALL_CLASSES.len()],
lock: B::Mutex,
}
impl<B: Backend> Arena<B> {
unsafe fn release(this: *const Self, guard: <B::Mutex as Mutex>::Guard<'_>) {
*(*this).rc.get() -= 1;
if *(*this).rc.get() == 0 {
drop(guard);
let this = this as *mut Self;
ptr::drop_in_place(ptr::addr_of_mut!((*this).lock));
reserve::delete::<B>(ptr::addr_of!((*this).page.p.r) as _);
}
}
}
impl<B: Backend> Arena<B> {
#[inline]
unsafe fn commited(&self) -> *mut [usize] {
let (_, offset) = Self::layout();
ptr::slice_from_raw_parts_mut(
(self as *const Self as *const u8).add(offset) as *mut usize,
Self::commited_len(),
)
}
unsafe fn alloc(&self, layout: Layout, zeroed: bool) -> *mut u8 {
let guard = self.lock.lock();
let rounded_size = layout.size().align_up(layout.align());
let x = if rounded_size > SMALL_MAX {
large::alloc(self, layout)
} else {
small::alloc(self, rounded_size, zeroed)
};
drop(guard);
x
}
#[inline]
fn commited_len() -> usize {
RESERVE_ALIGN / B::pagesize() / mem::size_of::<usize>() / 8
}
#[inline]
fn layout() -> (Layout, usize) {
Layout::new::<Self>()
.extend(Layout::array::<usize>(Self::commited_len()).unwrap())
.unwrap()
}
fn new() -> *mut Self {
for x in ARENAS.iter() {
let x = x.swap(ptr::null_mut(), Ordering::Acquire);
if !x.is_null() {
return x as _;
}
}
let (_, ptr) = reserve::new::<B>(RESERVE_ALIGN, ReserveType::SubHuge);
let ptr = ptr as *mut Self;
unsafe {
(*ptr).page.rc = AtomicUsize::new(1);
let mut start = (ptr as *mut u8).add(Self::layout().0.size());
start = start.add(start.align_offset(SMALL_CLASSES[0]));
(*ptr).page.vacancy = AtomicUsize::new(
(B::pagesize() - (start as usize - ptr as usize)) / SMALL_CLASSES[0],
);
(*ptr).page.zeroed = UnsafeCell::new(start);
(*ptr).rc = UnsafeCell::new(1);
B::Mutex::new(ptr::addr_of_mut!((*ptr).lock));
(*ptr).vacant[0] = UnsafeCell::new(&(*ptr).page);
(*(*ptr).commited())[0] = 1;
}
ptr
}
}
pub(super) unsafe fn alloc<B: Backend>(layout: Layout, zeroed: bool) -> *mut u8 {
tls_arena::<B, _, _>(|tls_arena| {
let x = tls_arena.get::<B>();
if !x.is_null() {
let ptr = (*x).alloc(layout, zeroed);
if !ptr.is_null() {
return ptr;
}
}
let arena = Arena::<B>::new();
if !arena.is_null() {
tls_arena.set(arena);
let ptr = (*arena).alloc(layout, zeroed);
if !ptr.is_null() {
return ptr;
}
}
ptr::null_mut()
})
}
pub(super) unsafe fn realloc_in_place<B: Backend>(
arena: *const ReserveHeader,
ptr: *mut u8,
layout: Layout,
) -> bool {
let arena = arena as *const Arena<B>;
let page = (ptr as usize - 1).align_down(B::pagesize()) as *mut Page;
let class = (*page).class;
let rounded_size = layout.size().align_up(layout.align());
if class == -1 {
if rounded_size > SMALL_MAX {
return large::realloc_in_place(page, &*arena, layout);
}
} else if rounded_size <= SMALL_MAX {
return small::realloc_in_place(class, rounded_size);
}
false
}
pub(super) unsafe fn dealloc<B: Backend>(arena: *const ReserveHeader, ptr: *mut u8) {
let arena = arena as *const Arena<B>;
let page = (ptr as usize - 1).align_down(B::pagesize()) as *mut Page;
let class = (*page).class;
if class == -1 {
large::dealloc(page, arena)
} else {
small::dealloc(page as _, arena, ptr, class)
}
}
pub(super) unsafe fn size<B: Backend>(ptr: *mut u8) -> usize {
let page = (ptr as usize - 1).align_down(B::pagesize()) as *mut Page;
let class = (*page).class;
if class == -1 {
large::size(page, ptr)
} else {
SMALL_CLASSES[class as usize]
}
}