Skip to main content

frame_alloc/implementations/
summary_buddy.rs

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
9/// Number of times the allocator [`SummaryBuddyAllocator`] retries an
10/// allocation that failed with a *spurious* `OutOfMemory` before giving up.
11const SPURIOUS_OOM_RETRIES: usize = 8;
12
13/// Compile-time tuning of the two summary-scan gates.
14///
15/// By default, the allocator uses [`DefaultGates`].
16pub trait GateConfig {
17    /// Minimum L1 words an order must have to be given a summary segment.
18    const SUMMARY_MIN_L1_WORDS: usize;
19    /// Minimum summary words an order must have before the frontier cursor is used.
20    const CURSOR_MIN_SUMMARY_WORDS: usize;
21}
22
23/// Default, production gate values.
24pub 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/// `ceil(total_frames / 2^k)` - number of blocks at order `k`. Overflow-safe.
31#[inline(always)]
32const fn blocks_at_order(total_frames: usize, k: usize) -> usize {
33    total_frames.div_ceil(1 << k)
34}
35
36/// `ceil(bits / usize::BITS)`.
37#[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
43/// Compute the number of `usize` words needed for the bitmap. `summary_min_l1_words`
44/// is `G::SUMMARY_MIN_L1_WORDS` threaded in so layout matches the gate in effect.
45const 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
65/// Validated initialisation parameters produced by [`SummaryBuddyAllocator::validate`]
66/// and consumed by [`SummaryBuddyAllocator::commit`]. Carrying it separates the
67/// pure validation from the infallible mutation so that a rejected `try_init`
68/// never touches allocator state.
69struct InitPlan {
70    /// Coordinate origin: `phys_base` rounded **down** to a `max_page` boundary.
71    /// The sub-`max_page` gap `[base_phys, phys_base)` is a phantom prefix - never
72    /// registered as usable, so it stays reserved and costs only bitmap bits.
73    base_phys: usize,
74    /// Total base frames spanned by `[base_phys, phys_base + span_len)`, i.e.
75    /// including the phantom prefix.
76    total_frames: usize,
77    /// Index into `usable` of the range that hosts the bitmap.
78    host_idx: usize,
79    /// Frames carved from the host range for the bitmap.
80    reserved_frames: usize,
81}
82
83/// A lock-free, bitmap-only buddy allocator with a two-level summary bitmap
84/// that carves its bitmap out of the managed physical memory region at init time.
85///
86/// `ORDERS` is the number of size classes: order `0` corresponds to the base
87/// frame size (`base.bytes()`), order `ORDERS - 1` to the largest block
88/// (`base.bytes() << (ORDERS - 1)`).
89///
90/// `P` is the [`Provenance`] strategy. Unlike the intrusive backends, this
91/// allocator only touches the managed memory at init - to obtain a pointer to the
92/// carved bitmap - and never reaches into the frames it hands out; so `P::create`
93/// is called exactly once, for the bitmap, and `P::destroy` is never needed (the
94/// bitmap is permanent).
95pub struct SummaryBuddyAllocator<const ORDERS: usize, P: Provenance, G: GateConfig = DefaultGates> {
96    base_frame: PageSize,
97    /// Largest page size a caller may request. Caps the allocation size and,
98    /// equivalently, the boundary `init` rounds `phys_base` down to.
99    max_page: PageSize,
100    /// Physical base of the entire managed region (including bitmap frames).
101    base_phys: AtomicUsize,
102    /// Total base-frame count (including bitmap frames). Zero before init.
103    total_frames: AtomicUsize,
104    /// Virtual pointer to the flat bitmap stored in its hosting usable range.
105    /// Null before `init` runs.
106    bitmap: AtomicPtr<u8>,
107    /// Word offset into the flat bitmap where order k's segment begins.
108    order_word_offsets: [AtomicUsize; ORDERS],
109    /// Number of `usize` words in order k's bitmap segment (precomputed).
110    bitmap_lens: [AtomicUsize; ORDERS],
111    /// Word offset into the flat bitmap where order k's **summary** segment begins.
112    summary_word_offsets: [AtomicUsize; ORDERS],
113    /// Number of `usize` words in order k's summary segment (precomputed).
114    summary_lens: [AtomicUsize; ORDERS],
115    /// Per-order summary-scan start hint.
116    summary_cursor: [AtomicUsize; ORDERS],
117    /// Free block count per order.
118    /// Invariant: `free_counts[k] >= actual free blocks at order k`.
119    free_counts: [AtomicUsize; ORDERS],
120    /// Total base-frames ever registered, for [`AllocatorStats::total_bytes`].
121    /// Written single-threaded at init; read for diagnostics. Stats-only.
122    #[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    /// Create a new, empty allocator with the given base frame size.
130    ///
131    /// All runtime state is zeroed. Call `init` / `init_region` to finish
132    /// initialisation before allocating. `max_page` defaults to the max block
133    /// (`base_frame << (ORDERS-1)`), the most conservative choice; use
134    /// [`with_max_page`](Self::with_max_page) to shrink the boundary `init` rounds
135    /// `phys_base` down to (a smaller `max_page` shrinks the phantom prefix).
136    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    /// Like [`new`](Self::new) but pins `max_page`, the largest page a caller may
142    /// request. It caps allocations (`ps > max_page` -> `InvalidPageSize`) and,
143    /// equivalently, sets the boundary `init` rounds `phys_base` down to - the two
144    /// are the same bound. Must lie in `base_frame ..= base_frame << (ORDERS-1)`.
145    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    /// Pure validation behind [`RegionInit::try_init`](RegionInit::try_init):
184    /// check every argument and locate the bitmap host without mutating any
185    /// allocator state. On success it returns an [`InitPlan`] for
186    /// [`commit`](Self::commit); on failure the allocator is untouched.
187    ///
188    /// The bitmap is carved from the first usable range large enough to hold it;
189    /// the remainder of that range stays allocatable. Holes between usable ranges
190    /// stay reserved. `phys_base` is a pure coordinate origin and need not itself
191    /// be usable RAM; it must be base-frame aligned but need **not** be
192    /// `max_page`-aligned - init rounds it down to a `max_page` boundary internally
193    /// (so a handed-out page is still naturally aligned) and reserves the
194    /// sub-`max_page` phantom prefix, which costs only bitmap bits.
195    ///
196    /// # Errors
197    ///
198    /// See [`RegionInit::try_init`](RegionInit::try_init). This implementation
199    /// reports base-frame misalignment of `phys_base` via [`InitError::Misaligned`].
200    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        // Round the coordinate origin down to a `max_page` boundary so a returned
223        // page (≤ max_page) is naturally aligned per the PhysicalAllocator
224        // contract. The gap `[base_phys, phys_base)` is a phantom prefix: it is
225        // never registered as usable, so it stays reserved and only enlarges the
226        // bitmap by the frames it spans. `base_phys` ≤ `phys_base`, both
227        // frame-aligned, so `prefix_frames` is exact.
228        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        // Usable ranges live at or above the real `phys_base`; the phantom prefix
233        // below it is never usable.
234        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        // Find the first usable range that can host the bitmap.
262        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    /// Infallible commit behind [`RegionInit::try_init`](RegionInit::try_init):
276    /// publish the metadata and register the usable frames from a validated
277    /// [`InitPlan`]. This is the first and only mutation of allocator state.
278    ///
279    /// # Safety
280    ///
281    /// `plan` must have come from [`validate`](Self::validate) with the same
282    /// `usable`. The host usable range must be exclusively owned and reachable
283    /// through `P::create`; call once, single-threaded. The allocator must be
284    /// published to other threads with a happens-before edge (thread spawn, mutex,
285    /// or a Release store / Acquire load of a ready flag) before any concurrent use.
286    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        // Create provenance for the carved bitmap.
298        // SAFETY: `bitmap_phys` is the base of the validated host range, which the
299        // caller guarantees is exclusively owned and reachable through `P::create`.
300        let bitmap_virt = unsafe { P::create(bitmap_phys) }.as_ptr();
301
302        // Zero the bitmap:
303        // SAFETY: `bitmap_virt` carries provenance over the whole host run, which
304        // holds ≥ `reserved` frames.
305        unsafe { ptr::write_bytes(bitmap_virt, 0, reserved_bytes) };
306
307        // Initialise lookup metadata.
308        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        // Register usable frames.
337        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                // SAFETY: `[base, base + len)` is an in-span, frame-aligned,
346                // exclusively-owned sub-range; metadata is fully published above.
347                unsafe { self.add_region(base, len) };
348            }
349        }
350    }
351
352    /// Register `[base, base + len)` as free memory.
353    ///
354    /// `init` calls this internally for each usable range. It is also the
355    /// primitive behind [`add_usable`](RegionInit::add_usable), so it may
356    /// be called after `init` to register a currently-reserved in-span sub-range,
357    /// provided it lies within the bitmap's address range `[base_phys, base_phys +
358    /// total_frames * base_frame)` and is not already free.
359    ///
360    /// # Safety
361    ///
362    /// * `init` must have been called before this method.
363    /// * `base` must be aligned to `self.base_frame.bytes()`.
364    /// * `len` must be a non-zero multiple of `self.base_frame.bytes()`.
365    /// * The memory must be exclusively owned and not accessed through any
366    ///   other alias while registered with this allocator.
367    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        // Uninitialised allocator -> null deref (UB).
373        assert!(
374            !self.bitmap.load(Relaxed).is_null(),
375            "add_usable/add_region called before init"
376        );
377        // An out-of-span/overflowing range -> out-of-bounds atomic write (UB).
378        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    /// Allocate `count` contiguous frames of size `ps`.
410    ///
411    /// The total `count * ps.bytes()` is rounded up to the next power-of-two
412    /// multiple of `base.bytes()` to select the buddy order. If the requested
413    /// total is not already such a multiple, the allocated block is larger than
414    /// requested and the excess bytes are wasted until the corresponding
415    /// `deallocate_physical` call (which must use the same `ps` and `count`).
416    #[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    /// Return a block of `order` starting at `phys`.
435    ///
436    /// # Safety
437    ///
438    /// `phys` must have been returned by [`alloc`](Self::alloc) with the same
439    /// `order`, and must not be used after this call.
440    #[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        // On an *uninitialised* allocator this is null, but every `free_counts[k]`
460        // is also zero, so the scan below short-circuits every order and returns
461        // `OutOfMemory` before the pointer is ever dereferenced.
462        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            // Fast path: Summary scan.
479            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                        // This summary bit didn't yield a block. For valid L1 words,
511                        // repair the shared stale-positive bit above; then drop it
512                        // locally and try the next.
513                        s &= !(1usize << s_bit);
514                    }
515                }
516            }
517
518            // Fallback: Direct scan.
519            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        // Split down to the requested order, freeing the upper buddy at each level.
534        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        // dealloc *writes* through `bitmap_base`, so an uninitialised allocator
553        // would be a null deref (UB), but covered by the unsafe contract.
554        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            // SAFETY: word_idx is within the bitmap; bitmap_base is the
564            // init-set, non-null base of that allocation.
565            let cell = unsafe { &*bitmap_base.add(word_idx) };
566
567            if k + 1 == ORDERS {
568                // Top order - no further merge possible.
569                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                    // Buddy appears free - consume it.
585                    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; // ascend
593                    }
594                } else {
595                    // Buddy allocated - mark this block free.
596                    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            // Buddy consumed; ascend to the parent.
615            k += 1;
616            block_i = block_i.min(block_i ^ 1) >> 1;
617        }
618    }
619
620    /// Compute the buddy order for `count` frames of size `ps`.
621    ///
622    /// `ps` must be a power-of-two multiple of `base_bytes`, which is guaranteed
623    /// by construction because [`PageSize`] only represents powers of two and the
624    /// caller rejects `ps < base_bytes` below.
625    #[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        // `total >= 1`, so `total - 1` never underflows.
640        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    /// `(absolute_word_index, bit_position)` for `block_i` at `order`.
658    #[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    /// `(absolute_summary_word_index, bit_within_word)` for L1 word `wi`
667    /// (counted within order `order`'s L1 segment).
668    #[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    /// Set the free bit for `block_i` at `order` without checking for a merge.
678    /// Used during split, where the buddy is known to be allocated.
679    ///
680    /// # Safety
681    ///
682    /// `base` must be the live bitmap base.
683    #[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    /// Keep the summary bit for L1 word `wi` consistent with that word's
698    /// zero/non-zero state, given an L1 CAS that moved it from `old` to `new`.
699    ///
700    /// # Safety
701    ///
702    /// `base` must be the live bitmap base; `l1_cell` must be the L1 word for
703    /// `(order, wi)`.
704    #[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    /// Clear one summary bit, then re-set it if the L1 word is or became
730    /// non-empty. The AcqRel clear synchronizes with a concurrent Release set of
731    /// the same summary bit, making that freer's preceding L1 write visible to
732    /// the recheck.
733    #[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    /// Try to grab one free block from the L1 word at absolute index `word_idx`
748    /// (L1 word `wi` within order `order`). Returns the bit on success.
749    ///
750    /// # Safety
751    ///
752    /// `base` must be the live bitmap base and `word_idx` in bounds.
753    #[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    /// Total free bytes. Non-linearizable under concurrent use.
781    ///
782    /// Internal: the public path is [`AllocatorStats::free_bytes`].
783    #[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    /// Free block count per order. Non-linearizable under concurrent use.
800    #[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    /// Number of base frames the bitmap occupies inside its hosting usable range
816    /// (carved from the first usable range large enough to hold it; for a
817    /// whole-span init that is the span start). Returns 0 before `init`.
818    #[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    /// Full structural-invariant check, run after every op under `--cfg audit`
830    /// (test builds only). The bitmap is lock-free, so this reads `Relaxed` and is
831    /// only meaningful for the sequential audit tests.
832    ///
833    /// Asserts the invariants the split/merge machinery is responsible for:
834    ///
835    /// * **valid bits** - every set bit is a real block index, never a stray bit
836    ///   in the padding tail of an order's last word;
837    /// * **merge completeness** - no block and its buddy are both free at the same
838    ///   order; they must have coalesced into the parent.
839    /// * **no overlap** - expanded to byte intervals, free blocks across all
840    ///   orders are pairwise disjoint (catches a sub-block aliased inside a larger
841    ///   free block);
842    /// * the free total never exceeds the allocatable capacity.
843    /// * `free_counts[k]` equals the L1 popcount at every order (in the quiescent
844    ///   single-threaded state these runs use) and the summary has no false
845    ///   negatives. False-positive summary bits are allowed under concurrent
846    ///   maintenance and are repaired lazily by the scanner.
847    #[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; // not initialised yet
854        }
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; // clear lowest set bit
871
872                    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                    // Merge completeness: below the top order, a free block's
879                    // buddy must not also be free.
880                    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        // No two free blocks may overlap (adjacency is fine).
912        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        // The free total can never exceed the allocatable (non-bitmap) capacity.
925        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    /// Test-only corruption hooks used by the audit non-vacuity tests.
936    #[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    /// Test-only corruption hook: set the summary bit for L1 word `wi` without
944    /// changing the L1 word. This models a stale-positive summary bit.
945    #[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    /// Test-only: run the structural audit on demand (see [`Self::audit`]).
958    #[cfg(all(test, audit))]
959    pub(crate) fn run_audit(&self) {
960        self.audit();
961    }
962
963    /// Test-only invariant: every non-empty L1 word must have its summary bit
964    /// set. Under concurrent maintenance the reverse is intentionally weaker:
965    /// stale-positive summary bits may point at empty L1 words and are repaired
966    /// lazily by the scanner.
967    #[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    /// Test-only exact summary check for sequential/quiescent tests. Concurrent
994    /// operation only guarantees [`Self::debug_summary_consistent`].
995    #[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        // SAFETY: `plan` came from `validate` with these same arguments; the
1034        // caller upholds the trait-level provenance/ownership/once contract.
1035        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        // Inherent method (same name) wins method-call resolution - no recursion.
1068        self.free_bytes()
1069    }
1070
1071    fn largest_free_bytes(&self) -> usize {
1072        let counts = self.free_stats();
1073        // Highest order with a free block is the largest run that can be served.
1074        for k in (0..ORDERS).rev() {
1075            if counts[k] > 0 {
1076                return self.base_frame.bytes() << k;
1077            }
1078        }
1079        0
1080    }
1081}