1use rvm_types::{PhysAddr, RvmError, RvmResult};
18
19use crate::PAGE_SIZE;
20
21const MAX_ORDER: usize = 10; const fn words_for_bits(bits: usize) -> usize {
26 bits.div_ceil(64)
27}
28
29const fn total_bitmap_bits(total_pages: usize) -> usize {
37 let mut bits = 0;
38 let mut order = 0;
39 while order <= MAX_ORDER {
40 bits += total_pages >> order;
41 order += 1;
42 }
43 bits
44}
45
46pub struct BuddyAllocator<const TOTAL_PAGES: usize, const BITMAP_WORDS: usize> {
58 base: PhysAddr,
60 bitmap: [u64; BITMAP_WORDS],
62 bit_offsets: [usize; MAX_ORDER + 1],
66}
67
68impl<const TOTAL_PAGES: usize, const BITMAP_WORDS: usize>
69 BuddyAllocator<TOTAL_PAGES, BITMAP_WORDS>
70{
71 pub const REQUIRED_BITMAP_WORDS: usize = words_for_bits(total_bitmap_bits(TOTAL_PAGES));
73
74 pub fn new(base: PhysAddr) -> RvmResult<Self> {
83 if !base.is_page_aligned() {
84 return Err(RvmError::AlignmentError);
85 }
86 if BITMAP_WORDS < Self::REQUIRED_BITMAP_WORDS {
88 return Err(RvmError::ResourceLimitExceeded);
89 }
90
91 let mut bit_offsets = [0usize; MAX_ORDER + 1];
93 let mut cumulative = 0;
94 let mut o = 0;
95 while o <= MAX_ORDER {
96 bit_offsets[o] = cumulative;
97 cumulative += TOTAL_PAGES >> o;
98 o += 1;
99 }
100
101 let mut alloc = Self {
102 base,
103 bitmap: [0u64; BITMAP_WORDS],
104 bit_offsets,
105 };
106 alloc.init_free_all();
107 Ok(alloc)
108 }
109
110 fn init_free_all(&mut self) {
115 self.bitmap.fill(0);
117
118 let max_usable_order = Self::max_usable_order();
120 let block_count = TOTAL_PAGES >> max_usable_order;
121 for blk in 0..block_count {
122 self.set_free(max_usable_order, blk);
123 }
124 }
125
126 const fn max_usable_order() -> usize {
128 let mut order = MAX_ORDER;
129 while order > 0 && (1usize << order) > TOTAL_PAGES {
131 order -= 1;
132 }
133 order
134 }
135
136 pub fn alloc_pages(&mut self, order: usize) -> RvmResult<PhysAddr> {
148 if order > Self::max_usable_order() {
149 return Err(RvmError::OutOfMemory);
150 }
151
152 if let Some(blk) = self.find_first_free(order) {
154 self.clear_free(order, blk);
155 let page_offset = blk << order;
156 let addr = self.base.as_u64() + (page_offset as u64 * PAGE_SIZE as u64);
157 return Ok(PhysAddr::new(addr));
158 }
159
160 let mut split_order = order + 1;
162 while split_order <= Self::max_usable_order() {
163 if let Some(blk) = self.find_first_free(split_order) {
164 self.clear_free(split_order, blk);
166
167 let mut current_order = split_order;
169 let mut current_blk = blk;
170 while current_order > order {
171 current_order -= 1;
172 let left_child = current_blk * 2;
174 let right_child = left_child + 1;
175 self.set_free(current_order, right_child);
177 current_blk = left_child;
179 }
180
181 let page_offset = current_blk << order;
182 let addr = self.base.as_u64() + (page_offset as u64 * PAGE_SIZE as u64);
183 return Ok(PhysAddr::new(addr));
184 }
185
186 split_order += 1;
187 }
188
189 Err(RvmError::OutOfMemory)
190 }
191
192 pub fn free_pages(&mut self, addr: PhysAddr, order: usize) -> RvmResult<()> {
202 if order > Self::max_usable_order() {
203 return Err(RvmError::InvalidTierTransition);
204 }
205 if addr.as_u64() < self.base.as_u64() {
206 return Err(RvmError::AlignmentError);
207 }
208
209 let offset_bytes = addr.as_u64() - self.base.as_u64();
210 if offset_bytes % (PAGE_SIZE as u64) != 0 {
211 return Err(RvmError::AlignmentError);
212 }
213 #[allow(clippy::cast_possible_truncation)]
214 let page_offset = (offset_bytes / PAGE_SIZE as u64) as usize;
215 if page_offset >= TOTAL_PAGES {
216 return Err(RvmError::AlignmentError);
217 }
218
219 let block_index = page_offset >> order;
220
221 if (block_index << order) != page_offset {
223 return Err(RvmError::AlignmentError);
224 }
225
226 if self.is_block_free(order, block_index) {
230 return Err(RvmError::InternalError);
231 }
232
233 self.set_free(order, block_index);
235 self.coalesce(order, block_index);
236
237 Ok(())
238 }
239
240 #[must_use]
242 pub fn free_page_count(&self) -> usize {
243 let mut count = 0;
244 let max_order = Self::max_usable_order();
245 let mut order = 0;
246 while order <= max_order {
247 let block_count = TOTAL_PAGES >> order;
248 for blk in 0..block_count {
249 if self.is_free(order, blk) {
250 count += 1 << order;
251 }
252 }
253 order += 1;
254 }
255 count
256 }
257
258 fn coalesce(&mut self, order: usize, block_index: usize) {
260 let mut current_order = order;
261 let mut current_blk = block_index;
262
263 while current_order < Self::max_usable_order() {
264 let buddy = current_blk ^ 1; let block_count = TOTAL_PAGES >> current_order;
266
267 if buddy >= block_count {
268 break; }
270
271 if !self.is_free(current_order, buddy) {
272 break; }
274
275 self.clear_free(current_order, current_blk);
277 self.clear_free(current_order, buddy);
278
279 current_order += 1;
281 current_blk /= 2;
282 self.set_free(current_order, current_blk);
283 }
284 }
285
286 fn is_block_free(&self, order: usize, block_index: usize) -> bool {
289 if self.is_free(order, block_index) {
290 return true;
291 }
292 let mut o = order + 1;
294 let mut blk = block_index / 2;
295 while o <= Self::max_usable_order() {
296 if self.is_free(o, blk) {
297 return true;
298 }
299 o += 1;
300 blk /= 2;
301 }
302 false
303 }
304
305 fn find_first_free(&self, order: usize) -> Option<usize> {
312 let block_count = TOTAL_PAGES >> order;
313 if block_count == 0 {
314 return None;
315 }
316 let base_bit = self.bit_offsets[order];
317 let start_word = base_bit / 64;
318 let start_bit_in_word = base_bit % 64;
319
320 let mut remaining = block_count;
322 let mut word_idx = start_word;
323 let mut bit_offset_in_level = 0usize;
324
325 if start_bit_in_word != 0 && word_idx < BITMAP_WORDS {
327 let mask = self.bitmap[word_idx] >> start_bit_in_word;
329 if mask != 0 {
330 let tz = mask.trailing_zeros() as usize;
331 if tz < remaining && (start_bit_in_word + tz) < 64 {
332 return Some(tz);
333 }
334 }
335 let bits_in_first_word = 64 - start_bit_in_word;
336 let consumed = bits_in_first_word.min(remaining);
337 remaining = remaining.saturating_sub(consumed);
338 bit_offset_in_level += consumed;
339 word_idx += 1;
340 }
341
342 while remaining > 0 && word_idx < BITMAP_WORDS {
344 let word = self.bitmap[word_idx];
345 if word != 0 {
346 let tz = word.trailing_zeros() as usize;
347 if tz < remaining.min(64) {
348 return Some(bit_offset_in_level + tz);
349 }
350 }
351 let consumed = remaining.min(64);
352 remaining -= consumed;
353 bit_offset_in_level += consumed;
354 word_idx += 1;
355 }
356
357 None
358 }
359
360 #[inline]
363 fn bit_offset(&self, order: usize, blk: usize) -> usize {
364 self.bit_offsets[order] + blk
365 }
366
367 #[inline]
369 fn is_free(&self, order: usize, blk: usize) -> bool {
370 let bit = self.bit_offset(order, blk);
371 let word = bit / 64;
372 let bit_in_word = bit % 64;
373 if word >= BITMAP_WORDS {
374 return false;
375 }
376 (self.bitmap[word] >> bit_in_word) & 1 == 1
377 }
378
379 #[inline]
381 fn set_free(&mut self, order: usize, blk: usize) {
382 let bit = self.bit_offset(order, blk);
383 let word = bit / 64;
384 let bit_in_word = bit % 64;
385 if word < BITMAP_WORDS {
386 self.bitmap[word] |= 1u64 << bit_in_word;
387 }
388 }
389
390 #[inline]
392 fn clear_free(&mut self, order: usize, blk: usize) {
393 let bit = self.bit_offset(order, blk);
394 let word = bit / 64;
395 let bit_in_word = bit % 64;
396 if word < BITMAP_WORDS {
397 self.bitmap[word] &= !(1u64 << bit_in_word);
398 }
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 type SmallAllocator = BuddyAllocator<16, 2>;
410
411 fn base() -> PhysAddr {
412 PhysAddr::new(0x1000_0000)
413 }
414
415 #[test]
416 fn create_allocator() {
417 let alloc = SmallAllocator::new(base()).unwrap();
418 assert_eq!(alloc.free_page_count(), 16);
419 }
420
421 #[test]
422 fn unaligned_base_fails() {
423 assert!(matches!(
424 SmallAllocator::new(PhysAddr::new(0x1000_0001)),
425 Err(RvmError::AlignmentError)
426 ));
427 }
428
429 #[test]
430 fn alloc_single_page() {
431 let mut alloc = SmallAllocator::new(base()).unwrap();
432 let addr = alloc.alloc_pages(0).unwrap();
433 assert!(addr.is_page_aligned());
434 assert!(addr.as_u64() >= base().as_u64());
435 assert_eq!(alloc.free_page_count(), 15);
436 }
437
438 #[test]
439 fn alloc_all_pages_individually() {
440 let mut alloc = SmallAllocator::new(base()).unwrap();
441 let mut addrs = [PhysAddr::new(0); 16];
442 for (i, addr) in addrs.iter_mut().enumerate() {
443 *addr = alloc.alloc_pages(0).unwrap();
444 let _ = i;
445 }
446 assert_eq!(alloc.free_page_count(), 0);
447
448 assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
450
451 for (i, a) in addrs.iter().enumerate() {
453 assert!(a.is_page_aligned());
454 for b in &addrs[(i + 1)..] {
455 assert_ne!(a, b);
456 }
457 }
458 }
459
460 #[test]
461 fn alloc_order_2() {
462 let mut alloc = SmallAllocator::new(base()).unwrap();
463 let addr = alloc.alloc_pages(2).unwrap();
465 assert!(addr.is_page_aligned());
466 assert_eq!(alloc.free_page_count(), 12);
467 }
468
469 #[test]
470 fn alloc_too_large_fails() {
471 let mut alloc = SmallAllocator::new(base()).unwrap();
472 assert_eq!(alloc.alloc_pages(5), Err(RvmError::OutOfMemory));
475 }
476
477 #[test]
478 fn free_and_realloc() {
479 let mut alloc = SmallAllocator::new(base()).unwrap();
480 let addr = alloc.alloc_pages(0).unwrap();
481 assert_eq!(alloc.free_page_count(), 15);
482
483 alloc.free_pages(addr, 0).unwrap();
484 assert_eq!(alloc.free_page_count(), 16);
485
486 let addr2 = alloc.alloc_pages(0).unwrap();
488 assert!(addr2.is_page_aligned());
489 }
490
491 #[test]
492 fn free_invalid_address() {
493 let mut alloc = SmallAllocator::new(base()).unwrap();
494 assert!(alloc.free_pages(PhysAddr::new(0), 0).is_err());
496 assert!(alloc
498 .free_pages(PhysAddr::new(base().as_u64() + 1), 0)
499 .is_err());
500 }
501
502 #[test]
503 fn double_free_detected() {
504 let mut alloc = SmallAllocator::new(base()).unwrap();
505 let addr = alloc.alloc_pages(0).unwrap();
506 alloc.free_pages(addr, 0).unwrap();
507 assert_eq!(alloc.free_pages(addr, 0), Err(RvmError::InternalError));
509 }
510
511 #[test]
512 fn buddy_coalescing() {
513 let mut alloc = SmallAllocator::new(base()).unwrap();
514
515 let a = alloc.alloc_pages(0).unwrap();
517 let b = alloc.alloc_pages(0).unwrap();
518 assert_eq!(alloc.free_page_count(), 14);
519
520 alloc.free_pages(a, 0).unwrap();
522 alloc.free_pages(b, 0).unwrap();
523 assert_eq!(alloc.free_page_count(), 16);
524
525 let big = alloc.alloc_pages(4).unwrap();
528 assert!(big.is_page_aligned());
529 assert_eq!(alloc.free_page_count(), 0);
530 }
531
532 #[test]
533 fn alloc_mixed_orders() {
534 let mut alloc = SmallAllocator::new(base()).unwrap();
535
536 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);
543
544 let _e = alloc.alloc_pages(0).unwrap();
546 assert_eq!(alloc.free_page_count(), 0);
547
548 assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
550 }
551
552 type MediumAllocator = BuddyAllocator<256, 16>;
554
555 #[test]
556 fn medium_allocator_full_cycle() {
557 let mut alloc = MediumAllocator::new(base()).unwrap();
558 assert_eq!(alloc.free_page_count(), 256);
559
560 let mut addrs = [PhysAddr::new(0); 64];
562 for addr in &mut addrs {
563 *addr = alloc.alloc_pages(0).unwrap();
564 }
565 assert_eq!(alloc.free_page_count(), 192);
566
567 for addr in &addrs {
569 alloc.free_pages(*addr, 0).unwrap();
570 }
571 assert_eq!(alloc.free_page_count(), 256);
572 }
573
574 #[test]
579 fn full_allocation_pressure_order_0() {
580 let mut alloc = SmallAllocator::new(base()).unwrap();
582 let mut addrs = [PhysAddr::new(0); 16];
583 for addr in &mut addrs {
584 *addr = alloc.alloc_pages(0).unwrap();
585 }
586 assert_eq!(alloc.free_page_count(), 0);
587 assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
588
589 alloc.free_pages(addrs[7], 0).unwrap();
591 assert_eq!(alloc.free_page_count(), 1);
592 let reused = alloc.alloc_pages(0).unwrap();
593 assert!(reused.is_page_aligned());
594 assert_eq!(alloc.free_page_count(), 0);
595 }
596
597 #[test]
598 fn full_allocation_pressure_mixed_orders() {
599 let mut alloc = SmallAllocator::new(base()).unwrap();
602 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);
608
609 alloc.free_pages(addr_1pg_e, 0).unwrap();
611 alloc.free_pages(addr_1pg_d, 0).unwrap();
612 assert_eq!(alloc.free_page_count(), 2);
613
614 alloc.free_pages(addr_2pg, 1).unwrap();
615 assert_eq!(alloc.free_page_count(), 4);
616
617 alloc.free_pages(addr_4pg, 2).unwrap();
618 assert_eq!(alloc.free_page_count(), 8);
619
620 alloc.free_pages(addr_8pg, 3).unwrap();
621 assert_eq!(alloc.free_page_count(), 16); }
623
624 #[test]
625 fn free_wrong_order_size_detected() {
626 let mut alloc = SmallAllocator::new(base()).unwrap();
630 let _addr = alloc.alloc_pages(1).unwrap();
631 }
635
636 #[test]
637 fn alloc_after_partial_free_coalescing() {
638 let mut alloc = SmallAllocator::new(base()).unwrap();
639
640 let mut addrs = [PhysAddr::new(0); 16];
642 for addr in &mut addrs {
643 *addr = alloc.alloc_pages(0).unwrap();
644 }
645 assert_eq!(alloc.free_page_count(), 0);
646
647 alloc.free_pages(addrs[0], 0).unwrap();
649 alloc.free_pages(addrs[1], 0).unwrap();
650
651 let big = alloc.alloc_pages(1).unwrap();
653 assert!(big.is_page_aligned());
654 assert_eq!(alloc.free_page_count(), 0);
655 }
656
657 #[test]
658 fn medium_allocator_full_pressure_and_recovery() {
659 let mut alloc = MediumAllocator::new(base()).unwrap();
660
661 let mut addrs = [PhysAddr::new(0); 256];
663 for addr in &mut addrs {
664 *addr = alloc.alloc_pages(0).unwrap();
665 }
666 assert_eq!(alloc.free_page_count(), 0);
667 assert_eq!(alloc.alloc_pages(0), Err(RvmError::OutOfMemory));
668
669 for addr in &addrs {
671 alloc.free_pages(*addr, 0).unwrap();
672 }
673 assert_eq!(alloc.free_page_count(), 256);
674
675 let big = alloc.alloc_pages(8).unwrap(); assert!(big.is_page_aligned());
679 assert_eq!(alloc.free_page_count(), 0);
680 }
681
682 #[test]
683 fn free_beyond_total_pages_fails() {
684 let mut alloc = SmallAllocator::new(base()).unwrap();
685 let beyond = PhysAddr::new(base().as_u64() + 16 * PAGE_SIZE as u64);
687 assert!(alloc.free_pages(beyond, 0).is_err());
688 }
689}