#![expect(
clippy::similar_names,
reason = "short field-name aliases like p_dc/p_rc match the layout diagram at the top of the module"
)]
use alloc::sync::Weak;
use core::alloc::Layout;
use core::cell::Cell;
use core::mem;
use core::ptr::NonNull;
use allocator_api2::alloc::Allocator;
use super::chunk_provider::ChunkProvider;
use super::constants::{CHUNK_ALIGN, LARGE, refcount_overflow_abort};
use super::drop_list::DropEntry;
#[repr(C)]
pub(crate) struct LocalChunk<A: Allocator + Clone> {
pub(crate) allocator: A,
pub(crate) provider: Weak<ChunkProvider<A>>,
pub(crate) capacity: usize,
pub(crate) refcount: Cell<usize>,
pub(crate) next: Cell<Option<NonNull<Self>>>,
pub(crate) drop_count: Cell<u16>,
pub(crate) data: [core::cell::UnsafeCell<u8>],
}
#[inline]
pub(crate) const fn header_size<A: Allocator + Clone>() -> usize {
core::mem::offset_of!(LocalChunk<A>, drop_count) + core::mem::size_of::<Cell<u16>>()
}
#[inline]
#[cfg_attr(test, mutants::skip)] pub(crate) const fn chunk_align<A: Allocator + Clone>() -> usize {
let a = core::mem::align_of::<A>();
if a > CHUNK_ALIGN { a } else { CHUNK_ALIGN }
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(crate) const fn alloc_size<A: Allocator + Clone>(total: usize) -> usize {
let user = core::mem::align_of::<A>();
let struct_align = if user > 8 { user } else { 8 };
let mask = struct_align - 1;
total.saturating_add(mask) & !mask
}
#[inline]
#[cfg_attr(test, mutants::skip)] pub(crate) const fn max_bump_extent<A: Allocator + Clone>() -> usize {
CHUNK_ALIGN - header_size::<A>()
}
impl<A: Allocator + Clone> LocalChunk<A> {
pub(crate) fn allocate(
allocator: A,
provider: Weak<ChunkProvider<A>>,
total_bytes: usize,
) -> Result<NonNull<Self>, allocator_api2::alloc::AllocError> {
let header_bytes = header_size::<A>();
if total_bytes < header_bytes {
return Err(allocator_api2::alloc::AllocError);
}
let payload = total_bytes - header_bytes;
let total = alloc_size::<A>(total_bytes);
let layout = Layout::from_size_align(total, chunk_align::<A>()).map_err(|_e| allocator_api2::alloc::AllocError)?;
let raw = allocator.allocate(layout)?;
let raw_ptr = raw.cast::<u8>();
let start_addr = raw_ptr.as_ptr() as usize;
if !super::constants::chunk_end_addr_fits_in_isize(start_addr, total) {
unsafe { allocator.deallocate(raw_ptr, layout) };
return Err(allocator_api2::alloc::AllocError);
}
let fat: *mut Self = core::ptr::slice_from_raw_parts_mut(raw_ptr.as_ptr(), payload) as *mut Self;
unsafe {
let p_alloc = core::ptr::addr_of_mut!((*fat).allocator);
core::ptr::write(p_alloc, allocator);
let p_prov = core::ptr::addr_of_mut!((*fat).provider);
core::ptr::write(p_prov, provider);
let p_cap = core::ptr::addr_of_mut!((*fat).capacity);
core::ptr::write(p_cap, payload);
let p_rc = core::ptr::addr_of_mut!((*fat).refcount);
core::ptr::write(p_rc, Cell::new(LARGE));
let p_dc = core::ptr::addr_of_mut!((*fat).drop_count);
core::ptr::write(p_dc, Cell::new(0));
let p_next = core::ptr::addr_of_mut!((*fat).next);
core::ptr::write(p_next, Cell::new(None));
}
Ok(unsafe { NonNull::new_unchecked(fat) })
}
#[inline]
pub(crate) unsafe fn data_ptr(chunk: NonNull<Self>) -> NonNull<u8> {
let base: *mut u8 = chunk.as_ptr().cast::<u8>();
let p = unsafe { base.add(header_size::<A>()) };
unsafe { NonNull::new_unchecked(p) }
}
#[inline]
pub(crate) fn data(&self) -> NonNull<u8> {
unsafe { Self::data_ptr(NonNull::from(self)) }
}
#[inline]
pub(crate) fn inc_ref(&self) {
let rc = &self.refcount;
let prev = rc.get();
debug_assert!(prev > 0, "inc_ref on a dead chunk");
check_local_refcount(prev);
rc.set(prev + 1);
}
#[inline]
pub(crate) fn drop_entries(&self) -> &[DropEntry] {
let count = self.drop_count.get() as usize;
let top = self.capacity;
let base = top - count * mem::size_of::<DropEntry>();
#[expect(
clippy::cast_ptr_alignment,
reason = "chunk payloads are CHUNK_ALIGN aligned; `data + base` is naturally `DropEntry`-aligned by construction"
)]
unsafe {
let data = Self::data_ptr(NonNull::from(self)).as_ptr();
let ptr = data.add(base).cast::<DropEntry>();
core::slice::from_raw_parts(ptr, count)
}
}
#[inline]
pub(crate) unsafe fn dec_ref(chunk: NonNull<Self>) {
let prev = unsafe { (*chunk.as_ptr()).refcount.get() };
debug_assert!(prev > 0, "dec_ref on a dead chunk");
unsafe { (*chunk.as_ptr()).refcount.set(prev - 1) };
if prev == 1 {
unsafe { Self::route_release(chunk) };
}
}
pub(crate) unsafe fn replay_drops(chunk: NonNull<Self>) {
let count = unsafe { (*chunk.as_ptr()).drop_count.get() } as usize;
if count == 0 {
return;
}
let capacity = unsafe { (*chunk.as_ptr()).capacity };
let data = unsafe { Self::data_ptr(chunk) }.as_ptr();
unsafe { (*chunk.as_ptr()).drop_count.set(0) };
unsafe { crate::internal::drop_list::replay_drop_entries(data, capacity, count) };
}
pub(crate) unsafe fn free_backing(chunk: NonNull<Self>) {
let (capacity, allocator) = unsafe {
let capacity = (*chunk.as_ptr()).capacity;
let allocator: A = core::ptr::read(core::ptr::addr_of!((*chunk.as_ptr()).allocator));
core::ptr::drop_in_place(core::ptr::addr_of_mut!((*chunk.as_ptr()).provider));
core::ptr::drop_in_place(core::ptr::addr_of_mut!((*chunk.as_ptr()).refcount));
core::ptr::drop_in_place(core::ptr::addr_of_mut!((*chunk.as_ptr()).drop_count));
core::ptr::drop_in_place(core::ptr::addr_of_mut!((*chunk.as_ptr()).next));
(capacity, allocator)
};
let total_bytes = alloc_size::<A>(header_size::<A>() + capacity);
let layout =
Layout::from_size_align(total_bytes, chunk_align::<A>()).expect("layout was valid at allocation time, must remain valid here");
let raw_ptr = chunk.as_ptr().cast::<u8>();
unsafe {
allocator.deallocate(NonNull::new_unchecked(raw_ptr), layout);
}
drop(allocator);
}
pub(crate) unsafe fn destroy(chunk: NonNull<Self>) {
unsafe { Self::replay_drops(chunk) };
unsafe { Self::free_backing(chunk) };
}
pub(crate) unsafe fn revive_for_reuse(&self) {
self.refcount.set(LARGE);
self.drop_count.set(0);
self.next.set(None);
}
pub(crate) unsafe fn reconcile_swap_out(chunk: NonNull<Self>, rcs_issued: usize, pinned: bool) {
let pin = usize::from(pinned);
let to_subtract = LARGE - rcs_issued - pin;
let prev = unsafe { (*chunk.as_ptr()).refcount.get() };
debug_assert!(prev >= to_subtract, "local swap-out reconcile underflow");
unsafe { (*chunk.as_ptr()).refcount.set(prev - to_subtract) };
if prev == to_subtract {
unsafe { Self::route_release(chunk) };
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
unsafe fn route_release(chunk: NonNull<Self>) {
let provider_weak = unsafe { alloc::sync::Weak::clone(&(*chunk.as_ptr()).provider) };
if let Some(provider) = provider_weak.upgrade() {
unsafe { provider.release_local(chunk) };
} else {
unsafe { Self::destroy(chunk) };
}
}
}
#[inline(always)]
#[expect(
clippy::inline_always,
reason = "must inline at every inc_ref site to avoid a per-call function-call overhead; see PERF.md"
)]
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)] fn check_local_refcount(prev: usize) {
if prev >= LARGE.saturating_add(LARGE) {
refcount_overflow_abort();
}
}
#[cfg(test)]
mod tests {
use allocator_api2::alloc::Global;
use super::*;
#[test]
fn header_size_matches_struct_layout() {
let expected = core::mem::offset_of!(LocalChunk<Global>, drop_count) + core::mem::size_of::<Cell<u16>>();
assert_eq!(header_size::<Global>(), expected);
assert!(header_size::<Global>() > 0);
}
}