use parking_lot::{Mutex, RwLock};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
pub const MIN_BLOCK_SIZE: usize = 16;
pub const MAX_BLOCK_SIZE: usize = 1 << 30;
pub const DEFAULT_POOL_SIZE: usize = 64 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BuddyError {
SizeTooLarge(usize),
ZeroSize,
OutOfMemory,
InvalidAddress(usize),
DoubleFree(usize),
BlockNotFound(usize),
PoolExhausted,
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct BlockHeader {
magic: u32,
order: u8,
flags: u8,
_padding: [u8; 2],
size: u32,
}
#[allow(dead_code)]
const BLOCK_MAGIC: u32 = 0xB0DD_1E5A;
#[allow(dead_code)]
const FLAG_ALLOCATED: u8 = 0x01;
#[allow(dead_code)]
const FLAG_SPLIT: u8 = 0x02;
#[allow(dead_code)]
impl BlockHeader {
fn new(order: u8, size: u32) -> Self {
Self {
magic: BLOCK_MAGIC,
order,
flags: 0,
_padding: [0; 2],
size,
}
}
fn is_valid(&self) -> bool {
self.magic == BLOCK_MAGIC
}
fn is_allocated(&self) -> bool {
self.flags & FLAG_ALLOCATED != 0
}
fn set_allocated(&mut self, allocated: bool) {
if allocated {
self.flags |= FLAG_ALLOCATED;
} else {
self.flags &= !FLAG_ALLOCATED;
}
}
fn is_split(&self) -> bool {
self.flags & FLAG_SPLIT != 0
}
fn set_split(&mut self, split: bool) {
if split {
self.flags |= FLAG_SPLIT;
} else {
self.flags &= !FLAG_SPLIT;
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct FreeBlock {
addr: usize,
next: usize,
}
#[derive(Debug, Default)]
pub struct BuddyStats {
pub allocations: AtomicU64,
pub deallocations: AtomicU64,
pub allocated_bytes: AtomicUsize,
pub peak_allocated_bytes: AtomicUsize,
pub splits: AtomicU64,
pub merges: AtomicU64,
pub failed_allocations: AtomicU64,
}
impl BuddyStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_allocation(&self, size: usize) {
self.allocations.fetch_add(1, Ordering::Relaxed);
let old = self.allocated_bytes.fetch_add(size, Ordering::Relaxed);
let new = old + size;
let mut current_peak = self.peak_allocated_bytes.load(Ordering::Relaxed);
while new > current_peak {
match self.peak_allocated_bytes.compare_exchange_weak(
current_peak,
new,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(p) => current_peak = p,
}
}
}
pub fn record_deallocation(&self, size: usize) {
self.deallocations.fetch_add(1, Ordering::Relaxed);
self.allocated_bytes.fetch_sub(size, Ordering::Relaxed);
}
pub fn record_split(&self) {
self.splits.fetch_add(1, Ordering::Relaxed);
}
pub fn record_merge(&self) {
self.merges.fetch_add(1, Ordering::Relaxed);
}
pub fn record_failed_allocation(&self) {
self.failed_allocations.fetch_add(1, Ordering::Relaxed);
}
}
#[inline]
pub fn size_to_order(size: usize) -> u8 {
if size <= MIN_BLOCK_SIZE {
return 4; }
let bits = (size - 1).leading_zeros();
(usize::BITS - bits) as u8
}
#[inline]
pub fn order_to_size(order: u8) -> usize {
1 << order
}
#[inline]
pub fn buddy_addr(addr: usize, order: u8) -> usize {
addr ^ (1 << order)
}
pub struct MemoryPool {
base: usize,
size: usize,
max_order: u8,
min_order: u8,
free_lists: Vec<Mutex<Vec<usize>>>,
allocated: RwLock<HashMap<usize, u8>>,
stats: BuddyStats,
}
impl MemoryPool {
pub fn new(base: usize, size: usize) -> Result<Self, BuddyError> {
if size == 0 {
return Err(BuddyError::ZeroSize);
}
if !size.is_power_of_two() {
return Err(BuddyError::SizeTooLarge(size));
}
let max_order = size_to_order(size);
let min_order = size_to_order(MIN_BLOCK_SIZE);
let num_orders = (max_order - min_order + 1) as usize;
let mut free_lists = Vec::with_capacity(num_orders);
for _ in 0..num_orders {
free_lists.push(Mutex::new(Vec::new()));
}
free_lists[num_orders - 1].lock().push(base);
Ok(Self {
base,
size,
max_order,
min_order,
free_lists,
allocated: RwLock::new(HashMap::new()),
stats: BuddyStats::new(),
})
}
fn list_index(&self, order: u8) -> usize {
(order - self.min_order) as usize
}
pub fn allocate(&self, size: usize) -> Result<usize, BuddyError> {
if size == 0 {
return Err(BuddyError::ZeroSize);
}
let required_order = size_to_order(size);
if required_order > self.max_order {
self.stats.record_failed_allocation();
return Err(BuddyError::SizeTooLarge(size));
}
let mut found_order = None;
for order in required_order..=self.max_order {
let idx = self.list_index(order);
let list = self.free_lists[idx].lock();
if !list.is_empty() {
found_order = Some(order);
break;
}
}
let available_order = match found_order {
Some(o) => o,
None => {
self.stats.record_failed_allocation();
return Err(BuddyError::OutOfMemory);
}
};
let addr = {
let idx = self.list_index(available_order);
self.free_lists[idx]
.lock()
.pop()
.ok_or(BuddyError::OutOfMemory)?
};
let final_addr = self.split_block(addr, available_order, required_order);
self.allocated.write().insert(final_addr, required_order);
self.stats.record_allocation(order_to_size(required_order));
Ok(final_addr)
}
fn split_block(&self, addr: usize, current_order: u8, target_order: u8) -> usize {
let mut current_addr = addr;
let mut order = current_order;
while order > target_order {
order -= 1;
self.stats.record_split();
let buddy = buddy_addr(current_addr, order);
let idx = self.list_index(order);
self.free_lists[idx].lock().push(buddy);
if buddy < current_addr {
current_addr = buddy;
}
}
current_addr
}
pub fn deallocate(&self, addr: usize) -> Result<(), BuddyError> {
let order = {
let mut allocated = self.allocated.write();
allocated
.remove(&addr)
.ok_or(BuddyError::InvalidAddress(addr))?
};
self.stats.record_deallocation(order_to_size(order));
self.merge_block(addr, order);
Ok(())
}
fn merge_block(&self, addr: usize, order: u8) {
let mut current_addr = addr;
let mut current_order = order;
while current_order < self.max_order {
let buddy = buddy_addr(current_addr, current_order);
let idx = self.list_index(current_order);
let mut list = self.free_lists[idx].lock();
if let Some(pos) = list.iter().position(|&a| a == buddy) {
list.swap_remove(pos);
drop(list);
self.stats.record_merge();
current_addr = current_addr.min(buddy);
current_order += 1;
} else {
list.push(current_addr);
return;
}
}
let idx = self.list_index(current_order);
self.free_lists[idx].lock().push(current_addr);
}
pub fn contains(&self, addr: usize) -> bool {
addr >= self.base && addr < self.base + self.size
}
pub fn stats(&self) -> &BuddyStats {
&self.stats
}
pub fn free_block_counts(&self) -> Vec<(u8, usize)> {
let mut counts = Vec::new();
for order in self.min_order..=self.max_order {
let idx = self.list_index(order);
let count = self.free_lists[idx].lock().len();
counts.push((order, count));
}
counts
}
pub fn free_bytes(&self) -> usize {
let mut total = 0;
for order in self.min_order..=self.max_order {
let idx = self.list_index(order);
let count = self.free_lists[idx].lock().len();
total += count * order_to_size(order);
}
total
}
}
pub struct BuddyAllocator {
pools: RwLock<Vec<MemoryPool>>,
default_pool_size: usize,
next_base: AtomicUsize,
stats: BuddyStats,
}
impl BuddyAllocator {
pub fn new() -> Self {
Self::with_pool_size(DEFAULT_POOL_SIZE)
}
pub fn with_pool_size(pool_size: usize) -> Self {
let pool_size = pool_size.next_power_of_two();
Self {
pools: RwLock::new(Vec::new()),
default_pool_size: pool_size,
next_base: AtomicUsize::new(0x1000), stats: BuddyStats::new(),
}
}
pub fn add_pool(&self, size: usize) -> Result<usize, BuddyError> {
let size = size.next_power_of_two();
let base = self.next_base.fetch_add(size, Ordering::SeqCst);
let pool = MemoryPool::new(base, size)?;
let pool_id = {
let mut pools = self.pools.write();
let id = pools.len();
pools.push(pool);
id
};
Ok(pool_id)
}
fn ensure_pool(&self) -> Result<(), BuddyError> {
let pools = self.pools.read();
if pools.is_empty() {
drop(pools);
self.add_pool(self.default_pool_size)?;
}
Ok(())
}
pub fn allocate(&self, size: usize) -> Result<usize, BuddyError> {
if size == 0 {
return Err(BuddyError::ZeroSize);
}
self.ensure_pool()?;
let pools = self.pools.read();
for pool in pools.iter() {
if let Ok(addr) = pool.allocate(size) {
self.stats
.record_allocation(order_to_size(size_to_order(size)));
return Ok(addr);
}
}
drop(pools);
let required_size = size.next_power_of_two().max(self.default_pool_size);
self.add_pool(required_size)?;
let pools = self.pools.read();
if let Some(pool) = pools.last()
&& let Ok(addr) = pool.allocate(size)
{
self.stats
.record_allocation(order_to_size(size_to_order(size)));
return Ok(addr);
}
self.stats.record_failed_allocation();
Err(BuddyError::OutOfMemory)
}
pub fn deallocate(&self, addr: usize) -> Result<(), BuddyError> {
let pools = self.pools.read();
for pool in pools.iter() {
let has_addr = pool.allocated.read().contains_key(&addr);
if has_addr {
let order = {
let allocated = pool.allocated.read();
allocated.get(&addr).copied()
};
if let Some(order) = order {
self.stats.record_deallocation(order_to_size(order));
}
return pool.deallocate(addr);
}
}
Err(BuddyError::InvalidAddress(addr))
}
pub fn stats(&self) -> &BuddyStats {
&self.stats
}
pub fn total_free_bytes(&self) -> usize {
let pools = self.pools.read();
pools.iter().map(|p| p.free_bytes()).sum()
}
pub fn pool_count(&self) -> usize {
self.pools.read().len()
}
}
impl Default for BuddyAllocator {
fn default() -> Self {
Self::new()
}
}
pub struct TypedBuddyAllocator<T> {
allocator: BuddyAllocator,
_marker: std::marker::PhantomData<T>,
}
impl<T> TypedBuddyAllocator<T> {
pub fn new() -> Self {
Self {
allocator: BuddyAllocator::new(),
_marker: std::marker::PhantomData,
}
}
pub fn allocate_one(&self) -> Result<usize, BuddyError> {
self.allocator.allocate(std::mem::size_of::<T>())
}
pub fn allocate_array(&self, count: usize) -> Result<usize, BuddyError> {
self.allocator.allocate(std::mem::size_of::<T>() * count)
}
pub fn deallocate(&self, addr: usize) -> Result<(), BuddyError> {
self.allocator.deallocate(addr)
}
}
impl<T> Default for TypedBuddyAllocator<T> {
fn default() -> Self {
Self::new()
}
}
pub struct SlabAllocator {
object_size: usize,
objects_per_slab: usize,
buddy: BuddyAllocator,
slabs: RwLock<HashMap<usize, Vec<usize>>>,
allocated: RwLock<HashMap<usize, usize>>,
stats: BuddyStats,
}
impl SlabAllocator {
pub fn new(object_size: usize) -> Self {
let object_size = object_size.max(8).next_power_of_two();
let slab_size = 4096usize;
let objects_per_slab = slab_size / object_size;
Self {
object_size,
objects_per_slab: objects_per_slab.max(1),
buddy: BuddyAllocator::new(),
slabs: RwLock::new(HashMap::new()),
allocated: RwLock::new(HashMap::new()),
stats: BuddyStats::new(),
}
}
pub fn allocate(&self) -> Result<usize, BuddyError> {
{
let mut slabs = self.slabs.write();
for (base, free_list) in slabs.iter_mut() {
if let Some(offset) = free_list.pop() {
let addr = base + offset;
self.allocated.write().insert(addr, *base);
self.stats.record_allocation(self.object_size);
return Ok(addr);
}
}
}
let slab_size = self.object_size * self.objects_per_slab;
let slab_base = self.buddy.allocate(slab_size)?;
let mut free_list = Vec::with_capacity(self.objects_per_slab - 1);
for i in 1..self.objects_per_slab {
free_list.push(i * self.object_size);
}
self.slabs.write().insert(slab_base, free_list);
self.allocated.write().insert(slab_base, slab_base);
self.stats.record_allocation(self.object_size);
Ok(slab_base)
}
pub fn deallocate(&self, addr: usize) -> Result<(), BuddyError> {
let slab_base = self
.allocated
.write()
.remove(&addr)
.ok_or(BuddyError::InvalidAddress(addr))?;
let offset = addr - slab_base;
self.slabs
.write()
.get_mut(&slab_base)
.ok_or(BuddyError::BlockNotFound(slab_base))?
.push(offset);
self.stats.record_deallocation(self.object_size);
Ok(())
}
pub fn stats(&self) -> &BuddyStats {
&self.stats
}
pub fn object_size(&self) -> usize {
self.object_size
}
}
pub struct BuddyArena {
buddy: BuddyAllocator,
current_block: Mutex<Option<ArenaBlock>>,
block_size: usize,
blocks: RwLock<Vec<usize>>,
}
struct ArenaBlock {
base: usize,
offset: usize,
size: usize,
}
impl BuddyArena {
pub fn new(block_size: usize) -> Self {
let block_size = block_size.next_power_of_two();
Self {
buddy: BuddyAllocator::new(),
current_block: Mutex::new(None),
block_size,
blocks: RwLock::new(Vec::new()),
}
}
pub fn allocate(&self, size: usize, align: usize) -> Result<usize, BuddyError> {
if size == 0 {
return Err(BuddyError::ZeroSize);
}
let mut current = self.current_block.lock();
if let Some(ref mut block) = *current {
let aligned_offset = (block.offset + align - 1) & !(align - 1);
if aligned_offset + size <= block.size {
block.offset = aligned_offset + size;
return Ok(block.base + aligned_offset);
}
}
let new_size = size.max(self.block_size).next_power_of_two();
let base = self.buddy.allocate(new_size)?;
self.blocks.write().push(base);
let aligned_offset = (align - 1) & !(align - 1);
*current = Some(ArenaBlock {
base,
offset: aligned_offset + size,
size: new_size,
});
Ok(base + aligned_offset)
}
pub fn allocate_val<T>(&self, _val: T) -> Result<usize, BuddyError> {
self.allocate(std::mem::size_of::<T>(), std::mem::align_of::<T>())
}
pub fn reset(&self) {
let mut current = self.current_block.lock();
*current = None;
let blocks = std::mem::take(&mut *self.blocks.write());
for block in blocks {
let _ = self.buddy.deallocate(block);
}
}
pub fn allocated_size(&self) -> usize {
self.blocks.read().len() * self.block_size
}
}
impl Drop for BuddyArena {
fn drop(&mut self) {
self.reset();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_to_order() {
assert_eq!(size_to_order(1), 4); assert_eq!(size_to_order(16), 4); assert_eq!(size_to_order(17), 5); assert_eq!(size_to_order(32), 5);
assert_eq!(size_to_order(64), 6);
assert_eq!(size_to_order(1024), 10);
assert_eq!(size_to_order(1025), 11); }
#[test]
fn test_order_to_size() {
assert_eq!(order_to_size(4), 16);
assert_eq!(order_to_size(5), 32);
assert_eq!(order_to_size(10), 1024);
assert_eq!(order_to_size(20), 1 << 20);
}
#[test]
fn test_buddy_addr() {
assert_eq!(buddy_addr(0, 4), 16);
assert_eq!(buddy_addr(16, 4), 0);
assert_eq!(buddy_addr(0, 5), 32);
assert_eq!(buddy_addr(32, 5), 0);
assert_eq!(buddy_addr(64, 5), 96);
assert_eq!(buddy_addr(96, 5), 64);
}
#[test]
fn test_memory_pool_basic() {
let pool = MemoryPool::new(0, 1024).unwrap();
let addr1 = pool.allocate(16).unwrap();
assert!(pool.contains(addr1));
let addr2 = pool.allocate(16).unwrap();
assert_ne!(addr1, addr2);
pool.deallocate(addr1).unwrap();
pool.deallocate(addr2).unwrap();
}
#[test]
fn test_memory_pool_splitting() {
let pool = MemoryPool::new(0, 256).unwrap();
let addr = pool.allocate(16).unwrap();
assert!(pool.stats().splits.load(Ordering::Relaxed) > 0);
pool.deallocate(addr).unwrap();
}
#[test]
fn test_memory_pool_merging() {
let pool = MemoryPool::new(0, 256).unwrap();
let addr1 = pool.allocate(16).unwrap();
let addr2 = pool.allocate(16).unwrap();
pool.deallocate(addr1).unwrap();
pool.deallocate(addr2).unwrap();
assert!(pool.stats().merges.load(Ordering::Relaxed) > 0);
let addr3 = pool.allocate(256).unwrap();
assert_eq!(addr3, 0);
}
#[test]
fn test_memory_pool_out_of_memory() {
let pool = MemoryPool::new(0, 256).unwrap();
let mut addrs = Vec::new();
for _ in 0..16 {
addrs.push(pool.allocate(16).unwrap());
}
assert!(matches!(pool.allocate(16), Err(BuddyError::OutOfMemory)));
pool.deallocate(addrs.pop().unwrap()).unwrap();
assert!(pool.allocate(16).is_ok());
}
#[test]
fn test_buddy_allocator_basic() {
let alloc = BuddyAllocator::new();
let addr1 = alloc.allocate(100).unwrap();
let addr2 = alloc.allocate(200).unwrap();
assert_ne!(addr1, addr2);
alloc.deallocate(addr1).unwrap();
alloc.deallocate(addr2).unwrap();
}
#[test]
fn test_buddy_allocator_auto_pool() {
let alloc = BuddyAllocator::with_pool_size(1024);
assert_eq!(alloc.pool_count(), 0);
let _ = alloc.allocate(16).unwrap();
assert_eq!(alloc.pool_count(), 1);
}
#[test]
fn test_buddy_allocator_multiple_pools() {
let alloc = BuddyAllocator::with_pool_size(256);
let mut addrs = Vec::new();
for _ in 0..32 {
addrs.push(alloc.allocate(16).unwrap());
}
assert!(alloc.pool_count() > 1);
for addr in addrs {
alloc.deallocate(addr).unwrap();
}
}
#[test]
fn test_typed_allocator() {
#[repr(C)]
struct MyStruct {
a: u64,
b: u32,
c: u16,
}
let alloc = TypedBuddyAllocator::<MyStruct>::new();
let addr1 = alloc.allocate_one().unwrap();
let addr2 = alloc.allocate_array(10).unwrap();
assert_ne!(addr1, addr2);
alloc.deallocate(addr1).unwrap();
alloc.deallocate(addr2).unwrap();
}
#[test]
fn test_slab_allocator() {
let slab = SlabAllocator::new(32);
let mut addrs = Vec::new();
for _ in 0..10 {
addrs.push(slab.allocate().unwrap());
}
for i in 0..addrs.len() {
for j in (i + 1)..addrs.len() {
assert_ne!(addrs[i], addrs[j]);
}
}
for addr in addrs {
slab.deallocate(addr).unwrap();
}
}
#[test]
fn test_slab_reuse() {
let slab = SlabAllocator::new(64);
let addr1 = slab.allocate().unwrap();
slab.deallocate(addr1).unwrap();
let addr2 = slab.allocate().unwrap();
assert_eq!(addr1, addr2);
}
#[test]
fn test_buddy_arena() {
let arena = BuddyArena::new(4096);
let addr1 = arena.allocate(100, 8).unwrap();
let addr2 = arena.allocate(200, 16).unwrap();
let addr3 = arena.allocate(50, 4).unwrap();
assert_ne!(addr1, addr2);
assert_ne!(addr2, addr3);
assert_ne!(addr1, addr3);
assert_eq!(addr2 % 16, 0);
arena.reset();
let _ = arena.allocate(100, 8).unwrap();
}
#[test]
fn test_arena_large_allocation() {
let arena = BuddyArena::new(256);
let result = arena.allocate(1024, 8);
assert!(result.is_ok(), "Allocation failed: {:?}", result);
let addr = result.unwrap();
println!("Arena allocated address: {:#x}", addr);
}
#[test]
fn test_stats_tracking() {
let pool = MemoryPool::new(0, 1024).unwrap();
let addr = pool.allocate(64).unwrap();
assert!(pool.stats().allocations.load(Ordering::Relaxed) > 0);
assert!(pool.stats().allocated_bytes.load(Ordering::Relaxed) > 0);
pool.deallocate(addr).unwrap();
assert!(pool.stats().deallocations.load(Ordering::Relaxed) > 0);
}
#[test]
fn test_free_bytes_tracking() {
let pool = MemoryPool::new(0, 1024).unwrap();
let initial_free = pool.free_bytes();
assert_eq!(initial_free, 1024);
let addr = pool.allocate(64).unwrap();
let after_alloc = pool.free_bytes();
assert!(after_alloc < initial_free);
pool.deallocate(addr).unwrap();
let after_free = pool.free_bytes();
assert_eq!(after_free, initial_free);
}
#[test]
fn test_double_free() {
let pool = MemoryPool::new(0, 1024).unwrap();
let addr = pool.allocate(64).unwrap();
pool.deallocate(addr).unwrap();
assert!(matches!(
pool.deallocate(addr),
Err(BuddyError::InvalidAddress(_))
));
}
#[test]
fn test_invalid_address() {
let pool = MemoryPool::new(0, 1024).unwrap();
assert!(matches!(
pool.deallocate(999),
Err(BuddyError::InvalidAddress(_))
));
}
#[test]
fn test_concurrent_allocations() {
use std::sync::Arc;
use std::thread;
let alloc = Arc::new(BuddyAllocator::new());
let mut handles = Vec::new();
for _ in 0..2 {
let alloc = Arc::clone(&alloc);
handles.push(thread::spawn(move || {
let mut addrs = Vec::new();
for _ in 0..10 {
if let Ok(addr) = alloc.allocate(32) {
addrs.push(addr);
}
}
std::thread::sleep(std::time::Duration::from_millis(1));
for addr in addrs {
let _ = alloc.deallocate(addr);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
}