//! A ZoneAllocator to allocate arbitrary object sizes (up to `ZoneAllocator::MAX_ALLOC_SIZE`) //! //! The ZoneAllocator achieves this by having many `SCAllocator` use crate::*; /// Creates an instance of a zone, we do this in a macro because we /// re-use the code in const and non-const functions /// /// We can get rid of this once the const fn feature is fully stabilized. macro_rules! new_zone { () => { ZoneAllocator { // TODO(perf): We should probably pick better classes // rather than powers-of-two (see SuperMalloc etc.) small_slabs: [ SCAllocator::new(1 << 3), // 8 SCAllocator::new(1 << 4), // 16 SCAllocator::new(1 << 5), // 32 SCAllocator::new(1 << 6), // 64 SCAllocator::new(1 << 7), // 128 SCAllocator::new(1 << 8), // 256 SCAllocator::new(1 << 9), // 512 SCAllocator::new(1 << 10), // 1024 SCAllocator::new(1 << 11), // 2048 ], ], } }; } /// A zone allocator for arbitrary sized allocations. /// /// Has a bunch of `SCAllocator` and through that can serve allocation /// requests for many different object sizes up to (MAX_SIZE_CLASSES) by selecting /// the right `SCAllocator` for allocation and deallocation. /// /// The allocator provides to refill functions `refill` and `refill_large` /// to provide the underlying `SCAllocator` with more memory in case it runs out. pub struct ZoneAllocator<'a> { small_slabs: [SCAllocator<'a, ObjectPage<'a>>; ZoneAllocator::MAX_BASE_SIZE_CLASSES], } impl<'a> Default for ZoneAllocator<'a> { fn default() -> ZoneAllocator<'a> { new_zone!() } } enum Slab { Base(usize), Unsupported, } impl<'a> ZoneAllocator<'a> { /// Maximum size that allocated within LargeObjectPages (2 MiB). /// This is also the maximum object size that this allocator can handle. pub const MAX_ALLOC_SIZE: usize = 1 << 11; /// Maximum size which is allocated with ObjectPages (4 KiB pages). /// /// e.g. this is 4 KiB - 80 bytes of meta-data. pub const MAX_BASE_ALLOC_SIZE: usize = 256; /// How many allocators of type SCAllocator we have. const MAX_BASE_SIZE_CLASSES: usize = 9; #[cfg(feature = "unstable")] pub const fn new() -> ZoneAllocator<'a> { new_zone!() } #[cfg(not(feature = "unstable"))] pub fn new() -> ZoneAllocator<'a> { new_zone!() } /// Return maximum size an object of size `current_size` can use. /// /// Used to optimize `realloc`. pub fn get_max_size(current_size: usize) -> Option { match current_size { 0..=8 => Some(8), 9..=16 => Some(16), 17..=32 => Some(32), 33..=64 => Some(64), 65..=128 => Some(128), 129..=256 => Some(256), 257..=512 => Some(512), 513..=1024 => Some(1024), 1025..=2048 => Some(2048), _ => None, } } /// Figure out index into zone array to get the correct slab allocator for that size. fn get_slab(requested_size: usize) -> Slab { match requested_size { 0..=8 => Slab::Base(0), 9..=16 => Slab::Base(1), 17..=32 => Slab::Base(2), 33..=64 => Slab::Base(3), 65..=128 => Slab::Base(4), 129..=256 => Slab::Base(5), 257..=512 => Slab::Base(6), 513..=1024 => Slab::Base(7), 1025..=2048 => Slab::Base(8), _ => Slab::Unsupported, } } /// Reclaims empty pages by calling `dealloc` on it and removing it from the /// empty lists in the [`SCAllocator`]. /// /// The `dealloc` function is called at most `reclaim_base_max` times for /// base pages, and at most `reclaim_large_max` for large pages. pub fn try_reclaim_base_pages(&mut self, mut to_reclaim: usize, mut dealloc: F) where F: Fn(*mut ObjectPage), { for i in 0..ZoneAllocator::MAX_BASE_SIZE_CLASSES { let slab = &mut self.small_slabs[i]; let just_reclaimed = slab.try_reclaim_pages(to_reclaim, &mut dealloc); to_reclaim = to_reclaim.saturating_sub(just_reclaimed); if to_reclaim == 0 { break; } } } } unsafe impl<'a> crate::Allocator<'a> for ZoneAllocator<'a> { /// Allocate a pointer to a block of memory described by `layout`. fn allocate(&mut self, layout: Layout) -> Result, AllocationError> { match ZoneAllocator::get_slab(layout.size()) { Slab::Base(idx) => self.small_slabs[idx].allocate(layout), Slab::Unsupported => Err(AllocationError::InvalidLayout), } } /// Deallocates a pointer to a block of memory, which was /// previously allocated by `allocate`. /// /// # Arguments /// * `ptr` - Address of the memory location to free. /// * `layout` - Memory layout of the block pointed to by `ptr`. fn deallocate(&mut self, ptr: NonNull, layout: Layout) -> Result<(), AllocationError> { match ZoneAllocator::get_slab(layout.size()) { Slab::Base(idx) => self.small_slabs[idx].deallocate(ptr, layout), Slab::Unsupported => Err(AllocationError::InvalidLayout), } } /// Refills the SCAllocator for a given Layout with an ObjectPage. /// /// # Safety /// ObjectPage needs to be emtpy etc. unsafe fn refill( &mut self, layout: Layout, new_page: &'a mut ObjectPage<'a>, ) -> Result<(), AllocationError> { match ZoneAllocator::get_slab(layout.size()) { Slab::Base(idx) => { self.small_slabs[idx].refill(new_page); Ok(()) } Slab::Unsupported => Err(AllocationError::InvalidLayout), } } }