use std::{ptr, slice};
use crate::{
BorrowedEntry, EbrGuard,
config::Config,
control::PageControl,
key::{Key, PageNo},
loom::{
alloc,
sync::atomic::{AtomicPtr, AtomicU32, Ordering},
},
slot::Slot,
};
pub(crate) struct Page<T, C> {
start_slot_id: u32,
capacity: u32,
slots: AtomicPtr<Slot<T, C>>,
free_head: AtomicU32, }
impl<T: 'static, C: Config> Page<T, C> {
pub(crate) fn new(page_no: PageNo<C>) -> Self {
Self {
start_slot_id: page_no.start_slot_id(),
capacity: page_no.capacity(),
slots: AtomicPtr::new(ptr::null_mut()),
free_head: AtomicU32::new(0),
}
}
pub(crate) unsafe fn add_free(&self, slot: &Slot<T, C>) {
let slots_ptr = self.slots.load(Ordering::Acquire);
debug_assert!(!slots_ptr.is_null());
let mut free_head = self.free_head.load(Ordering::Acquire);
loop {
slot.set_next_free(free_head);
let slot_index = unsafe { ptr::from_ref(slot).offset_from(slots_ptr) };
debug_assert!((0isize..(1 << 31)).contains(&slot_index));
#[allow(clippy::cast_sign_loss)]
let slot_index = slot_index as u32;
debug_assert!(slot_index < self.capacity);
if let Err(new_free_head) = self.free_head.compare_exchange(
free_head,
slot_index,
Ordering::AcqRel,
Ordering::Acquire,
) {
free_head = new_free_head;
} else {
break;
}
}
}
pub(crate) fn reserve(&self, page_control: &PageControl) -> Option<(Key, &Slot<T, C>)> {
let slots_ptr =
page_control.get_or_lock(|| self.slots.load(Ordering::Acquire), || self.allocate());
let mut free_head = self.free_head.load(Ordering::Acquire);
let (slot_index, slot) = loop {
if free_head == u32::MAX {
return None;
}
debug_assert!(free_head < self.capacity);
let slot = unsafe { &*slots_ptr.add(free_head as usize) };
let next_free_head = slot.next_free();
debug_assert!(next_free_head == u32::MAX || next_free_head < self.capacity);
if let Err(new_free_head) = self.free_head.compare_exchange(
free_head,
next_free_head,
Ordering::AcqRel,
Ordering::Acquire,
) {
free_head = new_free_head;
} else {
break (free_head, slot);
}
};
let key = unsafe { Key::new_unchecked(self.start_slot_id + slot_index, slot.generation()) };
Some((key, slot))
}
pub(crate) fn remove(&self, key: Key) -> bool {
let slots_ptr = self.slots.load(Ordering::Acquire);
if slots_ptr.is_null() {
return false;
}
let slot_id = key.slot_id::<C>();
let slot_index = slot_id - self.start_slot_id;
debug_assert!(slot_index < self.capacity);
let slot = unsafe { &*slots_ptr.add(slot_index as usize) };
if !slot.uninit(key) {
return false;
}
unsafe { self.add_free(slot) };
true
}
pub(crate) fn get<'g>(&self, key: Key, guard: &'g EbrGuard) -> Option<BorrowedEntry<'g, T>> {
let slots_ptr = self.slots.load(Ordering::Acquire);
if slots_ptr.is_null() {
return None;
}
let slot_index = key.slot_id::<C>() - self.start_slot_id;
debug_assert!(slot_index < self.capacity);
let slot = unsafe { &*slots_ptr.add(slot_index as usize) };
BorrowedEntry::new(slot.get(key, guard))
}
#[allow(clippy::iter_not_returning_iterator)]
pub(crate) fn iter<'g>(&self, guard: &'g EbrGuard) -> Option<Iter<'g, '_, T, C>> {
let slots_ptr = self.slots.load(Ordering::Acquire);
if slots_ptr.is_null() {
return None;
}
let slots = unsafe { slice::from_raw_parts(slots_ptr, self.capacity as usize) };
Some(Iter {
slots,
prev_slot_id: self.start_slot_id - 1,
guard,
})
}
#[cold]
#[inline(never)]
fn allocate(&self) {
debug_assert!(self.slots.load(Ordering::Relaxed).is_null());
let layout =
alloc::Layout::array::<Slot<T, C>>(self.capacity as usize).expect("invalid layout");
assert_ne!(layout.size(), 0);
let slots_ptr = unsafe { alloc::alloc(layout) };
assert!(!slots_ptr.is_null(), "failed to allocate memory");
#[allow(clippy::cast_ptr_alignment)] let slots_ptr = slots_ptr.cast::<Slot<T, C>>();
for slot_index in 0..self.capacity {
let slot_ptr = unsafe { slots_ptr.add(slot_index as usize) };
let next_free = if slot_index + 1 < self.capacity {
slot_index + 1
} else {
u32::MAX
};
let slot = Slot::new(next_free);
unsafe { slot_ptr.write(slot) };
}
debug_assert!(self.slots.load(Ordering::Relaxed).is_null());
self.slots.store(slots_ptr, Ordering::Release);
}
}
impl<T, C> Drop for Page<T, C> {
fn drop(&mut self) {
let slots_ptr = self.slots.load(Ordering::Acquire);
if slots_ptr.is_null() {
return;
}
for slot_index in 0..self.capacity {
let slot_ptr = unsafe { slots_ptr.add(slot_index as usize) };
unsafe { slot_ptr.drop_in_place() };
}
let layout =
alloc::Layout::array::<Slot<T, C>>(self.capacity as usize).expect("invalid layout");
unsafe { alloc::dealloc(slots_ptr.cast::<u8>(), layout) };
}
}
#[must_use]
pub(crate) struct Iter<'g, 's, T, C> {
slots: &'s [Slot<T, C>],
prev_slot_id: u32,
guard: &'g EbrGuard,
}
impl<'g, T: 'static, C: Config> Iterator for Iter<'g, '_, T, C> {
type Item = (Key, BorrowedEntry<'g, T>);
fn next(&mut self) -> Option<Self::Item> {
while let Some((slot, rest)) = self.slots.split_first() {
self.prev_slot_id += 1;
self.slots = rest;
let key = unsafe { Key::new_unchecked(self.prev_slot_id, slot.generation()) };
let ptr = slot.get(key, self.guard);
if let Some(entry) = BorrowedEntry::new(ptr) {
return Some((key, entry));
}
}
None
}
}
impl<T: 'static, C: Config> std::iter::FusedIterator for Iter<'_, '_, T, C> {}