use crate::types::Block;
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
#[cfg(target_pointer_width = "64")]
pub struct AtomicFreeList {
head: core::sync::atomic::AtomicUsize,
}
#[cfg(not(target_pointer_width = "64"))]
pub struct AtomicFreeList {
head: core::sync::atomic::AtomicPtr<Block>,
}
#[cfg(target_pointer_width = "64")]
impl AtomicFreeList {
const PACKED_PTR_BITS: u32 = 48;
const PTR_MASK: usize = (1usize << Self::PACKED_PTR_BITS) - 1;
const COUNT_WRAP_MASK: usize = (1usize << (usize::BITS - Self::PACKED_PTR_BITS)) - 1;
pub const fn new() -> Self {
Self {
head: core::sync::atomic::AtomicUsize::new(0),
}
}
#[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) {
let block_ptr = block.as_ptr();
let block_addr = block_ptr.expose_provenance();
if (block_addr & !Self::PTR_MASK) != 0 {
crate::abort::abort_on_corruption("Block address does not fit in 48 bits");
}
let cookie = unsafe {
let (segment, page_index) = crate::types::locate_segment(block_ptr.cast::<u8>());
(*segment.cast_const()).cookie_for_dynamic(encrypted, page_index)
};
let mut current = self.head.load(Ordering::Relaxed);
loop {
let current_addr = current & Self::PTR_MASK;
if block_addr == current_addr {
crate::abort::abort_on_corruption("Double free detected in AtomicFreeList");
}
let current_ptr = core::ptr::with_exposed_provenance_mut::<Block>(current_addr);
let next_count = ((current >> Self::PACKED_PTR_BITS) + 1) & Self::COUNT_WRAP_MASK;
unsafe {
(*block_ptr).set_next_dynamic(NonNull::new(current_ptr), encrypted, cookie);
}
let next_val = (next_count << Self::PACKED_PTR_BITS) | block_addr;
match self.head.compare_exchange_weak(
current,
next_val,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
}
#[inline]
pub fn pop_all(&self, _encrypted: bool, _cookie: usize) -> Option<(NonNull<Block>, usize)> {
let val = self.head.swap(0, Ordering::Acquire);
let addr = val & Self::PTR_MASK;
let count = val >> Self::PACKED_PTR_BITS;
let ptr = core::ptr::with_exposed_provenance_mut::<Block>(addr);
NonNull::new(ptr).map(|head| (head, count))
}
#[inline]
pub fn is_empty(&self) -> bool {
(self.head.load(Ordering::Relaxed) & Self::PTR_MASK) == 0
}
}
#[cfg(not(target_pointer_width = "64"))]
impl AtomicFreeList {
pub const fn new() -> Self {
Self {
head: core::sync::atomic::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) {
let block_ptr = block.as_ptr();
let block_addr = block_ptr as usize;
let cookie = if encrypted {
let segment_addr = block_addr & !(crate::constants::SEGMENT_SIZE - 1);
let page_index =
(block_addr & (crate::constants::SEGMENT_SIZE - 1)) >> crate::constants::PAGE_SHIFT;
unsafe { (*(segment_addr as *const crate::types::Segment)).keys[page_index] }
} else {
0
};
let mut current = self.head.load(Ordering::Relaxed);
loop {
if block_ptr == current {
crate::abort::abort_on_corruption("Double free detected in AtomicFreeList");
}
unsafe {
(*block_ptr).set_next_dynamic(NonNull::new(current), encrypted, cookie);
}
match self.head.compare_exchange_weak(
current,
block_ptr,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
}
#[inline]
pub fn pop_all(&self, encrypted: bool, cookie: usize) -> Option<(NonNull<Block>, usize)> {
let ptr = self.head.swap(core::ptr::null_mut(), Ordering::Acquire);
NonNull::new(ptr).map(|head| {
let mut count = 0;
let mut current = Some(head);
while let Some(node) = current {
count += 1;
if count > crate::constants::PAGE_SIZE {
crate::abort::abort_on_corruption("Cycle detected in AtomicFreeList");
}
current = unsafe { (*node.as_ptr()).get_next_dynamic(encrypted, cookie) };
}
(head, count)
})
}
#[inline]
pub fn is_empty(&self) -> bool {
self.head.load(Ordering::Relaxed).is_null()
}
}
impl Default for AtomicFreeList {
#[inline]
fn default() -> Self {
Self::new()
}
}