use core::alloc::Layout;
use core::cell::RefCell;
use core::mem::size_of;
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use super::super::interleaving::Interleaving;
use super::super::node::latch::Version;
use super::super::node::page::LatchedNodeHeader;
use super::super::node_stack::MAX_DEPTH;
use crate::allocator::{AllocError, Allocator};
struct FreeList {
locked: AtomicBool,
top: AtomicPtr<u8>,
}
pub(super) struct FreeLists {
leaves: FreeList,
internals: FreeList,
}
impl FreeList {
const LINK_OFFSET: usize = size_of::<LatchedNodeHeader>();
const fn new() -> Self {
Self {
locked: AtomicBool::new(false),
top: AtomicPtr::new(ptr::null_mut()),
}
}
fn with_locked<R>(&self, f: impl FnOnce() -> R) -> R {
loop {
Interleaving::switch_point();
if self
.locked
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
break;
}
Interleaving::wait(1);
}
let result = f();
Interleaving::switch_point();
self.locked.store(false, Ordering::Release);
result
}
fn link(page: NonNull<u8>) -> *mut *mut u8 {
unsafe { page.byte_add(Self::LINK_OFFSET).cast::<*mut u8>().as_ptr() }
}
fn push(&self, page: NonNull<u8>) {
self.with_locked(|| {
unsafe { Self::link(page).write(self.top.load(Ordering::Relaxed)) };
self.top.store(page.as_ptr(), Ordering::Relaxed);
});
}
fn pop(&self) -> Option<NonNull<u8>> {
Interleaving::switch_point();
if self.top.load(Ordering::Relaxed).is_null() {
return None;
}
self.with_locked(|| {
let page = NonNull::new(self.top.load(Ordering::Relaxed))?;
self.top
.store(unsafe { Self::link(page).read() }, Ordering::Relaxed);
Some(page)
})
}
}
impl FreeLists {
pub(super) const fn new() -> Self {
Self {
leaves: FreeList::new(),
internals: FreeList::new(),
}
}
pub(super) fn deallocate_all(
&mut self,
alloc: &impl Allocator,
leaf: Layout,
internal: Layout,
) {
for (list, layout) in [(&self.leaves, leaf), (&self.internals, internal)] {
while let Some(page) = list.pop() {
unsafe { alloc.deallocate(page, layout) };
}
}
}
}
pub(super) struct NodePages<'t, A> {
free: &'t FreeLists,
alloc: &'t A,
leaf: Layout,
internal: Layout,
taken: RefCell<TakenPages>,
}
struct TakenPages {
allocated: [Option<(NonNull<u8>, Version)>; MAX_DEPTH + 2],
allocated_len: usize,
freed: [Option<(NonNull<u8>, bool)>; MAX_DEPTH + 2],
freed_len: usize,
}
impl<'t, A: Allocator> NodePages<'t, A> {
pub(super) fn new(free: &'t FreeLists, alloc: &'t A, leaf: Layout, internal: Layout) -> Self {
debug_assert_ne!(leaf, internal, "a leaf's page tells its kind by its layout");
Self {
free,
alloc,
leaf,
internal,
taken: RefCell::new(TakenPages {
allocated: [None; MAX_DEPTH + 2],
allocated_len: 0,
freed: [None; MAX_DEPTH + 2],
freed_len: 0,
}),
}
}
fn list(&self, layout: Layout) -> Option<&'t FreeList> {
if layout == self.leaf {
Some(&self.free.leaves)
} else if layout == self.internal {
Some(&self.free.internals)
} else {
None
}
}
fn allocate_fresh(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
let page = self.alloc.allocate(layout)?;
unsafe {
if layout == self.leaf {
LatchedNodeHeader::initialize_latch(page);
} else {
page.write_bytes(0, layout.size());
}
}
Ok(page)
}
pub(super) fn has_freed(&self, page: NonNull<u8>) -> bool {
let taken = self.taken.borrow();
taken.freed[..taken.freed_len]
.iter()
.flatten()
.any(|&(freed, _)| freed == page)
}
}
unsafe impl<A: Allocator> Allocator for NodePages<'_, A> {
fn allocate(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
let Some(list) = self.list(layout) else {
return self.allocate_fresh(layout);
};
let page = match list.pop() {
Some(page) => page,
None => self.allocate_fresh(layout)?,
};
let version = unsafe { LatchedNodeHeader::latch_of(page) }.lock_exclusive();
let mut taken = self.taken.borrow_mut();
let len = taken.allocated_len;
taken.allocated[len] = Some((page, version));
taken.allocated_len += 1;
Ok(page)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
match self.list(layout) {
Some(_) => {
let mut taken = self.taken.borrow_mut();
let len = taken.freed_len;
taken.freed[len] = Some((ptr, layout == self.leaf));
taken.freed_len += 1;
}
None => unsafe { self.alloc.deallocate(ptr, layout) },
}
}
}
impl<A> Drop for NodePages<'_, A> {
fn drop(&mut self) {
let taken = self.taken.get_mut();
for (page, version) in taken.allocated[..taken.allocated_len].iter().flatten() {
unsafe { LatchedNodeHeader::latch_of(*page) }.unlock_exclusive(*version);
}
for &(page, is_leaf) in taken.freed[..taken.freed_len].iter().flatten() {
if is_leaf {
self.free.leaves.push(page);
} else {
self.free.internals.push(page);
}
}
}
}