use alloc::vec;
use alloc::vec::Vec;
use core::ptr;
pub trait BuddySystemAllocator {
fn alloc(&mut self, size: usize) -> Option<*mut u8>;
fn dealloc(&mut self, ptr: *mut u8, size: usize);
fn reset(&mut self);
}
pub struct BuddyAllocator {
total_size: usize, min_block_size: usize, max_order: usize, base_ptr: *mut u8, free_lists: Vec<Vec<*mut u8>>, }
impl BuddyAllocator {
pub fn new(total_size: usize, min_block_size: usize) -> Self {
assert!(
min_block_size.is_power_of_two(),
"min_block_size must be a power of two"
);
assert!(
total_size >= min_block_size,
"total_size must be >= min_block_size"
);
let max_order = (total_size / min_block_size).trailing_zeros() as usize;
BuddyAllocator {
total_size,
min_block_size,
max_order,
base_ptr: ptr::null_mut(),
free_lists: vec![Vec::new(); max_order + 1],
}
}
pub fn init(&mut self) -> bool {
let layout =
match alloc::alloc::Layout::from_size_align(self.total_size, self.min_block_size) {
Ok(layout) => layout,
Err(_) => {
self.base_ptr = core::ptr::null_mut();
return false;
}
};
let ptr = unsafe { alloc::alloc::alloc(layout) };
if !ptr.is_null() {
self.base_ptr = ptr;
self.free_lists[self.max_order].push(ptr);
true
} else {
self.base_ptr = core::ptr::null_mut();
false
}
}
fn order_for_size(&self, size: usize) -> Option<usize> {
let mut order = 0;
let mut block_size = self.min_block_size;
while block_size < size {
block_size <<= 1;
order += 1;
}
if order > self.max_order {
None
} else {
Some(order)
}
}
fn block_size(&self, order: usize) -> usize {
self.min_block_size << order
}
fn buddy_of(&self, ptr: *mut u8, order: usize) -> *mut u8 {
assert!(
!self.base_ptr.is_null(),
"BuddyAllocator not initialized: base_ptr is null"
);
let base = self.base_ptr as usize;
let ptr = ptr as usize;
if ptr < base {
panic!(
"Pointer passed to buddy_of is before base_ptr: ptr={:#x}, base_ptr={:#x}",
ptr, base
);
}
let offset = ptr - base;
let buddy_offset = offset ^ self.block_size(order);
unsafe { self.base_ptr.add(buddy_offset) }
}
}
impl BuddySystemAllocator for BuddyAllocator {
fn alloc(&mut self, size: usize) -> Option<*mut u8> {
if self.base_ptr.is_null() {
return None;
}
let order = self.order_for_size(size)?;
for current_order in order..=self.max_order {
if let Some(&block) = self.free_lists[current_order].last() {
self.free_lists[current_order].pop();
for split_order in (order..current_order).rev() {
let buddy = unsafe { block.add(self.block_size(split_order)) };
self.free_lists[split_order].push(buddy);
}
return Some(block);
}
}
None
}
fn dealloc(&mut self, mut ptr: *mut u8, size: usize) {
if self.base_ptr.is_null() {
return;
}
if let Some(mut order) = self.order_for_size(size) {
while order < self.max_order {
let buddy = self.buddy_of(ptr, order);
if let Some(pos) = self.free_lists[order].iter().position(|&p| p == buddy) {
self.free_lists[order].remove(pos);
if buddy < ptr {
ptr = buddy;
}
order += 1;
} else {
break;
}
}
self.free_lists[order].push(ptr);
}
}
fn reset(&mut self) {
for list in &mut self.free_lists {
list.clear();
}
if !self.base_ptr.is_null() {
self.free_lists[self.max_order].push(self.base_ptr);
}
}
}
impl Drop for BuddyAllocator {
fn drop(&mut self) {
if !self.base_ptr.is_null() {
unsafe {
alloc::alloc::dealloc(
self.base_ptr,
alloc::alloc::Layout::from_size_align(self.total_size, self.min_block_size)
.unwrap(),
);
}
self.base_ptr = core::ptr::null_mut();
}
for list in &mut self.free_lists {
list.clear();
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use alloc::vec::Vec;
#[test]
fn test_basic_alloc_dealloc() {
let total_size = 1024;
let min_block_size = 32;
let mut allocator = BuddyAllocator::new(total_size, min_block_size);
assert!(allocator.init(), "BuddyAllocator init failed");
let ptr = allocator.alloc(64);
assert!(ptr.is_some(), "Allocation failed");
let ptr_val = ptr.unwrap() as usize;
let base_val = allocator.base_ptr as usize;
assert!(ptr_val >= base_val, "Allocated pointer is before base_ptr");
allocator.dealloc(ptr.unwrap(), 64);
}
#[test]
fn test_multiple_allocs_and_deallocs() {
let total_size = 1024;
let min_block_size = 32;
let mut allocator = BuddyAllocator::new(total_size, min_block_size);
assert!(allocator.init(), "BuddyAllocator init failed");
let mut ptrs = Vec::new();
for _ in 0..4 {
let ptr = allocator.alloc(64);
assert!(ptr.is_some(), "Allocation failed");
let ptr_val = ptr.unwrap() as usize;
let base_val = allocator.base_ptr as usize;
assert!(ptr_val >= base_val, "Allocated pointer is before base_ptr");
ptrs.push(ptr.unwrap());
}
for &ptr in &ptrs {
allocator.dealloc(ptr, 64);
}
}
#[test]
fn test_exhaustion() {
let total_size = 128;
let min_block_size = 32;
let mut allocator = BuddyAllocator::new(total_size, min_block_size);
assert!(allocator.init(), "BuddyAllocator init failed");
let a = allocator.alloc(64);
assert!(a.is_some(), "Allocation of 64 failed");
let b = allocator.alloc(64);
assert!(b.is_some(), "Allocation of 64 failed");
assert!(
allocator.alloc(64).is_none(),
"Should not be able to allocate more"
);
allocator.dealloc(a.unwrap(), 64);
allocator.dealloc(b.unwrap(), 64);
}
#[test]
fn test_reset() {
let total_size = 256;
let min_block_size = 32;
let mut allocator = BuddyAllocator::new(total_size, min_block_size);
assert!(allocator.init(), "BuddyAllocator init failed");
let ptr1 = allocator.alloc(64);
let ptr2 = allocator.alloc(64);
assert!(ptr1.is_some() && ptr2.is_some());
let ptr1_val = ptr1.unwrap() as usize;
let ptr2_val = ptr2.unwrap() as usize;
let base_val = allocator.base_ptr as usize;
assert!(
ptr1_val >= base_val && ptr2_val >= base_val,
"Allocated pointer is before base_ptr"
);
allocator.reset();
let ptr3 = allocator.alloc(128);
assert!(ptr3.is_some());
let ptr3_val = ptr3.unwrap() as usize;
assert!(ptr3_val >= base_val, "Allocated pointer is before base_ptr");
}
#[test]
fn test_fragmentation_and_merge() {
let total_size = 256;
let min_block_size = 32;
let mut allocator = BuddyAllocator::new(total_size, min_block_size);
assert!(allocator.init(), "BuddyAllocator init failed");
let a = allocator.alloc(32).unwrap();
let b = allocator.alloc(32).unwrap();
let c = allocator.alloc(32).unwrap();
let d = allocator.alloc(32).unwrap();
let base_val = allocator.base_ptr as usize;
assert!(
(a as usize) >= base_val
&& (b as usize) >= base_val
&& (c as usize) >= base_val
&& (d as usize) >= base_val,
"Allocated pointer is before base_ptr"
);
allocator.dealloc(a, 32);
allocator.dealloc(c, 32);
allocator.dealloc(b, 32);
allocator.dealloc(d, 32);
let big = allocator.alloc(128);
assert!(big.is_some(), "Allocator did not merge blocks correctly");
let big_val = big.unwrap() as usize;
assert!(big_val >= base_val, "Allocated pointer is before base_ptr");
}
}