1use crate::allocator::PhysRange;
2use crate::{AllocError, InitError, PageSize, PhysicalAllocator, Provenance, RegionInit};
3use core::marker::PhantomData;
4use core::num::NonZeroUsize;
5use core::ptr;
6use core::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
7use core::sync::atomic::{AtomicPtr, AtomicUsize};
8
9const SPURIOUS_OOM_RETRIES: usize = 8;
12
13pub trait GateConfig {
17 const SUMMARY_MIN_L1_WORDS: usize;
19 const CURSOR_MIN_SUMMARY_WORDS: usize;
21}
22
23pub struct DefaultGates;
25impl GateConfig for DefaultGates {
26 const SUMMARY_MIN_L1_WORDS: usize = 128;
27 const CURSOR_MIN_SUMMARY_WORDS: usize = 32;
28}
29
30#[inline(always)]
32const fn blocks_at_order(total_frames: usize, k: usize) -> usize {
33 total_frames.div_ceil(1 << k)
34}
35
36#[inline(always)]
38const fn words_for_bits(bits: usize) -> usize {
39 const BPW: usize = usize::BITS as usize;
40 bits.div_ceil(BPW)
41}
42
43const fn alloc_bitmap_words_for(
46 total_frames: usize,
47 orders: usize,
48 summary_min_l1_words: usize,
49) -> usize {
50 let mut total = 0usize;
51 let mut k = 0;
52 while k < orders {
53 let l1 = words_for_bits(blocks_at_order(total_frames, k));
54 let summary = if l1 >= summary_min_l1_words {
55 words_for_bits(l1)
56 } else {
57 0
58 };
59 total += l1 + summary;
60 k += 1;
61 }
62 total
63}
64
65struct InitPlan {
70 base_phys: usize,
74 total_frames: usize,
77 host_idx: usize,
79 reserved_frames: usize,
81}
82
83pub struct SummaryBuddyAllocator<const ORDERS: usize, P: Provenance, G: GateConfig = DefaultGates> {
96 base_frame: PageSize,
97 max_page: PageSize,
100 base_phys: AtomicUsize,
102 total_frames: AtomicUsize,
104 bitmap: AtomicPtr<u8>,
107 order_word_offsets: [AtomicUsize; ORDERS],
109 bitmap_lens: [AtomicUsize; ORDERS],
111 summary_word_offsets: [AtomicUsize; ORDERS],
113 summary_lens: [AtomicUsize; ORDERS],
115 summary_cursor: [AtomicUsize; ORDERS],
117 free_counts: [AtomicUsize; ORDERS],
120 #[cfg(any(feature = "stats", test))]
123 capacity_frames: AtomicUsize,
124 _gates: PhantomData<fn() -> G>,
125 _provenance: PhantomData<fn() -> P>,
126}
127
128impl<const ORDERS: usize, P: Provenance, G: GateConfig> SummaryBuddyAllocator<ORDERS, P, G> {
129 pub const fn new(base_frame: PageSize) -> Self {
137 let max_block = PageSize::from_log2(base_frame.log2() + (ORDERS as u8) - 1);
138 Self::with_max_page(base_frame, max_block)
139 }
140
141 pub const fn with_max_page(base_frame: PageSize, max_page: PageSize) -> Self {
146 assert!(ORDERS > 0, "ORDERS must be > 0");
147 assert!(
148 ORDERS <= usize::BITS as usize,
149 "ORDERS exceeds usize bit width"
150 );
151 assert!(
152 base_frame.bytes() >= align_of::<AtomicUsize>(),
153 "base_frame must be at least word-aligned so the in-pool bitmap is AtomicUsize-aligned"
154 );
155 assert!(
156 (base_frame.log2() as usize) + ORDERS - 1 < usize::BITS as usize,
157 "base_frame.bytes() << (ORDERS-1) overflows usize; reduce ORDERS or base_frame"
158 );
159 assert!(
160 base_frame.log2() <= max_page.log2()
161 && (max_page.log2() as usize) < base_frame.log2() as usize + ORDERS,
162 "max_page must be in base_frame ..= base_frame << (ORDERS-1)"
163 );
164 Self {
165 base_frame,
166 max_page,
167 base_phys: AtomicUsize::new(0),
168 total_frames: AtomicUsize::new(0),
169 bitmap: AtomicPtr::new(ptr::null_mut()),
170 order_word_offsets: [const { AtomicUsize::new(0) }; ORDERS],
171 bitmap_lens: [const { AtomicUsize::new(0) }; ORDERS],
172 summary_word_offsets: [const { AtomicUsize::new(0) }; ORDERS],
173 summary_lens: [const { AtomicUsize::new(0) }; ORDERS],
174 summary_cursor: [const { AtomicUsize::new(0) }; ORDERS],
175 free_counts: [const { AtomicUsize::new(0) }; ORDERS],
176 #[cfg(any(feature = "stats", test))]
177 capacity_frames: AtomicUsize::new(0),
178 _gates: PhantomData,
179 _provenance: PhantomData,
180 }
181 }
182
183 fn validate(
201 &self,
202 phys_base: usize,
203 span_len: usize,
204 usable: &[PhysRange],
205 ) -> Result<InitPlan, InitError> {
206 let frame_bytes = self.base_frame.bytes();
207
208 if !self.bitmap.load(Relaxed).is_null() {
209 return Err(InitError::AlreadyInitialized);
210 }
211
212 if span_len == 0 || !span_len.is_multiple_of(frame_bytes) {
213 return Err(InitError::InvalidSpan);
214 }
215
216 if !phys_base.is_multiple_of(frame_bytes) {
217 return Err(InitError::Misaligned {
218 required: frame_bytes,
219 });
220 }
221
222 let base_phys = self.max_page.align_down(phys_base);
229 let prefix_frames = (phys_base - base_phys) / frame_bytes;
230 let total_frames = prefix_frames + span_len / frame_bytes;
231
232 let span_end = phys_base
235 .checked_add(span_len)
236 .ok_or(InitError::InvalidSpan)?;
237 let mut prev_end = phys_base;
238 for (index, r) in usable.iter().enumerate() {
239 if r.len == 0
240 || !r.base.is_multiple_of(frame_bytes)
241 || !r.len.is_multiple_of(frame_bytes)
242 || r.base < prev_end
243 {
244 return Err(InitError::InvalidUsable { index });
245 }
246 let r_end = r
247 .base
248 .checked_add(r.len)
249 .ok_or(InitError::InvalidUsable { index })?;
250 if r_end > span_end {
251 return Err(InitError::InvalidUsable { index });
252 }
253 prev_end = r_end;
254 }
255
256 let bitmap_words = alloc_bitmap_words_for(total_frames, ORDERS, G::SUMMARY_MIN_L1_WORDS);
257 let bitmap_bytes = bitmap_words * size_of::<usize>();
258 let reserved = bitmap_bytes.div_ceil(frame_bytes);
259 let required_bytes = reserved * frame_bytes;
260
261 let host_idx = usable
263 .iter()
264 .position(|r| r.len >= required_bytes)
265 .ok_or(InitError::MetadataWontFit { required_bytes })?;
266
267 Ok(InitPlan {
268 base_phys,
269 total_frames,
270 host_idx,
271 reserved_frames: reserved,
272 })
273 }
274
275 unsafe fn commit(&self, usable: &[PhysRange], plan: InitPlan) {
287 let frame_bytes = self.base_frame.bytes();
288 let InitPlan {
289 base_phys,
290 total_frames,
291 host_idx,
292 reserved_frames: reserved,
293 } = plan;
294 let reserved_bytes = reserved * frame_bytes;
295
296 let bitmap_phys = usable[host_idx].base;
297 let bitmap_virt = unsafe { P::create(bitmap_phys) }.as_ptr();
301
302 unsafe { ptr::write_bytes(bitmap_virt, 0, reserved_bytes) };
306
307 let mut off = 0usize;
309 for k in 0..ORDERS {
310 self.order_word_offsets[k].store(off, Relaxed);
311 let words = words_for_bits(blocks_at_order(total_frames, k));
312 self.bitmap_lens[k].store(words, Relaxed);
313 off += words;
314 }
315 for k in 0..ORDERS {
316 self.summary_word_offsets[k].store(off, Relaxed);
317 let l1_words = self.bitmap_lens[k].load(Relaxed);
318 let words = if l1_words >= G::SUMMARY_MIN_L1_WORDS {
319 words_for_bits(l1_words)
320 } else {
321 0
322 };
323 self.summary_lens[k].store(words, Relaxed);
324 off += words;
325 }
326 debug_assert_eq!(
327 off,
328 alloc_bitmap_words_for(total_frames, ORDERS, G::SUMMARY_MIN_L1_WORDS),
329 "bitmap layout size mismatch"
330 );
331
332 self.base_phys.store(base_phys, Relaxed);
333 self.total_frames.store(total_frames, Relaxed);
334 self.bitmap.store(bitmap_virt, Relaxed);
335
336 let host_alloc_base = bitmap_phys + reserved_bytes;
338 for (i, r) in usable.iter().enumerate() {
339 let (base, len) = if i == host_idx {
340 (host_alloc_base, r.len - reserved_bytes)
341 } else {
342 (r.base, r.len)
343 };
344 if len > 0 {
345 unsafe { self.add_region(base, len) };
348 }
349 }
350 }
351
352 unsafe fn add_region(&self, base: usize, len: usize) {
368 let base_bytes = self.base_frame.bytes();
369 let base_phys = self.base_phys.load(Relaxed);
370 let total_frames = self.total_frames.load(Relaxed);
371
372 assert!(
374 !self.bitmap.load(Relaxed).is_null(),
375 "add_usable/add_region called before init"
376 );
377 let region_end = base
379 .checked_add(len)
380 .expect("add_region: base + len overflows usize");
381 let span_end = base_phys
382 .checked_add(total_frames * base_bytes)
383 .expect("add_region: span end overflows usize");
384 assert!(
385 base >= base_phys && region_end <= span_end,
386 "add_region: [{base:#x}, {region_end:#x}) falls outside the initialised span [{base_phys:#x}, {span_end:#x})",
387 );
388 debug_assert_eq!(base % base_bytes, 0, "base not aligned to base frame size");
389 debug_assert_eq!(len % base_bytes, 0, "len not a multiple of base frame size");
390 debug_assert!(len > 0, "empty region");
391
392 #[cfg(any(feature = "stats", test))]
393 self.capacity_frames.fetch_add(len / base_bytes, Relaxed);
394
395 let mut addr = base;
396 while addr < region_end {
397 let remaining = region_end - addr;
398 let order = (0..ORDERS).rev().find(|&k| {
399 let block = base_bytes << k;
400 block <= remaining && (addr - base_phys).is_multiple_of(block)
401 });
402 let Some(order) = order else { break };
403 let block_size = base_bytes << order;
404 unsafe { self.dealloc_order(order, addr) };
405 addr += block_size;
406 }
407 }
408
409 #[inline]
417 fn alloc(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
418 let order = Self::order_for(self.base_frame, self.max_page, ps, count)?;
419 if order >= ORDERS {
420 return Err(AllocError::RequestTooLarge);
421 }
422 let mut result = self.alloc_order(order);
423 let mut attempts = 0;
424 while result == Err(AllocError::OutOfMemory) && attempts < SPURIOUS_OOM_RETRIES {
425 core::hint::spin_loop();
426 result = self.alloc_order(order);
427 attempts += 1;
428 }
429 #[cfg(audit)]
430 self.audit();
431 result
432 }
433
434 #[inline]
441 unsafe fn dealloc(&self, ps: PageSize, count: NonZeroUsize, phys: usize) {
442 let order = Self::order_for(self.base_frame, self.max_page, ps, count);
443 debug_assert!(
444 order.is_ok(),
445 "deallocate_physical: invalid page size or count"
446 );
447 if let Ok(order) = order
448 && order < ORDERS
449 {
450 unsafe { self.dealloc_order(order, phys) };
451 }
452 #[cfg(audit)]
453 self.audit();
454 }
455
456 #[inline]
457 fn alloc_order(&self, order: usize) -> Result<usize, AllocError> {
458 let mut k = order;
459 let bitmap_base = self.bitmap.load(Relaxed) as *const AtomicUsize;
463
464 let (found_block, found_order) = 'find: loop {
465 if k >= ORDERS {
466 return Err(AllocError::OutOfMemory);
467 }
468
469 if self.free_counts[k].load(Relaxed) == 0 {
470 k += 1;
471 continue;
472 }
473
474 let n_words = self.bitmap_lens[k].load(Relaxed);
475 let word_off = self.order_word_offsets[k].load(Relaxed);
476 let n_sum = self.summary_lens[k].load(Relaxed);
477
478 if n_sum != 0 {
480 let sum_off = self.summary_word_offsets[k].load(Relaxed);
481 let use_cursor = n_sum >= G::CURSOR_MIN_SUMMARY_WORDS;
482 let cur = if use_cursor {
483 self.summary_cursor[k].load(Relaxed)
484 } else {
485 0
486 };
487 let start = if cur < n_sum { cur } else { 0 };
488 for off in 0..n_sum {
489 let sw = {
490 let t = start + off;
491 if t >= n_sum { t - n_sum } else { t }
492 };
493 let s_cell = unsafe { &*bitmap_base.add(sum_off + sw) };
494 let mut s = s_cell.load(Relaxed);
495 while s != 0 {
496 let s_bit = s.trailing_zeros() as usize;
497 let wi = sw * usize::BITS as usize + s_bit;
498 if wi < n_words {
499 let l1_cell = unsafe { &*bitmap_base.add(word_off + wi) };
500 if let Some(bit) =
501 unsafe { self.try_grab_word(bitmap_base, word_off + wi, k, wi) }
502 {
503 if use_cursor && sw != cur {
504 self.summary_cursor[k].store(sw, Relaxed);
505 }
506 break 'find (wi * usize::BITS as usize + bit, k);
507 }
508 self.clear_summary_bit_then_recheck(s_cell, s_bit, l1_cell);
509 }
510 s &= !(1usize << s_bit);
514 }
515 }
516 }
517
518 if self.free_counts[k].load(Relaxed) != 0 {
520 for wi in 0..n_words {
521 if let Some(bit) =
522 unsafe { self.try_grab_word(bitmap_base, word_off + wi, k, wi) }
523 {
524 break 'find (wi * usize::BITS as usize + bit, k);
525 }
526 }
527 }
528 k += 1;
529 };
530
531 let phys = self.block_to_phys(found_block, found_order);
532
533 let mut block_at_m = found_block;
535 let mut m = found_order;
536 while m > order {
537 m -= 1;
538 unsafe {
539 self.free_block_no_merge(bitmap_base, m, block_at_m * 2 + 1);
540 }
541 block_at_m *= 2;
542 }
543
544 Ok(phys)
545 }
546
547 #[inline]
548 unsafe fn dealloc_order(&self, order: usize, phys: usize) {
549 let mut k = order;
550 let mut block_i = self.phys_to_block(phys, k);
551 let bitmap_base = self.bitmap.load(Relaxed) as *const AtomicUsize;
552 debug_assert!(
555 !bitmap_base.is_null(),
556 "allocator not initialised; call init before deallocate"
557 );
558
559 loop {
560 let wi = block_i / usize::BITS as usize;
561 let (word_idx, i_bit) = self.bit_addr(k, block_i);
562 let j_bit = i_bit ^ 1;
563 let cell = unsafe { &*bitmap_base.add(word_idx) };
566
567 if k + 1 == ORDERS {
568 self.free_counts[k].fetch_add(1, Relaxed);
570 let prev = cell.fetch_or(1usize << i_bit, Release);
571 debug_assert!(
572 prev & (1usize << i_bit) == 0,
573 "double-free: order-{k} block already marked free"
574 );
575 unsafe {
576 self.sync_summary(bitmap_base, cell, k, wi, prev, prev | (1usize << i_bit))
577 };
578 return;
579 }
580
581 loop {
582 let old = cell.load(Acquire);
583 if (old >> j_bit) & 1 == 1 {
584 let new = old & !(1usize << j_bit);
586 if cell
587 .compare_exchange_weak(old, new, AcqRel, Acquire)
588 .is_ok()
589 {
590 self.free_counts[k].fetch_sub(1, Relaxed);
591 unsafe { self.sync_summary(bitmap_base, cell, k, wi, old, new) };
592 break; }
594 } else {
595 debug_assert!(
597 (old >> i_bit) & 1 == 0,
598 "double-free: order-{k} block already marked free"
599 );
600 self.free_counts[k].fetch_add(1, Relaxed);
601 let new = old | (1usize << i_bit);
602 match cell.compare_exchange_weak(old, new, AcqRel, Acquire) {
603 Ok(_) => {
604 unsafe { self.sync_summary(bitmap_base, cell, k, wi, old, new) };
605 return;
606 }
607 Err(_) => {
608 self.free_counts[k].fetch_sub(1, Relaxed);
609 }
610 }
611 }
612 }
613
614 k += 1;
616 block_i = block_i.min(block_i ^ 1) >> 1;
617 }
618 }
619
620 #[inline]
626 fn order_for(
627 base: PageSize,
628 max_page: PageSize,
629 ps: PageSize,
630 count: NonZeroUsize,
631 ) -> Result<usize, AllocError> {
632 if ps.bytes() < base.bytes() || ps.bytes() > max_page.bytes() {
633 return Err(AllocError::InvalidPageSize);
634 }
635 let total = ps
636 .bytes()
637 .checked_mul(count.get())
638 .ok_or(AllocError::RequestTooLarge)?;
639 let frames = ((total - 1) >> base.log2()) + 1;
641 let blocks = frames
642 .checked_next_power_of_two()
643 .ok_or(AllocError::RequestTooLarge)?;
644 Ok(blocks.trailing_zeros() as usize)
645 }
646
647 #[inline(always)]
648 fn phys_to_block(&self, phys: usize, order: usize) -> usize {
649 (phys - self.base_phys.load(Relaxed)) >> (self.base_frame.log2() as usize + order)
650 }
651
652 #[inline(always)]
653 fn block_to_phys(&self, block_i: usize, order: usize) -> usize {
654 self.base_phys.load(Relaxed) + (block_i << (self.base_frame.log2() as usize + order))
655 }
656
657 #[inline(always)]
659 fn bit_addr(&self, order: usize, block_i: usize) -> (usize, usize) {
660 (
661 self.order_word_offsets[order].load(Relaxed) + block_i / usize::BITS as usize,
662 block_i % usize::BITS as usize,
663 )
664 }
665
666 #[inline(always)]
669 fn summary_addr(&self, order: usize, wi: usize) -> (usize, usize) {
670 let bpw = usize::BITS as usize;
671 (
672 self.summary_word_offsets[order].load(Relaxed) + wi / bpw,
673 wi % bpw,
674 )
675 }
676
677 #[inline(always)]
684 unsafe fn free_block_no_merge(&self, base: *const AtomicUsize, order: usize, block_i: usize) {
685 let wi = block_i / usize::BITS as usize;
686 let (word_idx, bit) = self.bit_addr(order, block_i);
687 let cell = unsafe { &*base.add(word_idx) };
688 self.free_counts[order].fetch_add(1, Relaxed);
689 let old = cell.fetch_or(1usize << bit, Release);
690 debug_assert!(
691 old & (1usize << bit) == 0,
692 "double-free: order-{order} block already marked free"
693 );
694 unsafe { self.sync_summary(base, cell, order, wi, old, old | (1usize << bit)) };
695 }
696
697 #[inline(always)]
705 unsafe fn sync_summary(
706 &self,
707 base: *const AtomicUsize,
708 l1_cell: &AtomicUsize,
709 order: usize,
710 wi: usize,
711 old: usize,
712 new: usize,
713 ) {
714 if (old == 0) == (new == 0) {
715 return;
716 }
717 if self.summary_lens[order].load(Relaxed) == 0 {
718 return;
719 }
720 let (s_word, s_bit) = self.summary_addr(order, wi);
721 let s_cell = unsafe { &*base.add(s_word) };
722 if new == 0 {
723 self.clear_summary_bit_then_recheck(s_cell, s_bit, l1_cell);
724 } else {
725 s_cell.fetch_or(1usize << s_bit, Release);
726 }
727 }
728
729 #[inline(always)]
734 fn clear_summary_bit_then_recheck(
735 &self,
736 s_cell: &AtomicUsize,
737 s_bit: usize,
738 l1_cell: &AtomicUsize,
739 ) {
740 let mask = 1usize << s_bit;
741 s_cell.fetch_and(!mask, AcqRel);
742 if l1_cell.load(Acquire) != 0 {
743 s_cell.fetch_or(mask, Release);
744 }
745 }
746
747 #[inline(always)]
754 unsafe fn try_grab_word(
755 &self,
756 base: *const AtomicUsize,
757 word_idx: usize,
758 order: usize,
759 wi: usize,
760 ) -> Option<usize> {
761 let cell = unsafe { &*base.add(word_idx) };
762 loop {
763 let old = cell.load(Relaxed);
764 if old == 0 {
765 return None;
766 }
767 let bit = old.trailing_zeros() as usize;
768 let new = old & !(1usize << bit);
769 if cell
770 .compare_exchange_weak(old, new, AcqRel, Acquire)
771 .is_ok()
772 {
773 self.free_counts[order].fetch_sub(1, Relaxed);
774 unsafe { self.sync_summary(base, cell, order, wi, old, new) };
775 return Some(bit);
776 }
777 }
778 }
779
780 #[cfg(any(feature = "stats", test))]
784 fn free_bytes(&self) -> usize {
785 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
786 let mut total = 0usize;
787 for k in 0..ORDERS {
788 let block_size = self.base_frame.bytes() << k;
789 let n_words = self.bitmap_lens[k].load(Relaxed);
790 let off = self.order_word_offsets[k].load(Relaxed);
791 for wi in 0..n_words {
792 let w = unsafe { &*base.add(off + wi) }.load(Relaxed);
793 total = total.saturating_add(w.count_ones() as usize * block_size);
794 }
795 }
796 total
797 }
798
799 #[cfg(any(feature = "stats", test))]
801 pub fn free_stats(&self) -> [usize; ORDERS] {
802 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
803 let mut counts = [0usize; ORDERS];
804 for (k, count) in counts.iter_mut().enumerate() {
805 let n_words = self.bitmap_lens[k].load(Relaxed);
806 let off = self.order_word_offsets[k].load(Relaxed);
807 for wi in 0..n_words {
808 let w = unsafe { &*base.add(off + wi) }.load(Relaxed);
809 *count += w.count_ones() as usize;
810 }
811 }
812 counts
813 }
814
815 #[cfg(any(feature = "stats", test))]
819 pub fn reserved_frames(&self) -> usize {
820 let total = self.total_frames.load(Relaxed);
821 if total == 0 {
822 return 0;
823 }
824 let bitmap_words = alloc_bitmap_words_for(total, ORDERS, G::SUMMARY_MIN_L1_WORDS);
825 let bitmap_bytes = bitmap_words * size_of::<usize>();
826 bitmap_bytes.div_ceil(self.base_frame.bytes())
827 }
828
829 #[cfg(audit)]
848 fn audit(&self) {
849 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
850 let base_bytes = self.base_frame.bytes();
851 let total_frames = self.total_frames.load(Relaxed);
852 if total_frames == 0 {
853 return; }
855
856 let mut blocks: alloc::vec::Vec<(usize, usize)> = alloc::vec::Vec::new();
857
858 for k in 0..ORDERS {
859 let n_blocks = blocks_at_order(total_frames, k);
860 let off = self.order_word_offsets[k].load(Relaxed);
861 let n_words = self.bitmap_lens[k].load(Relaxed);
862 let block_size = base_bytes << k;
863 let mut pop = 0usize;
864 for wi in 0..n_words {
865 pop += unsafe { &*base.add(off + wi) }.load(Relaxed).count_ones() as usize;
866
867 let mut bits = unsafe { &*base.add(off + wi) }.load(Relaxed);
868 while bits != 0 {
869 let block_i = wi * usize::BITS as usize + bits.trailing_zeros() as usize;
870 bits &= bits - 1; assert!(
873 block_i < n_blocks,
874 "audit: stray free bit at order {k}, block {block_i} \
875 ({n_blocks} blocks at this order)"
876 );
877
878 if k + 1 < ORDERS {
881 let buddy = block_i ^ 1;
882 if buddy < n_blocks {
883 let (bw, bb) = self.bit_addr(k, buddy);
884 let buddy_free =
885 (unsafe { &*base.add(bw) }.load(Relaxed) >> bb) & 1 == 1;
886 assert!(
887 !buddy_free,
888 "audit: order-{k} buddies {block_i} and {buddy} both free \
889 (should have merged)"
890 );
891 }
892 }
893
894 let phys = self.block_to_phys(block_i, k);
895 blocks.push((phys, phys + block_size));
896 }
897 }
898
899 assert_eq!(
900 pop,
901 self.free_counts[k].load(Relaxed),
902 "audit: free_counts[{k}] disagrees with the order-{k} L1 popcount"
903 );
904 }
905
906 assert!(
907 self.debug_summary_consistent(),
908 "audit: summary inconsistent"
909 );
910
911 blocks.sort_unstable_by_key(|&(start, _)| start);
913 for w in blocks.windows(2) {
914 assert!(
915 w[0].1 <= w[1].0,
916 "audit: overlapping free blocks [{:#x},{:#x}) and [{:#x},{:#x})",
917 w[0].0,
918 w[0].1,
919 w[1].0,
920 w[1].1
921 );
922 }
923
924 let bitmap_words = alloc_bitmap_words_for(total_frames, ORDERS, G::SUMMARY_MIN_L1_WORDS);
926 let reserved = (bitmap_words * size_of::<usize>()).div_ceil(base_bytes);
927 let free: usize = blocks.iter().map(|&(s, e)| e - s).sum();
928 let allocatable = (total_frames - reserved) * base_bytes;
929 assert!(
930 free <= allocatable,
931 "audit: free bytes {free:#x} exceed allocatable capacity {allocatable:#x}"
932 );
933 }
934
935 #[cfg(all(test, audit))]
937 pub(crate) fn corrupt_set_free_bit(&self, order: usize, block_i: usize) {
938 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
939 let (w, b) = self.bit_addr(order, block_i);
940 unsafe { &*base.add(w) }.fetch_or(1usize << b, Relaxed);
941 }
942
943 #[cfg(test)]
946 pub(crate) fn corrupt_set_summary_bit(&self, order: usize, wi: usize) {
947 assert_ne!(
948 self.summary_lens[order].load(Relaxed),
949 0,
950 "order-{order} has no summary segment"
951 );
952 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
953 let (w, b) = self.summary_addr(order, wi);
954 unsafe { &*base.add(w) }.fetch_or(1usize << b, Relaxed);
955 }
956
957 #[cfg(all(test, audit))]
959 pub(crate) fn run_audit(&self) {
960 self.audit();
961 }
962
963 #[cfg(any(test, audit))]
968 pub(crate) fn debug_summary_consistent(&self) -> bool {
969 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
970 if base.is_null() {
971 return false;
972 }
973 let bpw = usize::BITS as usize;
974 for k in 0..ORDERS {
975 if self.summary_lens[k].load(Relaxed) == 0 {
976 continue;
977 }
978 let n_words = self.bitmap_lens[k].load(Relaxed);
979 let word_off = self.order_word_offsets[k].load(Relaxed);
980 let sum_off = self.summary_word_offsets[k].load(Relaxed);
981 for wi in 0..n_words {
982 let l1 = unsafe { &*base.add(word_off + wi) }.load(Relaxed);
983 let s = unsafe { &*base.add(sum_off + wi / bpw) }.load(Relaxed);
984 let bit = (s >> (wi % bpw)) & 1;
985 if l1 != 0 && bit == 0 {
986 return false;
987 }
988 }
989 }
990 true
991 }
992
993 #[cfg(test)]
996 pub(crate) fn debug_summary_exact(&self) -> bool {
997 let base = self.bitmap.load(Relaxed) as *const AtomicUsize;
998 if base.is_null() {
999 return false;
1000 }
1001 let bpw = usize::BITS as usize;
1002 for k in 0..ORDERS {
1003 if self.summary_lens[k].load(Relaxed) == 0 {
1004 continue;
1005 }
1006 let n_words = self.bitmap_lens[k].load(Relaxed);
1007 let word_off = self.order_word_offsets[k].load(Relaxed);
1008 let sum_off = self.summary_word_offsets[k].load(Relaxed);
1009 for wi in 0..n_words {
1010 let l1 = unsafe { &*base.add(word_off + wi) }.load(Relaxed);
1011 let s = unsafe { &*base.add(sum_off + wi / bpw) }.load(Relaxed);
1012 let bit = (s >> (wi % bpw)) & 1;
1013 let expect = usize::from(l1 != 0);
1014 if bit != expect {
1015 return false;
1016 }
1017 }
1018 }
1019 true
1020 }
1021}
1022
1023unsafe impl<const ORDERS: usize, P: Provenance, G: GateConfig> RegionInit
1024 for SummaryBuddyAllocator<ORDERS, P, G>
1025{
1026 unsafe fn try_init(
1027 &self,
1028 phys_base: usize,
1029 span_len: usize,
1030 usable: &[PhysRange],
1031 ) -> Result<(), InitError> {
1032 let plan = self.validate(phys_base, span_len, usable)?;
1033 unsafe { self.commit(usable, plan) };
1036 Ok(())
1037 }
1038
1039 unsafe fn add_usable(&self, base: usize, len: usize) {
1040 unsafe { self.add_region(base, len) };
1041 }
1042}
1043
1044unsafe impl<const ORDERS: usize, P: Provenance, G: GateConfig> PhysicalAllocator
1045 for SummaryBuddyAllocator<ORDERS, P, G>
1046{
1047 #[inline]
1048 fn allocate_physical(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
1049 self.alloc(ps, count)
1050 }
1051
1052 #[inline]
1053 unsafe fn deallocate_physical(&self, ps: PageSize, count: NonZeroUsize, phys: usize) {
1054 unsafe { self.dealloc(ps, count, phys) }
1055 }
1056}
1057
1058#[cfg(any(feature = "stats", test))]
1059impl<const ORDERS: usize, P: Provenance, G: GateConfig> crate::AllocatorStats
1060 for SummaryBuddyAllocator<ORDERS, P, G>
1061{
1062 fn total_bytes(&self) -> usize {
1063 self.capacity_frames.load(Relaxed) * self.base_frame.bytes()
1064 }
1065
1066 fn free_bytes(&self) -> usize {
1067 self.free_bytes()
1069 }
1070
1071 fn largest_free_bytes(&self) -> usize {
1072 let counts = self.free_stats();
1073 for k in (0..ORDERS).rev() {
1075 if counts[k] > 0 {
1076 return self.base_frame.bytes() << k;
1077 }
1078 }
1079 0
1080 }
1081}