use crate::loom_shim::{AtomicPtr, Ordering};
use crate::types::{Block, Segment};
use core::ptr::NonNull;
#[cfg(target_pointer_width = "64")]
use super::wide::PackedHead as SelectedHead;
#[cfg(not(target_pointer_width = "64"))]
use super::narrow::BareHead as SelectedHead;
pub(crate) trait HeadCodec: 'static {
const COUNT_REQUIRES_WALK: bool;
fn load(head: &AtomicPtr<Block>, order: Ordering) -> *mut Block;
fn cas(
head: &AtomicPtr<Block>,
current: *mut Block,
next: *mut Block,
success: Ordering,
failure: Ordering,
) -> Result<*mut Block, *mut Block>;
fn swap_null(head: &AtomicPtr<Block>, order: Ordering) -> *mut Block;
fn addr(raw: *mut Block) -> *mut Block;
fn assert_packable(addr: *mut Block);
fn pack(addr: *mut Block, current: *mut Block) -> *mut Block;
fn validate(raw: *mut Block);
fn count_of(raw: *mut Block, walked: Option<usize>) -> usize;
}
pub struct AtomicFreeList {
pub(crate) head: AtomicPtr<crate::types::Block>,
}
impl Default for AtomicFreeList {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl AtomicFreeList {
#[cfg(not(loom))]
pub const fn new() -> Self {
Self {
head: AtomicPtr::new(core::ptr::null_mut()),
}
}
#[cfg(loom)]
pub fn new() -> Self {
Self {
head: AtomicPtr::new(core::ptr::null_mut()),
}
}
#[inline]
pub fn push<P: crate::policy::AllocPolicy>(&self, block: NonNull<Block>) {
self.push_dynamic(block, P::ENABLE_FREE_LIST_ENCRYPTION);
}
#[inline]
pub fn push_dynamic(&self, block: NonNull<Block>, encrypted: bool) {
self.push_dynamic_with::<SelectedHead>(block, encrypted);
}
#[cfg(test)]
#[inline]
pub(crate) fn push_raw(&self, block: NonNull<Block>) {
self.push_raw_with::<SelectedHead>(block);
}
#[inline]
pub fn pop_all(&self, encrypted: bool, cookie: usize) -> Option<(NonNull<Block>, usize)> {
self.drain::<SelectedHead, true>(encrypted, cookie)
}
#[inline]
pub(crate) fn pop_all_raw(&self) -> Option<(NonNull<Block>, usize)> {
self.drain::<SelectedHead, false>(false, 0)
}
#[inline]
pub fn is_empty(&self) -> bool {
SelectedHead::addr(SelectedHead::load(&self.head, Ordering::Relaxed)).is_null()
}
#[inline]
fn assert_not_in_queue<C: HeadCodec>(&self, block_ptr: *mut Block) {
let head_ptr = C::addr(C::load(&self.head, Ordering::Relaxed));
if !head_ptr.is_null() && head_ptr == C::addr(block_ptr) {
crate::abort::abort_on_corruption("Double free detected in AtomicFreeList");
}
}
#[inline]
fn push_loop<C: HeadCodec>(&self, block_ptr: *mut Block, mut set_next: impl FnMut(*mut Block)) {
let mut current = C::load(&self.head, Ordering::Relaxed);
loop {
let current_ptr = C::addr(current);
if current_ptr == block_ptr {
crate::abort::abort_on_corruption("Double free detected in AtomicFreeList");
}
set_next(current_ptr);
let next = C::pack(block_ptr, current);
match C::cas(
&self.head,
current,
next,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
}
#[inline]
fn push_dynamic_with<C: HeadCodec>(&self, block: NonNull<Block>, encrypted: bool) {
let block_ptr = block.as_ptr();
let (segment, _) = unsafe { crate::types::locate_segment(block_ptr.cast::<u8>()) };
if !unsafe { Segment::free_list_mode_matches(segment.cast_const(), encrypted) } {
crate::abort::abort_on_corruption(
"free-list mode mismatch: AtomicFreeList push path does not match the segment",
);
}
if !encrypted {
self.push_raw_with::<C>(block);
return;
}
C::assert_packable(block_ptr);
let cookie = unsafe {
let (segment, page_index) = crate::types::locate_segment(block_ptr.cast::<u8>());
Segment::cookie_for_dynamic(segment.cast_const(), encrypted, page_index)
};
self.assert_not_in_queue::<C>(block_ptr);
self.push_loop::<C>(block_ptr, |current_ptr| {
unsafe {
(*block_ptr).set_next_dynamic(NonNull::new(current_ptr), encrypted, cookie);
}
});
}
#[inline]
fn push_raw_with<C: HeadCodec>(&self, block: NonNull<Block>) {
let block_ptr = block.as_ptr();
let (segment, _) = unsafe { crate::types::locate_segment(block_ptr.cast::<u8>()) };
if unsafe { Segment::free_list_encrypted(segment.cast_const()) } {
crate::abort::abort_on_corruption(
"raw AtomicFreeList push used while segment free-list links are encrypted",
);
}
C::assert_packable(block_ptr);
self.assert_not_in_queue::<C>(block_ptr);
self.push_loop::<C>(block_ptr, |current_ptr| {
unsafe {
(*block_ptr).set_next_raw(NonNull::new(current_ptr));
}
});
}
#[inline]
fn drain<C: HeadCodec, const VERIFY: bool>(
&self,
encrypted: bool,
cookie: usize,
) -> Option<(NonNull<Block>, usize)> {
let raw = C::swap_null(&self.head, Ordering::Acquire);
C::validate(raw);
let head = NonNull::new(C::addr(raw))?;
let walked = if C::COUNT_REQUIRES_WALK || VERIFY {
Some(walked_chain_len(head, encrypted, cookie))
} else {
None
};
let count = C::count_of(raw, walked);
Some((head, count))
}
}
#[inline]
fn walked_chain_len(head: NonNull<Block>, encrypted: bool, cookie: usize) -> usize {
let mut walked = 0usize;
let mut current = Some(head);
while let Some(node) = current {
walked += 1;
if walked > crate::constants::PAGE_SIZE {
crate::abort::abort_on_corruption("Cycle detected in AtomicFreeList");
}
current = unsafe {
if encrypted {
(*node.as_ptr()).get_next_dynamic(encrypted, cookie)
} else {
(*node.as_ptr()).get_next_raw()
}
};
}
walked
}