use super::{
AllocationType, Region, Suballocation, SuballocationNode, Suballocator, SuballocatorError,
};
use crate::{
memory::{
allocator::{align_up, array_vec::ArrayVec, AllocationHandle, DeviceLayout},
is_aligned, DeviceAlignment,
},
DeviceSize, NonZeroDeviceSize,
};
use std::cmp;
#[derive(Debug)]
pub struct BuddyAllocator {
region: Region,
free_size: DeviceSize,
free_list: ArrayVec<Vec<DeviceSize>, { Self::MAX_ORDERS }>,
}
impl BuddyAllocator {
pub(super) const MIN_NODE_SIZE: DeviceSize = 16;
const MAX_ORDERS: usize = 32;
}
unsafe impl Suballocator for BuddyAllocator {
type Suballocations<'a> = std::iter::Empty<SuballocationNode>;
fn new(region: Region) -> Self {
const EMPTY_FREE_LIST: Vec<DeviceSize> = Vec::new();
assert!(region.size().is_power_of_two());
assert!(region.size() >= BuddyAllocator::MIN_NODE_SIZE);
let max_order = (region.size() / BuddyAllocator::MIN_NODE_SIZE).trailing_zeros() as usize;
assert!(max_order < BuddyAllocator::MAX_ORDERS);
let mut free_list =
ArrayVec::new(max_order + 1, [EMPTY_FREE_LIST; BuddyAllocator::MAX_ORDERS]);
free_list[max_order].push(region.offset());
BuddyAllocator {
region,
free_size: region.size(),
free_list,
}
}
#[inline]
fn allocate(
&mut self,
layout: DeviceLayout,
allocation_type: AllocationType,
buffer_image_granularity: DeviceAlignment,
) -> Result<Suballocation, SuballocatorError> {
fn prev_power_of_two(val: DeviceSize) -> DeviceSize {
const MAX_POWER_OF_TWO: DeviceSize = DeviceAlignment::MAX.as_devicesize();
if let Some(val) = NonZeroDeviceSize::new(val) {
MAX_POWER_OF_TWO >> val.leading_zeros()
} else {
0
}
}
let mut size = layout.size();
let mut alignment = layout.alignment();
if buffer_image_granularity != DeviceAlignment::MIN {
debug_assert!(is_aligned(self.region.offset(), buffer_image_granularity));
if allocation_type == AllocationType::Unknown
|| allocation_type == AllocationType::NonLinear
{
size = align_up(size, buffer_image_granularity);
alignment = cmp::max(alignment, buffer_image_granularity);
}
}
let size = cmp::max(size, BuddyAllocator::MIN_NODE_SIZE).next_power_of_two();
let min_order = (size / BuddyAllocator::MIN_NODE_SIZE).trailing_zeros() as usize;
for (order, free_list) in self.free_list.iter_mut().enumerate().skip(min_order) {
for (index, &offset) in free_list.iter().enumerate() {
if is_aligned(offset, alignment) {
free_list.remove(index);
for (order, free_list) in self
.free_list
.iter_mut()
.enumerate()
.skip(min_order)
.take(order - min_order)
.rev()
{
let size = BuddyAllocator::MIN_NODE_SIZE << order;
let right_child = offset + size;
let (Ok(index) | Err(index)) = free_list.binary_search(&right_child);
free_list.insert(index, right_child);
}
self.free_size -= size;
return Ok(Suballocation {
offset,
size: layout.size(),
allocation_type,
handle: AllocationHandle::from_index(min_order),
});
}
}
}
if prev_power_of_two(self.free_size()) >= layout.size() {
Err(SuballocatorError::FragmentedRegion)
} else {
Err(SuballocatorError::OutOfRegionMemory)
}
}
#[inline]
unsafe fn deallocate(&mut self, suballocation: Suballocation) {
let mut offset = suballocation.offset;
let order = suballocation.handle.as_index();
let min_order = order;
debug_assert!(!self.free_list[order].contains(&offset));
for (order, free_list) in self.free_list.iter_mut().enumerate().skip(min_order) {
let size = BuddyAllocator::MIN_NODE_SIZE << order;
let buddy_offset = ((offset - self.region.offset()) ^ size) + self.region.offset();
match free_list.binary_search(&buddy_offset) {
Ok(index) => {
free_list.remove(index);
offset = cmp::min(offset, buddy_offset);
}
Err(_) => {
let (Ok(index) | Err(index)) = free_list.binary_search(&offset);
free_list.insert(index, offset);
let size = BuddyAllocator::MIN_NODE_SIZE << min_order;
self.free_size += size;
break;
}
}
}
}
fn reset(&mut self) {
self.free_size = self.region.size();
self.free_list.iter_mut().for_each(Vec::clear);
let max_order =
(self.region.size() / BuddyAllocator::MIN_NODE_SIZE).trailing_zeros() as usize;
self.free_list[max_order].push(self.region.offset());
}
#[inline]
fn free_size(&self) -> DeviceSize {
self.free_size
}
#[inline]
fn suballocations(&self) -> Self::Suballocations<'_> {
todo!()
}
}