use rvm_types::{PhysAddr, RvmError, RvmResult};
use crate::PAGE_SIZE;
const MAX_ORDER: usize = 10;
const fn words_for_bits(bits: usize) -> usize {
bits.div_ceil(64)
}
const fn total_bitmap_bits(total_pages: usize) -> usize {
let mut bits = 0;
let mut order = 0;
while order <= MAX_ORDER {
bits += total_pages >> order;
order += 1;
}
bits
}
pub struct BuddyAllocator<const TOTAL_PAGES: usize, const BITMAP_WORDS: usize> {
base: PhysAddr,
bitmap: [u64; BITMAP_WORDS],
bit_offsets: [usize; MAX_ORDER + 1],
}
impl<const TOTAL_PAGES: usize, const BITMAP_WORDS: usize>
BuddyAllocator<TOTAL_PAGES, BITMAP_WORDS>
{
pub const REQUIRED_BITMAP_WORDS: usize = words_for_bits(total_bitmap_bits(TOTAL_PAGES));
pub fn new(base: PhysAddr) -> RvmResult<Self> {
if !base.is_page_aligned() {
return Err(RvmError::AlignmentError);
}
if BITMAP_WORDS < Self::REQUIRED_BITMAP_WORDS {
return Err(RvmError::ResourceLimitExceeded);
}
let mut bit_offsets = [0usize; MAX_ORDER + 1];
let mut cumulative = 0;
let mut o = 0;
while o <= MAX_ORDER {
bit_offsets[o] = cumulative;
cumulative += TOTAL_PAGES >> o;
o += 1;
}
let mut alloc = Self {
base,
bitmap: [0u64; BITMAP_WORDS],
bit_offsets,
};
alloc.init_free_all();
Ok(alloc)
}
fn init_free_all(&mut self) {
self.bitmap.fill(0);
let max_usable_order = Self::max_usable_order();
let block_count = TOTAL_PAGES >> max_usable_order;
for blk in 0..block_count {
self.set_free(max_usable_order, blk);
}
}
const fn max_usable_order() -> usize {
let mut order = MAX_ORDER;
while order > 0 && (1usize << order) > TOTAL_PAGES {
order -= 1;
}
order
}
pub fn alloc_pages(&mut self, order: usize) -> RvmResult<PhysAddr> {
if order > Self::max_usable_order() {
return Err(RvmError::OutOfMemory);
}
if let Some(blk) = self.find_first_free(order) {
self.clear_free(order, blk);
let page_offset = blk << order;
let addr = self.base.as_u64() + (page_offset as u64 * PAGE_SIZE as u64);
return Ok(PhysAddr::new(addr));
}
let mut split_order = order + 1;
while split_order <= Self::max_usable_order() {
if let Some(blk) = self.find_first_free(split_order) {
self.clear_free(split_order, blk);
let mut current_order = split_order;
let mut current_blk = blk;
while current_order > order {
current_order -= 1;
let left_child = current_blk * 2;
let right_child = left_child + 1;
self.set_free(current_order, right_child);
current_blk = left_child;
}
let page_offset = current_blk << order;
let addr = self.base.as_u64() + (page_offset as u64 * PAGE_SIZE as u64);
return Ok(PhysAddr::new(addr));
}
split_order += 1;
}
Err(RvmError::OutOfMemory)
}
pub fn free_pages(&mut self, addr: PhysAddr, order: usize) -> RvmResult<()> {
if order > Self::max_usable_order() {
return Err(RvmError::InvalidTierTransition);
}
if addr.as_u64() < self.base.as_u64() {
return Err(RvmError::AlignmentError);
}
let offset_bytes = addr.as_u64() - self.base.as_u64();
if offset_bytes % (PAGE_SIZE as u64) != 0 {
return Err(RvmError::AlignmentError);
}
#[allow(clippy::cast_possible_truncation)]
let page_offset = (offset_bytes / PAGE_SIZE as u64) as usize;
if page_offset >= TOTAL_PAGES {
return Err(RvmError::AlignmentError);
}
let block_index = page_offset >> order;
if (block_index << order) != page_offset {
return Err(RvmError::AlignmentError);
}
if self.is_block_free(order, block_index) {
return Err(RvmError::InternalError);
}
self.set_free(order, block_index);
self.coalesce(order, block_index);
Ok(())
}
#[must_use]
pub fn free_page_count(&self) -> usize {
let mut count = 0;
let max_order = Self::max_usable_order();
let mut order = 0;
while order <= max_order {
let block_count = TOTAL_PAGES >> order;
for blk in 0..block_count {
if self.is_free(order, blk) {
count += 1 << order;
}
}
order += 1;
}
count
}
fn coalesce(&mut self, order: usize, block_index: usize) {
let mut current_order = order;
let mut current_blk = block_index;
while current_order < Self::max_usable_order() {
let buddy = current_blk ^ 1; let block_count = TOTAL_PAGES >> current_order;
if buddy >= block_count {
break; }
if !self.is_free(current_order, buddy) {
break; }
self.clear_free(current_order, current_blk);
self.clear_free(current_order, buddy);
current_order += 1;
current_blk /= 2;
self.set_free(current_order, current_blk);
}
}
fn is_block_free(&self, order: usize, block_index: usize) -> bool {
if self.is_free(order, block_index) {
return true;
}
let mut o = order + 1;
let mut blk = block_index / 2;
while o <= Self::max_usable_order() {
if self.is_free(o, blk) {
return true;
}
o += 1;
blk /= 2;
}
false
}
fn find_first_free(&self, order: usize) -> Option<usize> {
let block_count = TOTAL_PAGES >> order;
if block_count == 0 {
return None;
}
let base_bit = self.bit_offsets[order];
let start_word = base_bit / 64;
let start_bit_in_word = base_bit % 64;
let mut remaining = block_count;
let mut word_idx = start_word;
let mut bit_offset_in_level = 0usize;
if start_bit_in_word != 0 && word_idx < BITMAP_WORDS {
let mask = self.bitmap[word_idx] >> start_bit_in_word;
if mask != 0 {
let tz = mask.trailing_zeros() as usize;
if tz < remaining && (start_bit_in_word + tz) < 64 {
return Some(tz);
}
}
let bits_in_first_word = 64 - start_bit_in_word;
let consumed = bits_in_first_word.min(remaining);
remaining = remaining.saturating_sub(consumed);
bit_offset_in_level += consumed;
word_idx += 1;
}
while remaining > 0 && word_idx < BITMAP_WORDS {
let word = self.bitmap[word_idx];
if word != 0 {
let tz = word.trailing_zeros() as usize;
if tz < remaining.min(64) {
return Some(bit_offset_in_level + tz);
}
}
let consumed = remaining.min(64);
remaining -= consumed;
bit_offset_in_level += consumed;
word_idx += 1;
}
None
}
#[inline]
fn bit_offset(&self, order: usize, blk: usize) -> usize {
self.bit_offsets[order] + blk
}
#[inline]
fn is_free(&self, order: usize, blk: usize) -> bool {
let bit = self.bit_offset(order, blk);
let word = bit / 64;
let bit_in_word = bit % 64;
if word >= BITMAP_WORDS {
return false;
}
(self.bitmap[word] >> bit_in_word) & 1 == 1
}
#[inline]
fn set_free(&mut self, order: usize, blk: usize) {
let bit = self.bit_offset(order, blk);
let word = bit / 64;
let bit_in_word = bit % 64;
if word < BITMAP_WORDS {
self.bitmap[word] |= 1u64 << bit_in_word;
}
}
#[inline]
fn clear_free(&mut self, order: usize, blk: usize) {
let bit = self.bit_offset(order, blk);
let word = bit / 64;
let bit_in_word = bit % 64;
if word < BITMAP_WORDS {
self.bitmap[word] &= !(1u64 << bit_in_word);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type SmallAllocator = BuddyAllocator<16, 2>;
fn base() -> PhysAddr {
PhysAddr::new(0x1000_0000)
}
#[test]
fn create_allocator() {
let alloc = SmallAllocator::new(base()).unwrap();
assert_eq!(alloc.free_page_count(), 16);
}
#[test]
fn unaligned_base_fails() {
assert!(matches!(
SmallAllocator::new(PhysAddr::new(0x1000_0001)),
Err(RvmError::AlignmentError)
));
}
#[test]
fn alloc_single_page() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let addr = alloc.alloc_pages(0).unwrap();
assert!(addr.is_page_aligned());
assert!(addr.as_u64() >= base().as_u64());
assert_eq!(alloc.free_page_count(), 15);
}
#[test]
fn alloc_all_pages_individually() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let mut addrs = [PhysAddr::new(0); 16];
for (i, addr) in addrs.iter_mut().enumerate() {
*addr = alloc.alloc_pages(0).unwrap();
let _ = i;
}
assert_eq!(alloc.free_page_count(), 0);
assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
for (i, a) in addrs.iter().enumerate() {
assert!(a.is_page_aligned());
for b in &addrs[(i + 1)..] {
assert_ne!(a, b);
}
}
}
#[test]
fn alloc_order_2() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let addr = alloc.alloc_pages(2).unwrap();
assert!(addr.is_page_aligned());
assert_eq!(alloc.free_page_count(), 12);
}
#[test]
fn alloc_too_large_fails() {
let mut alloc = SmallAllocator::new(base()).unwrap();
assert_eq!(alloc.alloc_pages(5), Err(RvmError::OutOfMemory));
}
#[test]
fn free_and_realloc() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let addr = alloc.alloc_pages(0).unwrap();
assert_eq!(alloc.free_page_count(), 15);
alloc.free_pages(addr, 0).unwrap();
assert_eq!(alloc.free_page_count(), 16);
let addr2 = alloc.alloc_pages(0).unwrap();
assert!(addr2.is_page_aligned());
}
#[test]
fn free_invalid_address() {
let mut alloc = SmallAllocator::new(base()).unwrap();
assert!(alloc.free_pages(PhysAddr::new(0), 0).is_err());
assert!(alloc
.free_pages(PhysAddr::new(base().as_u64() + 1), 0)
.is_err());
}
#[test]
fn double_free_detected() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let addr = alloc.alloc_pages(0).unwrap();
alloc.free_pages(addr, 0).unwrap();
assert_eq!(alloc.free_pages(addr, 0), Err(RvmError::InternalError));
}
#[test]
fn buddy_coalescing() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let a = alloc.alloc_pages(0).unwrap();
let b = alloc.alloc_pages(0).unwrap();
assert_eq!(alloc.free_page_count(), 14);
alloc.free_pages(a, 0).unwrap();
alloc.free_pages(b, 0).unwrap();
assert_eq!(alloc.free_page_count(), 16);
let big = alloc.alloc_pages(4).unwrap();
assert!(big.is_page_aligned());
assert_eq!(alloc.free_page_count(), 0);
}
#[test]
fn alloc_mixed_orders() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let _a = alloc.alloc_pages(0).unwrap(); let _b = alloc.alloc_pages(1).unwrap(); let _c = alloc.alloc_pages(2).unwrap(); let _d = alloc.alloc_pages(3).unwrap(); assert_eq!(alloc.free_page_count(), 1);
let _e = alloc.alloc_pages(0).unwrap();
assert_eq!(alloc.free_page_count(), 0);
assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
}
type MediumAllocator = BuddyAllocator<256, 16>;
#[test]
fn medium_allocator_full_cycle() {
let mut alloc = MediumAllocator::new(base()).unwrap();
assert_eq!(alloc.free_page_count(), 256);
let mut addrs = [PhysAddr::new(0); 64];
for addr in &mut addrs {
*addr = alloc.alloc_pages(0).unwrap();
}
assert_eq!(alloc.free_page_count(), 192);
for addr in &addrs {
alloc.free_pages(*addr, 0).unwrap();
}
assert_eq!(alloc.free_page_count(), 256);
}
#[test]
fn full_allocation_pressure_order_0() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let mut addrs = [PhysAddr::new(0); 16];
for addr in &mut addrs {
*addr = alloc.alloc_pages(0).unwrap();
}
assert_eq!(alloc.free_page_count(), 0);
assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
alloc.free_pages(addrs[7], 0).unwrap();
assert_eq!(alloc.free_page_count(), 1);
let reused = alloc.alloc_pages(0).unwrap();
assert!(reused.is_page_aligned());
assert_eq!(alloc.free_page_count(), 0);
}
#[test]
fn full_allocation_pressure_mixed_orders() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let addr_8pg = alloc.alloc_pages(3).unwrap(); let addr_4pg = alloc.alloc_pages(2).unwrap(); let addr_2pg = alloc.alloc_pages(1).unwrap(); let addr_1pg_d = alloc.alloc_pages(0).unwrap(); let addr_1pg_e = alloc.alloc_pages(0).unwrap(); assert_eq!(alloc.free_page_count(), 0);
alloc.free_pages(addr_1pg_e, 0).unwrap();
alloc.free_pages(addr_1pg_d, 0).unwrap();
assert_eq!(alloc.free_page_count(), 2);
alloc.free_pages(addr_2pg, 1).unwrap();
assert_eq!(alloc.free_page_count(), 4);
alloc.free_pages(addr_4pg, 2).unwrap();
assert_eq!(alloc.free_page_count(), 8);
alloc.free_pages(addr_8pg, 3).unwrap();
assert_eq!(alloc.free_page_count(), 16); }
#[test]
fn free_wrong_order_size_detected() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let _addr = alloc.alloc_pages(1).unwrap();
}
#[test]
fn alloc_after_partial_free_coalescing() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let mut addrs = [PhysAddr::new(0); 16];
for addr in &mut addrs {
*addr = alloc.alloc_pages(0).unwrap();
}
assert_eq!(alloc.free_page_count(), 0);
alloc.free_pages(addrs[0], 0).unwrap();
alloc.free_pages(addrs[1], 0).unwrap();
let big = alloc.alloc_pages(1).unwrap();
assert!(big.is_page_aligned());
assert_eq!(alloc.free_page_count(), 0);
}
#[test]
fn medium_allocator_full_pressure_and_recovery() {
let mut alloc = MediumAllocator::new(base()).unwrap();
let mut addrs = [PhysAddr::new(0); 256];
for addr in &mut addrs {
*addr = alloc.alloc_pages(0).unwrap();
}
assert_eq!(alloc.free_page_count(), 0);
assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
for addr in &addrs {
alloc.free_pages(*addr, 0).unwrap();
}
assert_eq!(alloc.free_page_count(), 256);
let big = alloc.alloc_pages(8).unwrap(); assert!(big.is_page_aligned());
assert_eq!(alloc.free_page_count(), 0);
}
#[test]
fn free_beyond_total_pages_fails() {
let mut alloc = SmallAllocator::new(base()).unwrap();
let beyond = PhysAddr::new(base().as_u64() + 16 * PAGE_SIZE as u64);
assert!(alloc.free_pages(beyond, 0).is_err());
}
}