use std::num::NonZeroU64;
use crate::align::align_nonzero;
use super::OutOfMemory;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BuddyAllocation {
offset: u64,
size: NonZeroU64,
index: usize,
}
impl super::Allocation for BuddyAllocation {
fn offset(&self) -> u64 {
self.offset
}
fn size(&self) -> u64 {
self.size.get()
}
}
#[derive(Clone)]
pub struct BuddyAllocator {
blocks: Vec<Block>,
o0_size: u64,
max_order: u8,
}
impl std::fmt::Debug for BuddyAllocator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut string = String::new();
let mut index = 0;
for level in 0..=self.max_order {
let order = self.max_order - level;
let start_spacing = 2usize.pow(order as u32) - 1;
let item_spacing = 2 * start_spacing + 1;
string.extend(std::iter::repeat(' ').take(start_spacing));
for _block in 0..2usize.pow(level as u32) {
if self.is_allocated(index) {
string.push_str("x");
} else {
string.push_str(format!("{:x}", self.read_block(index)).as_str());
}
string.extend(std::iter::repeat(' ').take(item_spacing));
index += 1;
}
string.push('\n');
}
f.write_str(&string)
}
}
impl BuddyAllocator {
pub fn new(max_order: u8, o0_size: u64) -> Self {
let max_level = max_order + 1;
let mut blocks = vec![Block::new(0); Self::blocks_for_level(max_level)];
let mut i = 0;
for o in 0..max_level {
let n = 1 << o;
blocks[i..(i + n)].fill(Block::new(max_order - o));
i += n;
}
BuddyAllocator {
blocks,
o0_size,
max_order,
}
}
fn read_block(&self, i: usize) -> u8 {
self.blocks[i].order
}
fn write_block(&mut self, i: usize, new_order: u8) {
self.blocks[i].order = new_order;
}
fn update(&mut self, mut i: usize, max_level: u8) {
for _ in 0..max_level {
let i_right = (i + 1) & !1;
i = Self::parent(i);
let left = self.read_block(i_right - 1);
let right = self.read_block(i_right);
self.write_block(i, left.max(right)); }
}
const fn blocks_for_level(level: u8) -> usize {
((1 << level) - 1) as usize
}
const fn left_child(i: usize) -> usize {
((i + 1) << 1) - 1
}
#[allow(dead_code)]
const fn right_child(i: usize) -> usize {
(((i + 1) << 1) + 1) - 1
}
const fn parent(i: usize) -> usize {
((i + 1) >> 1) - 1
}
fn is_allocated(&self, i: usize) -> bool {
if self.read_block(i) == 0 {
true
} else if i == 0 {
false
} else {
self.is_allocated(Self::parent(i))
}
}
pub fn order_of(size: NonZeroU64, o0_size: u64) -> u8 {
let o0_blocks_needed = unsafe { NonZeroU64::new_unchecked(div_ceil(size.get(), o0_size)) };
log2_ceil(o0_blocks_needed) as u8
}
}
fn div_ceil(a: u64, b: u64) -> u64 {
(a + b - 1) / b
}
pub const fn log2_ceil(x: NonZeroU64) -> u32 {
u64::BITS - (x.get() - 1).leading_zeros()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Block {
order: u8,
}
impl Block {
pub fn new(order: u8) -> Self {
Block { order: order + 1 }
}
}
impl super::Allocator for BuddyAllocator {
type Allocation = BuddyAllocation;
fn allocate(
&mut self,
size: NonZeroU64,
alignment: NonZeroU64,
) -> Result<Self::Allocation, OutOfMemory> {
assert!(
self.o0_size % alignment.get() == 0 || alignment.get() % self.o0_size == 0,
"buddy allocator cannot provide allocations when the alignment given is not a multiple of the minimum block size or viceversa"
);
let aligned = align_nonzero(alignment, size);
let order = Self::order_of(size, self.o0_size);
let root = self.read_block(0);
if root == 0 || root - 1 < order {
return Err(OutOfMemory);
}
let max_level = self.max_order - order;
let mut offset = 0; let mut i = 0; for level in 0..max_level {
let i_parent = i;
let i_left = Self::left_child(i_parent);
let left = self.read_block(i_left);
if left != 0 && left - 1 >= order {
i = i_left;
} else {
i = i_left + 1;
offset += 1 << ((self.max_order - level - 1) as u64);
}
}
self.write_block(i, 0);
self.update(i, max_level);
Ok(BuddyAllocation {
offset: self.o0_size * offset,
size: aligned,
index: i,
})
}
fn from_properties(min_alloc: u64, capacity: NonZeroU64) -> Self {
Self::new(Self::order_of(capacity, min_alloc), min_alloc)
}
}
impl super::Deallocator for BuddyAllocator {
fn deallocate(&mut self, alloc: &Self::Allocation) {
let order = Self::order_of(alloc.size, self.o0_size);
self.write_block(alloc.index, order + 1);
self.update(alloc.index, self.max_order - order);
}
}
#[cfg(test)]
mod test {
use crate::Allocator;
use super::BuddyAllocator;
use nonzero_ext::nonzero;
#[test]
fn empty() {
let allocator = BuddyAllocator::new(4, 1);
println!("{:?}", allocator);
assert_eq!(
allocator.blocks,
vec![
5, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1
]
.into_iter()
.map(|order| super::Block { order })
.collect::<Vec<_>>()
)
}
#[test]
fn order() {
assert_eq!(BuddyAllocator::order_of(nonzero!(1u64), 1), 0);
assert_eq!(
BuddyAllocator::order_of(nonzero!(100u64), 1),
7
);
assert_eq!(
BuddyAllocator::order_of(nonzero!(50u64), 10),
3
);
}
#[test]
fn allocate_single() {
let mut allocator = BuddyAllocator::new(2, 1);
allocator.allocate(nonzero!(1u64), nonzero!(1u64)).unwrap();
println!("{:?}", allocator);
assert_eq!(
allocator.blocks,
vec![2, 1, 2, 0, 1, 1, 1]
.into_iter()
.map(|order| super::Block { order })
.collect::<Vec<_>>()
)
}
#[test]
fn allocate_small() {
let mut allocator = BuddyAllocator::new(2, 1);
allocator.allocate(nonzero!(1u64), nonzero!(1u64)).unwrap();
allocator.allocate(nonzero!(1u64), nonzero!(1u64)).unwrap();
allocator.allocate(nonzero!(1u64), nonzero!(1u64)).unwrap();
println!("{:?}", allocator);
assert_eq!(
allocator.blocks,
vec![1, 0, 1, 0, 0, 0, 1]
.into_iter()
.map(|order| super::Block { order })
.collect::<Vec<_>>()
)
}
#[test]
fn allocate_mixed() {
let mut allocator = BuddyAllocator::new(2, 1);
allocator.allocate(nonzero!(1u64), nonzero!(1u64)).unwrap();
allocator.allocate(nonzero!(2u64), nonzero!(1u64)).unwrap();
println!("{:?}", allocator);
assert_eq!(
allocator.blocks,
vec![1, 1, 0, 0, 1, 1, 1]
.into_iter()
.map(|order| super::Block { order })
.collect::<Vec<_>>()
)
}
#[test]
fn allocate_aligned() {
let mut allocator = BuddyAllocator::new(5, 8);
let a1 = allocator.allocate(nonzero!(1u64), nonzero!(16u64)).unwrap();
assert_eq!(a1.offset % 16, 0);
let a2 = allocator
.allocate(nonzero!(32u64), nonzero!(32u64))
.unwrap();
assert_eq!(a2.offset % 32, 0);
let a3 = allocator
.allocate(nonzero!(33u64), nonzero!(32u64))
.unwrap();
assert_eq!(a3.offset % 32, 0);
println!("{:?}", allocator);
}
}