use const_default1::ConstDefault;
#[cfg(feature = "unstable")]
use core::fmt;
use core::{
alloc::Layout,
debug_assert, debug_assert_eq,
hint::unreachable_unchecked,
marker::PhantomData,
mem::{self, MaybeUninit},
num::NonZeroUsize,
ptr::{addr_of, NonNull},
};
use crate::{
int::BinInteger,
utils::{nonnull_slice_from_raw_parts, nonnull_slice_len, nonnull_slice_start, round_up},
};
#[doc = include_str!("tlsf-diagram.svg")]
#[derive(Debug)]
pub struct Tlsf<'pool, FLBitmap, SLBitmap, const FLLEN: usize, const SLLEN: usize> {
fl_bitmap: FLBitmap,
sl_bitmap: [SLBitmap; FLLEN],
first_free: [[Option<NonNull<FreeBlockHdr>>; SLLEN]; FLLEN],
_phantom: PhantomData<&'pool ()>,
}
unsafe impl<FLBitmap, SLBitmap, const FLLEN: usize, const SLLEN: usize> Send
for Tlsf<'_, FLBitmap, SLBitmap, FLLEN, SLLEN>
{
}
unsafe impl<FLBitmap, SLBitmap, const FLLEN: usize, const SLLEN: usize> Sync
for Tlsf<'_, FLBitmap, SLBitmap, FLLEN, SLLEN>
{
}
pub const GRANULARITY: usize = core::mem::size_of::<usize>() * 4;
const GRANULARITY_LOG2: u32 = GRANULARITY.trailing_zeros();
#[repr(C)]
#[cfg_attr(target_pointer_width = "16", repr(align(4)))]
#[cfg_attr(target_pointer_width = "32", repr(align(8)))]
#[cfg_attr(target_pointer_width = "64", repr(align(16)))]
#[derive(Debug)]
struct BlockHdr {
size: usize,
prev_phys_block: Option<NonNull<BlockHdr>>,
}
const SIZE_USED: usize = 1;
const SIZE_SENTINEL: usize = 2;
const SIZE_SIZE_MASK: usize = !((1 << GRANULARITY_LOG2) - 1);
impl BlockHdr {
#[inline]
unsafe fn next_phys_block(this: *const Self) -> NonNull<BlockHdr> {
let size = (*this).size;
debug_assert!((size & SIZE_SENTINEL) == 0, "`self` must not be a sentinel");
NonNull::new_unchecked((this as *mut u8).add(size & SIZE_SIZE_MASK)).cast()
}
}
#[repr(C)]
#[cfg_attr(target_pointer_width = "16", repr(align(8)))]
#[cfg_attr(target_pointer_width = "32", repr(align(16)))]
#[cfg_attr(target_pointer_width = "64", repr(align(32)))]
#[derive(Debug)]
struct FreeBlockHdr {
common: BlockHdr,
next_free: Option<NonNull<FreeBlockHdr>>,
prev_free: Option<NonNull<FreeBlockHdr>>,
}
#[repr(C)]
#[derive(Debug)]
struct UsedBlockHdr {
common: BlockHdr,
}
#[derive(Debug)]
#[repr(C)]
struct UsedBlockPad {
block_hdr: NonNull<UsedBlockHdr>,
}
impl UsedBlockPad {
#[inline]
fn get_for_allocation(ptr: NonNull<u8>) -> *mut Self {
ptr.cast::<Self>().as_ptr().wrapping_sub(1)
}
}
impl<FLBitmap: BinInteger, SLBitmap: BinInteger, const FLLEN: usize, const SLLEN: usize> Default
for Tlsf<'_, FLBitmap, SLBitmap, FLLEN, SLLEN>
{
fn default() -> Self {
Self::new()
}
}
impl<FLBitmap: BinInteger, SLBitmap: BinInteger, const FLLEN: usize, const SLLEN: usize>
ConstDefault for Tlsf<'_, FLBitmap, SLBitmap, FLLEN, SLLEN>
{
const DEFAULT: Self = Self::new();
}
impl<'pool, FLBitmap: BinInteger, SLBitmap: BinInteger, const FLLEN: usize, const SLLEN: usize>
Tlsf<'pool, FLBitmap, SLBitmap, FLLEN, SLLEN>
{
#[inline]
pub const fn new() -> Self {
Self {
fl_bitmap: FLBitmap::ZERO,
sl_bitmap: [SLBitmap::ZERO; FLLEN],
first_free: [[None; SLLEN]; FLLEN],
_phantom: {
let () = Self::VALID;
PhantomData
},
}
}
#[allow(dead_code)]
const FLLEN: usize = FLLEN;
#[allow(dead_code)]
const SLLEN: usize = SLLEN;
const VALID: () = {
if FLLEN == 0 {
panic!("`FLLEN` must not be zero");
}
if SLLEN == 0 {
panic!("`SLLEN` must not be zero");
}
if (FLBitmap::BITS as u128) < FLLEN as u128 {
panic!("`FLBitmap` should contain at least `FLLEN` bits");
}
if (SLBitmap::BITS as u128) < SLLEN as u128 {
panic!("`SLBitmap` should contain at least `SLLEN` bits");
}
};
const MAX_POOL_SIZE: Option<usize> = {
let shift = GRANULARITY_LOG2 + FLLEN as u32;
if shift < usize::BITS {
Some(1 << shift)
} else {
None
}
};
const SLI: u32 = if SLLEN.is_power_of_two() {
SLLEN.trailing_zeros()
} else {
panic!("`SLLEN` is not power of two")
};
#[inline]
fn map_floor(size: usize) -> Option<(usize, usize)> {
debug_assert!(size >= GRANULARITY);
debug_assert!(size % GRANULARITY == 0);
let fl = usize::BITS - GRANULARITY_LOG2 - 1 - size.leading_zeros();
let sl = size.rotate_right((fl + GRANULARITY_LOG2).wrapping_sub(Self::SLI));
debug_assert!(((sl >> Self::SLI) & 1) == 1);
if fl as usize >= FLLEN {
return None;
}
Some((fl as usize, sl & (SLLEN - 1)))
}
#[inline]
fn map_ceil(size: usize) -> Option<(usize, usize)> {
debug_assert!(size >= GRANULARITY);
debug_assert!(size % GRANULARITY == 0);
let mut fl = usize::BITS - GRANULARITY_LOG2 - 1 - size.leading_zeros();
let mut sl = size.rotate_right((fl + GRANULARITY_LOG2).wrapping_sub(Self::SLI));
debug_assert!(((sl >> Self::SLI) & 1) == 1);
sl = (sl & (SLLEN - 1)) + (sl >= (1 << (Self::SLI + 1))) as usize;
fl += (sl >> Self::SLI) as u32;
if fl as usize >= FLLEN {
return None;
}
Some((fl as usize, sl & (SLLEN - 1)))
}
const MAX_MAP_CEIL_AND_UNMAP_INPUT: usize = {
let max1 = !(usize::MAX >> (Self::SLI + 1));
if FLLEN as u32 - 1 < usize::BITS - GRANULARITY_LOG2 - 1 {
max1 >> ((usize::BITS - GRANULARITY_LOG2 - 1) - (FLLEN as u32 - 1))
} else {
max1
}
};
#[inline]
fn map_ceil_and_unmap(size: usize) -> Option<usize> {
debug_assert!(size >= GRANULARITY);
debug_assert!(size % GRANULARITY == 0);
if size > Self::MAX_MAP_CEIL_AND_UNMAP_INPUT {
return None;
}
let fl = usize::BITS - GRANULARITY_LOG2 - 1 - size.leading_zeros();
let list_min_size = if GRANULARITY_LOG2 < Self::SLI && fl < Self::SLI - GRANULARITY_LOG2 {
size
} else {
let shift = fl + GRANULARITY_LOG2 - Self::SLI;
(size + ((1 << shift) - 1)) & !((1 << shift) - 1)
};
Some(list_min_size)
}
#[cfg_attr(target_arch = "wasm32", inline(never))]
unsafe fn link_free_block(&mut self, block: NonNull<FreeBlockHdr>, size: usize) {
let (fl, sl) = Self::map_floor(size).unwrap_or_else(|| {
debug_assert!(false, "could not map size {}", size);
unreachable_unchecked()
});
let first_free = &mut self.first_free[fl][sl];
let next_free = first_free.replace(block);
*nn_field!(block, next_free) = next_free;
*nn_field!(block, prev_free) = None;
if let Some(mut next_free) = next_free {
next_free.as_mut().prev_free = Some(block);
}
self.fl_bitmap.set_bit(fl as u32);
self.sl_bitmap[fl].set_bit(sl as u32);
}
#[cfg_attr(target_arch = "wasm32", inline(never))]
unsafe fn unlink_free_block(&mut self, mut block: NonNull<FreeBlockHdr>, size: usize) {
let next_free = block.as_mut().next_free;
let prev_free = block.as_mut().prev_free;
if let Some(mut next_free) = next_free {
next_free.as_mut().prev_free = prev_free;
}
if let Some(mut prev_free) = prev_free {
prev_free.as_mut().next_free = next_free;
} else {
let (fl, sl) = Self::map_floor(size).unwrap_or_else(|| {
debug_assert!(false, "could not map size {}", size);
unreachable_unchecked()
});
let first_free = &mut self.first_free[fl][sl];
debug_assert_eq!(*first_free, Some(block));
*first_free = next_free;
if next_free.is_none() {
self.sl_bitmap[fl].clear_bit(sl as u32);
if self.sl_bitmap[fl] == SLBitmap::ZERO {
self.fl_bitmap.clear_bit(fl as u32);
}
}
}
}
pub unsafe fn insert_free_block_ptr(&mut self, block: NonNull<[u8]>) -> Option<NonZeroUsize> {
let len = nonnull_slice_len(block);
let unaligned_start = block.as_ptr() as *mut u8;
let start = round_up(unaligned_start, GRANULARITY);
let len = if let Some(x) =
len.checked_sub((start as usize).wrapping_sub(unaligned_start as usize))
{
x & !(GRANULARITY - 1)
} else {
return None;
};
let pool_len = self.insert_free_block_ptr_aligned(NonNull::new_unchecked(
core::ptr::slice_from_raw_parts_mut(start, len),
))?;
Some(NonZeroUsize::new_unchecked(
pool_len.get() + (start as usize).wrapping_sub(unaligned_start as usize),
))
}
pub(crate) unsafe fn insert_free_block_ptr_aligned(
&mut self,
block: NonNull<[u8]>,
) -> Option<NonZeroUsize> {
let start = block.as_ptr() as *mut u8;
let mut size = nonnull_slice_len(block);
let mut cursor = start;
while size >= GRANULARITY * 2 {
let chunk_size = if let Some(max_pool_size) = Self::MAX_POOL_SIZE {
size.min(max_pool_size)
} else {
size
};
debug_assert_eq!(chunk_size % GRANULARITY, 0);
let block = NonNull::new_unchecked(cursor as *mut FreeBlockHdr);
*nn_field!(block, common) = BlockHdr {
size: chunk_size - GRANULARITY,
prev_phys_block: None,
};
let sentinel_block =
BlockHdr::next_phys_block(nn_field!(block, common)).cast::<UsedBlockHdr>();
*nn_field!(sentinel_block, common) = BlockHdr {
size: GRANULARITY | SIZE_USED | SIZE_SENTINEL,
prev_phys_block: Some(block.cast()),
};
self.link_free_block(block, chunk_size - GRANULARITY);
debug_assert!(
(cursor as usize).checked_add(chunk_size).is_some() || size == chunk_size
);
size -= chunk_size;
cursor = cursor.wrapping_add(chunk_size);
}
NonZeroUsize::new((cursor as usize).wrapping_sub(start as usize))
}
pub unsafe fn append_free_block_ptr(&mut self, block: NonNull<[u8]>) -> usize {
let start = nonnull_slice_start(block);
let len = nonnull_slice_len(block) & !(GRANULARITY - 1);
if Self::MAX_POOL_SIZE.is_some() {
let block = nonnull_slice_from_raw_parts(start, len);
return self
.insert_free_block_ptr_aligned(block)
.map(NonZeroUsize::get)
.unwrap_or(0);
} else if len == 0 {
return 0;
}
let original_start = start.as_ptr();
let mut start = original_start;
let end = (start as usize).wrapping_add(len);
start = start.wrapping_sub(super::GRANULARITY);
let sentinel_block = start as *mut UsedBlockHdr;
debug_assert_eq!(
(*sentinel_block).common.size,
GRANULARITY | SIZE_USED | SIZE_SENTINEL
);
let penultimate_block = (*sentinel_block).common.prev_phys_block.unwrap_or_else(|| {
debug_assert!(false, "sentinel block has no `prev_phys_block`");
unreachable_unchecked()
});
let last_nonassimilated_block;
if (penultimate_block.as_ref().size & SIZE_USED) == 0 {
let free_block = penultimate_block.cast::<FreeBlockHdr>();
let free_block_size = free_block.as_ref().common.size;
debug_assert_eq!(
free_block_size,
free_block.as_ref().common.size & SIZE_SIZE_MASK
);
self.unlink_free_block(free_block, free_block_size);
start = free_block.as_ptr() as *mut u8;
last_nonassimilated_block = free_block.as_ref().common.prev_phys_block;
} else {
last_nonassimilated_block = Some(penultimate_block);
}
let block = nonnull_slice_from_raw_parts(
NonNull::new_unchecked(start),
end.wrapping_sub(start as usize),
);
let pool_len = self
.insert_free_block_ptr_aligned(block)
.unwrap_or_else(|| {
debug_assert!(false, "`pool_size_to_contain_allocation` is an impostor");
unreachable_unchecked()
})
.get();
let mut first_block = nonnull_slice_start(block).cast::<FreeBlockHdr>();
first_block.as_mut().common.prev_phys_block = last_nonassimilated_block;
pool_len - (original_start as usize).wrapping_sub(start as usize)
}
#[inline]
pub fn insert_free_block(&mut self, block: &'pool mut [MaybeUninit<u8>]) -> impl Send + Sync {
unsafe { self.insert_free_block_ptr(NonNull::new(block as *mut [_] as _).unwrap()) };
}
#[inline]
pub(crate) fn pool_size_to_contain_allocation(layout: Layout) -> Option<usize> {
let max_overhead =
layout.align().saturating_sub(GRANULARITY / 2) + mem::size_of::<UsedBlockHdr>();
let search_size = layout.size().checked_add(max_overhead)?;
let search_size = search_size.checked_add(GRANULARITY - 1)? & !(GRANULARITY - 1);
let list_min_size = Self::map_ceil_and_unmap(search_size)?;
list_min_size.checked_add(GRANULARITY)
}
pub fn allocate(&mut self, layout: Layout) -> Option<NonNull<u8>> {
unsafe {
let max_overhead =
layout.align().saturating_sub(GRANULARITY / 2) + mem::size_of::<UsedBlockHdr>();
let search_size = layout.size().checked_add(max_overhead)?;
let search_size = search_size.checked_add(GRANULARITY - 1)? & !(GRANULARITY - 1);
let (fl, sl) = self.search_suitable_free_block_list_for_allocation(search_size)?;
let first_free = self.first_free.get_unchecked_mut(fl).get_unchecked_mut(sl);
let block = first_free.unwrap_or_else(|| {
debug_assert!(false, "bitmap outdated");
unreachable_unchecked()
});
let mut next_phys_block = BlockHdr::next_phys_block(nn_field!(block, common));
let size_and_flags = block.as_ref().common.size;
let size = size_and_flags ;
debug_assert_eq!(size, size_and_flags & SIZE_SIZE_MASK);
debug_assert!(size >= search_size);
*first_free = block.as_ref().next_free;
if let Some(mut next_free) = *first_free {
next_free.as_mut().prev_free = None;
} else {
let sl_bitmap = self.sl_bitmap.get_unchecked_mut(fl);
sl_bitmap.clear_bit(sl as u32);
if *sl_bitmap == SLBitmap::ZERO {
self.fl_bitmap.clear_bit(fl as u32);
}
}
let unaligned_ptr =
(block.as_ptr() as *mut u8).wrapping_add(mem::size_of::<UsedBlockHdr>());
let ptr = NonNull::new_unchecked(round_up(unaligned_ptr, layout.align()));
if layout.align() < GRANULARITY {
debug_assert_eq!(unaligned_ptr, ptr.as_ptr());
} else {
debug_assert_ne!(unaligned_ptr, ptr.as_ptr());
}
let overhead = ptr.as_ptr() as usize - block.as_ptr() as usize;
debug_assert!(overhead <= max_overhead);
let new_size = overhead + layout.size();
let new_size = (new_size + GRANULARITY - 1) & !(GRANULARITY - 1);
debug_assert!(new_size <= search_size);
if new_size == size {
} else {
let new_free_block: NonNull<FreeBlockHdr> =
NonNull::new_unchecked(block.cast::<u8>().as_ptr().add(new_size)).cast();
let new_free_block_size = size - new_size;
debug_assert!((next_phys_block.as_ref().size & SIZE_USED) != 0);
next_phys_block.as_mut().prev_phys_block = Some(new_free_block.cast());
*nn_field!(new_free_block, common) = BlockHdr {
size: new_free_block_size,
prev_phys_block: Some(block.cast()),
};
self.link_free_block(new_free_block, new_free_block_size);
}
let mut block = block.cast::<UsedBlockHdr>();
block.as_mut().common.size = new_size | SIZE_USED;
if layout.align() >= GRANULARITY {
(*UsedBlockPad::get_for_allocation(ptr)).block_hdr = block;
}
Some(ptr)
}
}
#[inline]
fn search_suitable_free_block_list_for_allocation(
&self,
min_size: usize,
) -> Option<(usize, usize)> {
let (mut fl, mut sl) = Self::map_ceil(min_size)?;
sl = self.sl_bitmap[fl].bit_scan_forward(sl as u32) as usize;
if sl < SLLEN {
debug_assert!(self.sl_bitmap[fl].get_bit(sl as u32));
return Some((fl, sl));
}
fl = self.fl_bitmap.bit_scan_forward(fl as u32 + 1) as usize;
if fl < FLLEN {
debug_assert!(self.fl_bitmap.get_bit(fl as u32));
sl = self.sl_bitmap[fl].trailing_zeros() as usize;
if sl >= SLLEN {
debug_assert!(false, "bitmap contradiction");
unsafe { unreachable_unchecked() };
}
debug_assert!(self.sl_bitmap[fl].get_bit(sl as u32));
Some((fl, sl))
} else {
None
}
}
#[inline]
unsafe fn used_block_hdr_for_allocation(
ptr: NonNull<u8>,
align: usize,
) -> NonNull<UsedBlockHdr> {
if align >= GRANULARITY {
(*UsedBlockPad::get_for_allocation(ptr)).block_hdr
} else {
NonNull::new_unchecked(ptr.as_ptr().sub(GRANULARITY / 2)).cast()
}
}
#[inline]
unsafe fn used_block_hdr_for_allocation_unknown_align(
ptr: NonNull<u8>,
) -> NonNull<UsedBlockHdr> {
let c1_block_hdr_ptr: *const NonNull<UsedBlockHdr> =
addr_of!((*UsedBlockPad::get_for_allocation(ptr)).block_hdr);
let c2_block_hdr = ptr.cast::<UsedBlockHdr>().as_ptr().wrapping_sub(1);
let c2_prev_phys_block_ptr: *const Option<NonNull<BlockHdr>> =
addr_of!((*c2_block_hdr).common.prev_phys_block);
debug_assert_eq!(
c1_block_hdr_ptr as *const usize,
c2_prev_phys_block_ptr as *const usize
);
if let Some(block_ptr) = *c2_prev_phys_block_ptr {
let block_end = block_ptr.as_ptr() as usize + block_ptr.as_ref().size;
if ptr.as_ptr() as usize > block_end {
NonNull::new_unchecked(c2_block_hdr)
} else {
*c1_block_hdr_ptr
}
} else {
NonNull::new_unchecked(c2_block_hdr)
}
}
pub unsafe fn deallocate(&mut self, ptr: NonNull<u8>, align: usize) {
let block = Self::used_block_hdr_for_allocation(ptr, align).cast::<BlockHdr>();
self.deallocate_block(block);
}
pub(crate) unsafe fn deallocate_unknown_align(&mut self, ptr: NonNull<u8>) {
let block = Self::used_block_hdr_for_allocation_unknown_align(ptr).cast::<BlockHdr>();
self.deallocate_block(block);
}
#[inline]
unsafe fn deallocate_block(&mut self, mut block: NonNull<BlockHdr>) {
let mut size = block.as_ref().size & !SIZE_USED;
debug_assert!((block.as_ref().size & SIZE_USED) != 0);
let mut new_next_phys_block;
let next_phys_block = BlockHdr::next_phys_block(block.as_ptr());
let next_phys_block_size_and_flags = next_phys_block.as_ref().size;
if (next_phys_block_size_and_flags & SIZE_USED) == 0 {
let next_phys_block_size = next_phys_block_size_and_flags;
debug_assert_eq!(
next_phys_block_size_and_flags & SIZE_SIZE_MASK,
next_phys_block_size
);
size += next_phys_block_size;
new_next_phys_block = BlockHdr::next_phys_block(next_phys_block.as_ptr());
self.unlink_free_block(next_phys_block.cast(), next_phys_block_size);
} else {
new_next_phys_block = next_phys_block;
}
if let Some(prev_phys_block) = block.as_ref().prev_phys_block {
let prev_phys_block_size_and_flags = prev_phys_block.as_ref().size;
if (prev_phys_block_size_and_flags & SIZE_USED) == 0 {
let prev_phys_block_size = prev_phys_block_size_and_flags;
debug_assert_eq!(
prev_phys_block_size_and_flags & SIZE_SIZE_MASK,
prev_phys_block_size
);
size += prev_phys_block_size;
self.unlink_free_block(prev_phys_block.cast(), prev_phys_block_size);
block = prev_phys_block;
}
}
debug_assert!((size & SIZE_USED) == 0);
block.as_mut().size = size;
let block = block.cast::<FreeBlockHdr>();
self.link_free_block(block, size);
debug_assert_eq!(
new_next_phys_block,
BlockHdr::next_phys_block(nn_field!(block, common))
);
new_next_phys_block.as_mut().prev_phys_block = Some(block.cast());
}
#[inline]
pub(crate) unsafe fn size_of_allocation(ptr: NonNull<u8>, align: usize) -> usize {
let block = Self::used_block_hdr_for_allocation(ptr, align);
let size = block.as_ref().common.size - SIZE_USED;
debug_assert_eq!(size, block.as_ref().common.size & SIZE_SIZE_MASK);
let block_end = block.as_ptr() as usize + size;
let payload_start = ptr.as_ptr() as usize;
block_end - payload_start
}
#[inline]
pub(crate) unsafe fn size_of_allocation_unknown_align(ptr: NonNull<u8>) -> usize {
let block = Self::used_block_hdr_for_allocation_unknown_align(ptr);
let size = block.as_ref().common.size - SIZE_USED;
debug_assert_eq!(size, block.as_ref().common.size & SIZE_SIZE_MASK);
let block_end = block.as_ptr() as usize + size;
let payload_start = ptr.as_ptr() as usize;
block_end - payload_start
}
#[cfg(feature = "unstable")]
#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "unstable")))]
pub unsafe fn allocation_usable_size(ptr: NonNull<u8>) -> usize {
Self::size_of_allocation_unknown_align(ptr)
}
pub unsafe fn reallocate(
&mut self,
ptr: NonNull<u8>,
new_layout: Layout,
) -> Option<NonNull<u8>> {
let block = Self::used_block_hdr_for_allocation(ptr, new_layout.align());
let old_size = Self::size_of_allocation(ptr, new_layout.align());
if let Some(x) = self.reallocate_inplace(ptr, block, new_layout) {
return Some(x);
}
let new_ptr = self.allocate(new_layout)?;
debug_assert!(new_layout.size() >= old_size);
core::ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), old_size);
self.deallocate(ptr, new_layout.align());
Some(new_ptr)
}
#[inline]
unsafe fn reallocate_inplace(
&mut self,
ptr: NonNull<u8>,
mut block: NonNull<UsedBlockHdr>,
new_layout: Layout,
) -> Option<NonNull<u8>> {
let overhead = ptr.as_ptr() as usize - block.as_ptr() as usize;
let new_size = overhead.checked_add(new_layout.size())?;
let new_size = new_size.checked_add(GRANULARITY - 1)? & !(GRANULARITY - 1);
let old_size = block.as_ref().common.size - SIZE_USED;
debug_assert_eq!(old_size, block.as_ref().common.size & SIZE_SIZE_MASK);
if new_size <= old_size {
if new_size == old_size {
} else {
let shrink_by = old_size - new_size;
let new_free_block: NonNull<FreeBlockHdr> =
NonNull::new_unchecked(block.cast::<u8>().as_ptr().add(new_size)).cast();
let mut new_free_block_size = shrink_by;
let mut next_phys_block = BlockHdr::next_phys_block(nn_field!(block, common));
let next_phys_block_size_and_flags = next_phys_block.as_ref().size;
if (next_phys_block_size_and_flags & SIZE_USED) == 0 {
let next_phys_block_size = next_phys_block_size_and_flags;
debug_assert_eq!(
next_phys_block_size,
next_phys_block_size_and_flags & SIZE_SIZE_MASK
);
self.unlink_free_block(next_phys_block.cast(), next_phys_block_size);
new_free_block_size += next_phys_block_size;
let mut next_next_phys_block =
BlockHdr::next_phys_block(next_phys_block.as_ptr());
next_next_phys_block.as_mut().prev_phys_block = Some(new_free_block.cast());
} else {
next_phys_block.as_mut().prev_phys_block = Some(new_free_block.cast());
}
*nn_field!(new_free_block, common) = BlockHdr {
size: new_free_block_size,
prev_phys_block: Some(block.cast()),
};
self.link_free_block(new_free_block, new_free_block_size);
block.as_mut().common.size = new_size | SIZE_USED;
}
return Some(ptr);
}
debug_assert!(new_size > old_size);
let grow_by = new_size - old_size;
let next_phys_block = BlockHdr::next_phys_block(nn_field!(block, common));
let mut moving_clearance = old_size;
let mut moving_clearance_end = next_phys_block;
#[allow(clippy::never_loop)]
'nonmoving: loop {
let next_phys_block_size_and_flags = next_phys_block.as_ref().size;
if (next_phys_block_size_and_flags & SIZE_USED) != 0 {
break 'nonmoving;
}
let mut next_phys_block_size = next_phys_block_size_and_flags;
debug_assert_eq!(
next_phys_block_size,
next_phys_block_size_and_flags & SIZE_SIZE_MASK
);
let mut next_phys_block = next_phys_block.cast::<FreeBlockHdr>();
let mut next_next_phys_block =
BlockHdr::next_phys_block(nn_field!(next_phys_block, common));
moving_clearance += next_phys_block_size;
moving_clearance_end = next_next_phys_block;
if grow_by > next_phys_block_size {
break 'nonmoving;
}
self.unlink_free_block(next_phys_block, next_phys_block_size);
if grow_by < next_phys_block_size {
next_phys_block_size -= grow_by;
next_phys_block =
NonNull::new_unchecked(block.cast::<u8>().as_ptr().add(new_size)).cast();
*nn_field!(next_phys_block, common) = BlockHdr {
size: next_phys_block_size,
prev_phys_block: Some(block.cast()),
};
self.link_free_block(next_phys_block, next_phys_block_size);
next_next_phys_block.as_mut().prev_phys_block = Some(next_phys_block.cast());
} else {
debug_assert_eq!(grow_by, next_phys_block_size);
next_next_phys_block.as_mut().prev_phys_block = Some(block.cast());
}
block.as_mut().common.size = new_size | SIZE_USED;
return Some(ptr);
}
let prev_phys_block = block.as_ref().common.prev_phys_block?;
let prev_phys_block_size_and_flags = prev_phys_block.as_ref().size;
if (prev_phys_block_size_and_flags & SIZE_USED) != 0 {
return None;
}
let prev_phys_block_size = prev_phys_block_size_and_flags;
debug_assert_eq!(
prev_phys_block_size,
prev_phys_block_size_and_flags & SIZE_SIZE_MASK
);
moving_clearance += prev_phys_block_size;
let unaligned_ptr =
(prev_phys_block.as_ptr() as *mut u8).wrapping_add(mem::size_of::<UsedBlockHdr>());
let new_ptr = NonNull::new_unchecked(round_up(unaligned_ptr, new_layout.align()));
let new_overhead = new_ptr.as_ptr() as usize - prev_phys_block.as_ptr() as usize;
let new_size = new_overhead.checked_add(new_layout.size())?;
let new_size = new_size.checked_add(GRANULARITY - 1)? & !(GRANULARITY - 1);
if new_size > moving_clearance {
return None;
}
self.unlink_free_block(prev_phys_block.cast(), prev_phys_block_size);
let next_phys_block_size_and_flags = next_phys_block.as_ref().size;
if (next_phys_block_size_and_flags & SIZE_USED) == 0 {
let next_phys_block_size = next_phys_block_size_and_flags;
debug_assert_eq!(
next_phys_block_size,
next_phys_block_size_and_flags & SIZE_SIZE_MASK
);
self.unlink_free_block(next_phys_block.cast(), next_phys_block_size);
}
core::ptr::copy(
ptr.as_ptr(),
new_ptr.as_ptr(),
new_layout.size().min(old_size - overhead),
);
let mut new_block = prev_phys_block.cast::<UsedBlockHdr>();
if new_size == moving_clearance {
moving_clearance_end.as_mut().prev_phys_block = Some(new_block.cast());
} else {
let new_free_block: NonNull<FreeBlockHdr> =
NonNull::new_unchecked(new_block.cast::<u8>().as_ptr().add(new_size)).cast();
let mut new_free_block_size = moving_clearance - new_size;
let moving_clearance_end_size_and_flags = moving_clearance_end.as_ref().size;
if (moving_clearance_end_size_and_flags & SIZE_USED) == 0 {
let moving_clearance_end_size = moving_clearance_end_size_and_flags;
debug_assert_eq!(
moving_clearance_end_size,
moving_clearance_end_size_and_flags & SIZE_SIZE_MASK
);
self.unlink_free_block(moving_clearance_end.cast(), moving_clearance_end_size);
new_free_block_size += moving_clearance_end_size_and_flags;
let mut next_next_phys_block =
BlockHdr::next_phys_block(moving_clearance_end.as_mut());
next_next_phys_block.as_mut().prev_phys_block = Some(new_free_block.cast());
} else {
moving_clearance_end.as_mut().prev_phys_block = Some(new_free_block.cast());
}
*nn_field!(new_free_block, common) = BlockHdr {
size: new_free_block_size,
prev_phys_block: Some(new_block.cast()),
};
self.link_free_block(new_free_block, new_free_block_size);
}
new_block.as_mut().common.size = new_size | SIZE_USED;
if new_layout.align() >= GRANULARITY {
(*UsedBlockPad::get_for_allocation(new_ptr)).block_hdr = new_block;
}
Some(new_ptr)
}
#[cfg(feature = "unstable")]
#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "unstable")))]
pub unsafe fn iter_blocks(
&self,
pool: NonNull<[u8]>,
) -> impl Iterator<Item = BlockInfo<'_>> + Send + '_ {
let len = nonnull_slice_len(pool);
struct SendPtr(*mut u8);
unsafe impl Send for SendPtr {}
let unaligned_start = pool.as_ptr() as *mut u8;
let mut start = SendPtr(round_up(unaligned_start, GRANULARITY));
let mut len = len.saturating_sub((start.0 as usize).wrapping_sub(unaligned_start as usize));
core::iter::from_fn(move || {
let _ = &start;
if len == 0 {
None
} else {
let block_hdr = &*(start.0 as *const BlockHdr);
let block_size = block_hdr.size & SIZE_SIZE_MASK;
len -= block_size;
start.0 = start.0.wrapping_add(block_size);
Some(BlockInfo { block_hdr })
}
})
.filter(|block_info| {
(block_info.block_hdr.size & SIZE_SENTINEL) == 0
})
}
}
#[derive(Clone, Copy)]
#[cfg(feature = "unstable")]
#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "unstable")))]
pub struct BlockInfo<'a> {
block_hdr: &'a BlockHdr,
}
#[cfg(feature = "unstable")]
impl fmt::Debug for BlockInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BlockInfo")
.field("ptr", &self.as_ptr_range())
.field("size", &self.size())
.field("is_occupied", &self.is_occupied())
.finish()
}
}
#[cfg(feature = "unstable")]
impl BlockInfo<'_> {
#[inline]
pub fn size(&self) -> usize {
self.block_hdr.size & SIZE_SIZE_MASK
}
#[inline]
pub fn max_payload_size(&self) -> usize {
self.size() - GRANULARITY / 2
}
#[inline]
pub fn as_ptr(&self) -> NonNull<[u8]> {
nonnull_slice_from_raw_parts(NonNull::from(self.block_hdr).cast(), self.size())
}
#[inline]
fn as_ptr_range(&self) -> core::ops::Range<*mut u8> {
let start = self.block_hdr as *const _ as *mut u8;
let end = start.wrapping_add(self.size());
start..end
}
#[inline]
pub fn is_occupied(&self) -> bool {
(self.block_hdr.size & SIZE_USED) != 0
}
}
#[cfg(test)]
mod tests;