Skip to main content

region_alloc/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! # RegionAllocator
6//!
7//! ## Overview
8//! A `RegionAllocator` is a utility class designed to help with the bookkeeping
9//! involved in managing the allocation/partitioning of a 64-bit space into
10//! non-overlapping "Regions". In addition to the `RegionAllocator`, there are two
11//! other classes involved in the use of a `RegionAllocator`;
12//! `Region` and `RegionPool`.
13//!
14//! A `Region` consists of an unsigned 64-bit base address and an unsigned 64-bit
15//! size. A `Region` is considered valid iff its size is non-zero, and it does not
16//! wrap its 64-bit space.
17//!
18//! See the "Memory Allocation" section for a discussion of the `RegionPool`.
19//!
20//! `RegionAllocator` users can create an allocator and then add any number of
21//! non-overlapping Regions to its pool of regions available for allocation.
22//! They may then request that regions be allocated from the pool either by
23//! requesting that a region be allocated with a particular size/alignment, or
24//! by asking for a specific base/size. The `RegionAllocator` will manage all of
25//! the bookkeeping involved in breaking available regions into smaller chunks,
26//! tracking allocated regions, and re-merging regions when they are returned to
27//! the allocator.
28//!
29//! ## Memory Allocation
30//! `RegionAllocator`s require dynamically allocated memory in order to store the
31//! bookkeeping required for managing available regions. In order to control
32//! heap fragmentation and the frequency of heap interaction, a `RegionPool` object
33//! may be used to allocate bookkeeping overhead in larger slabs which are carved up
34//! and placed on a free list to be used by a `RegionAllocator`. `RegionPool`s are
35//! created with a defined slab size as well as a maximum memory limit. The pool
36//! will initially allocate a single slab, but will attempt to grow any time
37//! bookkeeping is needed but the free list is empty and the allocation of
38//! another slab would not push the allocator over its maximum memory limit.
39//!
40//! `RegionPool`s are ref-counted objects (`RefPtr<RegionPool>`) that may be shared by multiple
41//! `RegionAllocator`s. This allows sub-systems which use multiple allocators to
42//! impose system-wide limits on bookkeeping overhead. If a `RegionPool` allocator
43//! is to be used, it must be assigned to the `RegionAllocator` before any regions
44//! can be added or allocated, and the pool may not be re-assigned while the
45//! allocator is using any bookkeeping from the pool.
46//!
47//! ## APIs and Object lifecycle management
48//! The API makes use of `fbl` managed pointer types in order to simplify lifecycle
49//! management. `RegionPool`s are managed with `RefPtr<RegionPool>` while `Region`s are handed
50//! out via `UniquePtr<Region>`. `RegionAllocator`s themselves impose no lifecycle
51//! restrictions and may be heap allocated, stack allocated, or embedded directly
52//! in objects as the user sees fit. It is an error to allow a `RegionAllocator`
53//! to destruct while there are allocations in flight.
54//!
55//! ## Thread Safety
56//! `RegionAllocator` and `RegionPool`s use `KMutex` or `RawMutex` objects to provide thread
57//! safety in multi-threaded environments. As such, `RegionAllocator`s are not
58//! currently suitable for use in code which may run at IRQ context, or which
59//! must never block.
60//!
61//! Each `RegionAllocator` has its own mutex allowing for concurrent access across
62//! multiple allocators, even when the allocators share the same `RegionPool`.
63//! `RegionPool`s also hold their own mutex which may be obtained by an Allocator
64//! while holding the Allocator's Mutex.
65//!
66//! ## Simple Usage Example
67//!
68//! ```rust
69//! use pin_init::stack_pin_init;
70//! use region_alloc::{RegionAllocator, RegionPool, RegionSpan, AllowOverlap};
71//! use zx_status::Status;
72//!
73//! # fn main() -> Result<(), Status> {
74//! // Create a pool and assign it to a stack allocated allocator. Limit the
75//! // bookkeeping memory to 32KB. This will ensure that no heap interactions
76//! // take place after startup (during operation).
77//! let pool = RegionPool::create(32 << 10).map_err(|_| Status::NO_MEMORY)?;
78//! stack_pin_init!(let alloc = RegionAllocator::init_with_pool(pool));
79//!
80//! // Add regions to the pool which can be allocated from
81//! // [3GB,   4GB)
82//! alloc.add_region(RegionSpan { base: 0xC000_0000, size: 0x4000_0000 }, AllowOverlap::No)?;
83//! // [256GB, 257GB)
84//! alloc.add_region(RegionSpan { base: 0x40_0000_0000, size: 0x4000_0000 }, AllowOverlap::No)?;
85//!
86//! // Grab some specific regions out of the available regions.
87//! // [3GB + 1MB,   3GB + 2MB)
88//! let r1 = alloc.get_region_specific(RegionSpan { base: 0xC010_0000, size: 0x10_0000 })?;
89//! // [256GB + 1MB, 256GB + 2MB)
90//! let r2 = alloc.get_region_specific(RegionSpan { base: 0x40_0010_0000, size: 0x10_0000 })?;
91//!
92//! // Grab some pointer aligned regions of various sizes
93//! let r3 = alloc.get_region_pointer_aligned(1024)?;
94//! let r4 = alloc.get_region_pointer_aligned(75)?;
95//! let r5 = alloc.get_region_pointer_aligned(80000)?;
96//!
97//! // Grab some page aligned regions of various sizes
98//! let r6 = alloc.get_region(1024,  4 << 10)?;
99//! let r7 = alloc.get_region(75,    4 << 10)?;
100//! let r8 = alloc.get_region(80000, 4 << 10)?;
101//!
102//! // Access base and size:
103//! assert_eq!(r3.size(), 1024);
104//! assert_eq!(r8.size(), 80000);
105//!
106//! // No need to clean up. Regions will automatically be returned to the
107//! // allocator as they go out of scope. Then the allocator will return all of
108//! // its available regions to the pool when it goes out of scope. Finally, the
109//! // pool will free all of its memory as the allocator releases its reference
110//! // to the pool.
111//! # Ok(())
112//! # }
113//! ```
114
115#![no_std]
116
117use core::ptr::NonNull;
118use fbl::{
119    Recyclable, RefPtr, TrackingSize, UniquePtr, WavlTree, WavlTreeContainable, WavlTreeKeyable,
120    WavlTreeNode,
121};
122use kalloc::{AllocError, Box};
123use ksync::{KMutex, RawMutex, guarded, kcell_init, lock};
124use zx_status::Status;
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct RegionSpan {
128    pub base: u64,
129    pub size: u64,
130}
131
132impl RegionSpan {
133    pub fn end(&self) -> Result<u64, Status> {
134        self.base.checked_add(self.size).ok_or(Status::INVALID_ARGS)
135    }
136
137    pub fn validate(&self) -> Result<(), Status> {
138        if self.size == 0 {
139            return Err(Status::INVALID_ARGS);
140        }
141        let _ = self.end()?;
142        Ok(())
143    }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub struct RegionKey {
148    pub base: u64,
149    pub size: u64,
150}
151
152impl Ord for RegionKey {
153    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
154        match self.size.cmp(&other.size) {
155            core::cmp::Ordering::Equal => self.base.cmp(&other.base),
156            ord => ord,
157        }
158    }
159}
160
161impl PartialOrd for RegionKey {
162    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
163        Some(self.cmp(other))
164    }
165}
166
167pub struct SortByBase;
168pub struct SortBySize;
169
170#[derive(WavlTreeContainable)]
171pub struct Region {
172    span: RegionSpan,
173    key_size: RegionKey,
174    owner: NonNull<RegionAllocator>,
175
176    #[wavl_node(tag = SortByBase)]
177    node_base: WavlTreeNode<Region>,
178
179    #[wavl_node(tag = SortBySize)]
180    node_size: WavlTreeNode<Region>,
181}
182
183// SAFETY: Region doesn't hold thread-local data and can be safely sent across thread boundaries.
184unsafe impl Send for Region {}
185// SAFETY: Region's fields are only mutated under the allocator's lock, so it's Sync.
186unsafe impl Sync for Region {}
187
188// SAFETY: The Recyclable trait requires that `recycle` is safe to call with a valid pointer.
189// `Region` handles recycling via its allocator owner.
190unsafe impl Recyclable for Region {
191    /// Recycle a region when its `UniquePtr` goes out of scope.
192    ///
193    /// # Safety
194    /// The caller must guarantee that `ptr` is a valid, unique pointer to a `Region`
195    /// that is no longer referenced anywhere else.
196    unsafe fn recycle(ptr: NonNull<Self>) {
197        // SAFETY: `ptr` is verified to be valid and dereferenceable.
198        // We release the region back to its owner.
199        // `owner` is guaranteed to outlive all regions in flight.
200        unsafe {
201            let owner = ptr.as_ref().owner;
202            owner.as_ref().release_region(ptr);
203        }
204    }
205}
206
207impl Region {
208    pub fn base(&self) -> u64 {
209        self.span.base
210    }
211    pub fn size(&self) -> u64 {
212        self.span.size
213    }
214
215    fn new(span: RegionSpan, owner: NonNull<RegionAllocator>) -> Self {
216        Self {
217            span,
218            key_size: RegionKey { base: span.base, size: span.size },
219            owner,
220            node_base: WavlTreeNode::new(),
221            node_size: WavlTreeNode::new(),
222        }
223    }
224
225    fn update_key(&mut self) {
226        self.key_size = RegionKey { base: self.span.base, size: self.span.size };
227    }
228}
229
230impl WavlTreeKeyable<u64> for Region {
231    type Key<'a> = u64;
232    fn get_key(&self) -> u64 {
233        self.span.base
234    }
235}
236
237impl WavlTreeKeyable<RegionKey> for Region {
238    type Key<'a> = RegionKey;
239    fn get_key(&self) -> RegionKey {
240        self.key_size
241    }
242}
243
244#[fbl::ref_counted]
245#[pin_init::pin_data]
246#[derive(fbl::Recyclable)]
247#[repr(C)]
248pub struct RegionPool {
249    #[pin]
250    allocator: fbl::SlabAllocator<Region, RawMutex, { RegionAllocator::REGION_POOL_SLAB_SIZE }>,
251}
252
253impl RegionPool {
254    pub fn create(max_memory: usize) -> Result<RefPtr<Self>, AllocError> {
255        let slab_size = RegionAllocator::REGION_POOL_SLAB_SIZE;
256        if slab_size > max_memory {
257            return Err(AllocError);
258        }
259        let max_slabs = max_memory / slab_size;
260
261        let pool = fbl::pin_make_ref_counted!(Self {
262            allocator <- fbl::SlabAllocator::init(max_slabs),
263        })
264        .map_err(|_| AllocError)?;
265
266        pool.allocator.preallocate()?;
267        Ok(pool)
268    }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum AllowOverlap {
273    No,
274    Yes,
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub enum AllowIncomplete {
279    No,
280    Yes,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub enum TestRegionSet {
285    Allocated,
286    Available,
287}
288
289#[guarded]
290#[pin_init::pin_data(PinnedDrop)]
291pub struct RegionAllocator {
292    #[mutex]
293    mu: KMutex,
294
295    #[pin]
296    // TODO(https://fxbug.dev/536051491): Remove this once WavlTree is pin-aware.
297    #[ksync(unpinned)]
298    #[guarded_by(mu)]
299    allocated_regions_by_base: WavlTree<u64, NonNull<Region>, SortByBase, TrackingSize>,
300
301    #[pin]
302    // TODO(https://fxbug.dev/536051491): Remove this once WavlTree is pin-aware.
303    #[ksync(unpinned)]
304    #[guarded_by(mu)]
305    avail_regions_by_base: WavlTree<u64, NonNull<Region>, SortByBase, TrackingSize>,
306
307    #[pin]
308    // TODO(https://fxbug.dev/536051491): Remove this once WavlTree is pin-aware.
309    #[ksync(unpinned)]
310    #[guarded_by(mu)]
311    avail_regions_by_size: WavlTree<RegionKey, NonNull<Region>, SortBySize, TrackingSize>,
312
313    #[guarded_by(mu)]
314    region_pool: Option<RefPtr<RegionPool>>,
315}
316
317// SAFETY: RegionAllocator uses a mutex (`mu`) to synchronize access to its internal state,
318// making it safe to send across threads.
319unsafe impl Send for RegionAllocator {}
320// SAFETY: RegionAllocator uses a mutex (`mu`) to synchronize access to its internal state,
321// making it safe to share across threads.
322unsafe impl Sync for RegionAllocator {}
323
324impl RegionAllocator {
325    pub const REGION_POOL_SLAB_SIZE: usize = 4096;
326
327    pub fn init() -> impl pin_init::PinInit<Self, core::convert::Infallible> {
328        pin_init::pin_init!(Self {
329            mu <- KMutex::init(),
330            allocated_regions_by_base <- kcell_init(WavlTree::new()),
331            avail_regions_by_base <- kcell_init(WavlTree::new()),
332            avail_regions_by_size <- kcell_init(WavlTree::new()),
333            region_pool: None.into(),
334        })
335    }
336
337    /// Initialize a `RegionAllocator` with a specified `RegionPool`.
338    pub fn init_with_pool(
339        pool: RefPtr<RegionPool>,
340    ) -> impl pin_init::PinInit<Self, core::convert::Infallible> {
341        pin_init::pin_init!(Self {
342            mu <- KMutex::init(),
343            allocated_regions_by_base <- kcell_init(WavlTree::new()),
344            avail_regions_by_base <- kcell_init(WavlTree::new()),
345            avail_regions_by_size <- kcell_init(WavlTree::new()),
346            region_pool: Some(pool).into(),
347        })
348    }
349
350    pub fn has_region_pool(&self) -> bool {
351        lock!(let guard = self.lock_mu());
352        guard.fields().region_pool.is_some()
353    }
354
355    pub fn set_region_pool(&self, pool: RefPtr<RegionPool>) -> Result<(), Status> {
356        lock!(let mut guard = self.lock_mu());
357        let fields = guard.as_mut().fields_mut();
358
359        if !fields.allocated_regions_by_base.is_empty() || !fields.avail_regions_by_base.is_empty()
360        {
361            return Err(Status::BAD_STATE);
362        }
363
364        *fields.region_pool = Some(pool);
365        Ok(())
366    }
367
368    pub fn reset(&self) {
369        lock!(let mut guard = self.lock_mu());
370        let fields = guard.as_mut().fields_mut();
371
372        debug_assert!(fields.allocated_regions_by_base.is_empty());
373
374        fields.avail_regions_by_base.clear();
375        while let Some(region_ptr) = fields.avail_regions_by_size.pop_front() {
376            // SAFETY: The popped `region_ptr` is a valid pointer to a `Region` that was stored in
377            // the allocator's available trees.  It has been removed from `avail_regions_by_size`,
378            // and `avail_regions_by_base` was cleared, so there are no other references to it.
379            unsafe {
380                Self::destroy_region_raw(fields.region_pool, region_ptr);
381            }
382        }
383    }
384
385    pub fn add_region(
386        &self,
387        region: RegionSpan,
388        allow_overlap: AllowOverlap,
389    ) -> Result<(), Status> {
390        region.validate()?;
391
392        lock!(let mut guard = self.lock_mu());
393        let mut fields = guard.as_mut().fields_mut();
394
395        self.add_subtract_sanity_check_locked_mut(fields.allocated_regions_by_base, &region)?;
396
397        if allow_overlap != AllowOverlap::Yes {
398            let intersects = self.intersects_locked(fields.avail_regions_by_base, &region)?;
399            if intersects {
400                return Err(Status::INVALID_ARGS);
401            }
402        }
403
404        let region_ptr = self.create_region_raw(fields.region_pool, region)?;
405
406        self.add_region_to_avail_locked(&mut fields, region_ptr, allow_overlap);
407        Ok(())
408    }
409
410    pub fn subtract_region(
411        &self,
412        to_subtract: RegionSpan,
413        allow_incomplete: AllowIncomplete,
414    ) -> Result<(), Status> {
415        to_subtract.validate()?;
416        let region_end = to_subtract.end()?;
417
418        lock!(let mut guard = self.lock_mu());
419        let fields = guard.as_mut().fields_mut();
420
421        self.add_subtract_sanity_check_locked_mut(fields.allocated_regions_by_base, &to_subtract)?;
422
423        let mut region = to_subtract;
424
425        let mut before_contains = false;
426        let mut before_ptr = None;
427        let mut before_end = 0;
428
429        {
430            let mut before_cursor = fields.avail_regions_by_base.upper_bound(&region.base);
431            before_cursor.move_prev();
432
433            if let Some(before) = before_cursor.get() {
434                before_end = before.base() + before.size();
435                if region.base >= before.base() && region_end <= before_end {
436                    before_contains = true;
437                    before_ptr = Some(NonNull::from(before));
438                }
439            }
440
441            if before_contains {
442                // SAFETY: `before_ptr` is confirmed to be `Some` containing a valid, non-null
443                // pointer to a `Region` in the available set.
444                let before = unsafe { before_ptr.unwrap().as_ref() };
445
446                // Case 1: Same region
447                if region.base == before.base() && region_end == before_end {
448                    let removed_ptr = before_cursor.erase().unwrap();
449                    let key = before.key_size;
450                    fields.avail_regions_by_size.erase(&key);
451                    // SAFETY: `removed_ptr` has been removed from all indices and can be safely
452                    // destroyed.
453                    unsafe {
454                        Self::destroy_region_raw(fields.region_pool, removed_ptr);
455                    }
456                    return Ok(());
457                }
458
459                // Case 2: Split in middle
460                if region.base != before.base() && region_end != before_end {
461                    // The allocator lock is held. We are creating a new region to hold the second
462                    // half of the split.
463                    let second_ptr = self.create_region_raw(
464                        fields.region_pool,
465                        RegionSpan { base: region_end, size: before_end - region_end },
466                    )?;
467                    let key = before.key_size;
468                    let first_ptr = fields.avail_regions_by_size.erase(&key).unwrap();
469
470                    // SAFETY: `first_ptr` is a valid pointer to a `Region`. Exclusivity is
471                    // guaranteed because we hold the allocator lock, and although the region
472                    // remains in `avail_regions_by_base` (its base address is unchanged), we have
473                    // erased it from `avail_regions_by_size` and will not access it through the
474                    // base tree during mutation.
475                    unsafe {
476                        let first = &mut *first_ptr.as_ptr();
477
478                        first.span.size = region.base - first.base();
479                        first.update_key();
480                    }
481
482                    // SAFETY: We insert the modified `first_ptr` and newly created `second_ptr`
483                    // back into the WavlTree indices.  The trees will now own these pointers.
484                    unsafe {
485                        fields.avail_regions_by_size.insert_raw(first_ptr);
486                        fields.avail_regions_by_base.insert_raw(second_ptr);
487                        fields.avail_regions_by_size.insert_raw(second_ptr);
488                    }
489                    return Ok(());
490                }
491
492                // Case 3: Trim front
493                if region.base == before.base() {
494                    let key = before.key_size;
495                    let bptr = fields.avail_regions_by_size.erase(&key).unwrap();
496                    // SAFETY: `bptr` is a valid pointer to a `Region`.
497                    let base = unsafe { bptr.as_ref().base() };
498                    fields.avail_regions_by_base.erase(&base);
499
500                    // SAFETY: `bptr` is a valid pointer to a `Region`. Exclusivity is guaranteed
501                    // because we have erased the region from both the size and base available trees
502                    // (since its base address is changing).
503                    unsafe {
504                        let b = &mut *bptr.as_ptr();
505                        b.span.base += region.size;
506                        b.span.size -= region.size;
507                        b.update_key();
508                    }
509
510                    // SAFETY: Re-inserting the trimmed region pointer back into the available
511                    // indices is safe.
512                    unsafe {
513                        fields.avail_regions_by_size.insert_raw(bptr);
514                        fields.avail_regions_by_base.insert_raw(bptr);
515                    }
516                    return Ok(());
517                }
518
519                // Case 4: Trim end
520                let key = before.key_size;
521                let bptr = fields.avail_regions_by_size.erase(&key).unwrap();
522                // SAFETY: `bptr` is a valid pointer to a `Region`. Exclusivity is guaranteed
523                // because we hold the allocator lock, and although the region remains in
524                // `avail_regions_by_base` (its base address is unchanged), we have erased it from
525                // `avail_regions_by_size` and will not access it through the base tree during
526                // mutation.
527                unsafe {
528                    let b = &mut *bptr.as_ptr();
529                    b.span.size -= region.size;
530                    b.update_key();
531                }
532                // SAFETY: Re-inserting the trimmed region pointer back into the available index is
533                // safe.
534                unsafe {
535                    fields.avail_regions_by_size.insert_raw(bptr);
536                }
537                return Ok(());
538            }
539        } // before_cursor dropped
540
541        if allow_incomplete != AllowIncomplete::Yes {
542            return Err(Status::INVALID_ARGS);
543        }
544
545        {
546            let mut before_cursor = fields.avail_regions_by_base.upper_bound(&region.base);
547            before_cursor.move_prev();
548            if before_cursor.get().is_some() {
549                let before = before_cursor.get().unwrap();
550                let before_end = before.base() + before.size();
551                if before_end > region.base {
552                    if before.base() == region.base {
553                        let removed_ptr = before_cursor.erase().unwrap();
554                        // SAFETY: `removed_ptr` is a valid pointer.
555                        let key = unsafe { removed_ptr.as_ref().key_size };
556                        fields.avail_regions_by_size.erase(&key);
557                        // SAFETY: `removed_ptr` has been removed from all indices and can be safely
558                        // destroyed.
559                        unsafe {
560                            Self::destroy_region_raw(fields.region_pool, removed_ptr);
561                        }
562                    } else {
563                        let key = before.key_size;
564                        let bptr = fields.avail_regions_by_size.erase(&key).unwrap();
565                        // SAFETY: `bptr` is a valid pointer to a `Region`. Exclusivity is
566                        // guaranteed because we hold the allocator lock, and although the region
567                        // remains in `avail_regions_by_base` (its base address is unchanged), we
568                        // have erased it from `avail_regions_by_size` and will not access it
569                        // through the base tree during mutation.
570                        unsafe {
571                            let b = &mut *bptr.as_ptr();
572                            b.span.size = region.base - b.base();
573                            b.update_key();
574                        }
575                        // SAFETY: Re-inserting the trimmed region pointer back into the available
576                        // index is safe.
577                        unsafe {
578                            fields.avail_regions_by_size.insert_raw(bptr);
579                        }
580                    }
581                    region.base = before_end;
582                    region.size = region_end - region.base;
583                }
584            }
585        } // before_cursor dropped
586
587        let mut after_cursor = fields.avail_regions_by_base.upper_bound(&region.base);
588        while after_cursor.get().is_some() {
589            let after = after_cursor.get().unwrap();
590            if after.base() >= region_end {
591                break;
592            }
593
594            let after_end = after.base() + after.size();
595
596            if after_end > region_end {
597                // Trim front
598                let trim_ptr = after_cursor.erase().unwrap();
599                // SAFETY: `trim_ptr` is a valid pointer.
600                let key = unsafe { trim_ptr.as_ref().key_size };
601                fields.avail_regions_by_size.erase(&key);
602
603                // SAFETY: `trim_ptr` is a valid pointer to a `Region`. Exclusivity is guaranteed
604                // because we have erased the region from both the size and base available trees
605                // (since its base address is changing).
606                unsafe {
607                    let t = &mut *trim_ptr.as_ptr();
608                    t.span.base = region_end;
609                    t.span.size = after_end - t.span.base;
610                    t.update_key();
611                }
612                // SAFETY: Re-inserting the trimmed region pointer back into the available indices
613                // is safe.
614                unsafe {
615                    fields.avail_regions_by_size.insert_raw(trim_ptr);
616                    fields.avail_regions_by_base.insert_raw(trim_ptr);
617                }
618                break;
619            }
620
621            let trim_ptr = after_cursor.erase().unwrap();
622            // SAFETY: `trim_ptr` is a valid pointer.
623            let key = unsafe { trim_ptr.as_ref().key_size };
624            fields.avail_regions_by_size.erase(&key);
625
626            region.base = after_end;
627            region.size = region_end - region.base;
628            // SAFETY: `trim_ptr` has been removed from all indices and can be safely destroyed.
629            unsafe {
630                Self::destroy_region_raw(fields.region_pool, trim_ptr);
631            }
632
633            if region.size == 0 {
634                break;
635            }
636        }
637
638        debug_assert_eq!(fields.avail_regions_by_base.len(), fields.avail_regions_by_size.len());
639        Ok(())
640    }
641
642    pub fn get_region(&self, size: u64, alignment: u64) -> Result<UniquePtr<Region>, Status> {
643        if size == 0 || alignment == 0 || !alignment.is_power_of_two() {
644            return Err(Status::INVALID_ARGS);
645        }
646
647        lock!(let mut guard = self.lock_mu());
648        let mut fields = guard.as_mut().fields_mut();
649
650        let mask = alignment - 1;
651        let inv_mask = !mask;
652
653        let search_key = RegionKey { base: 0, size };
654        let mut iter = fields.avail_regions_by_size.lower_bound(&search_key);
655
656        let mut aligned_base = 0;
657        let mut found_key = None;
658
659        while iter.get().is_some() {
660            let r = iter.get().unwrap();
661            debug_assert!(r.size() >= size);
662
663            // Align base
664            aligned_base = (r.base() + mask) & inv_mask;
665            let overhead = aligned_base - r.base();
666            let leftover = r.size() - size;
667
668            if aligned_base >= r.base() && overhead <= leftover {
669                found_key = Some(r.key_size);
670                break;
671            }
672            iter.move_next();
673        }
674
675        if found_key.is_none() {
676            return Err(Status::NOT_FOUND);
677        }
678
679        // iter is dropped here
680        self.alloc_from_avail_locked(&mut fields, found_key.unwrap(), aligned_base, size)
681    }
682
683    /// Get a region out of the set of currently available regions which has a
684    /// specified size and is pointer-aligned (aligned to `core::mem::size_of::<*const ()>()`).
685    pub fn get_region_pointer_aligned(&self, size: u64) -> Result<UniquePtr<Region>, Status> {
686        self.get_region(size, core::mem::size_of::<*const ()>() as u64)
687    }
688
689    pub fn get_region_specific(
690        &self,
691        requested_region: RegionSpan,
692    ) -> Result<UniquePtr<Region>, Status> {
693        requested_region.validate()?;
694        let base = requested_region.base;
695        let size = requested_region.size;
696
697        lock!(let mut guard = self.lock_mu());
698        let mut fields = guard.as_mut().fields_mut();
699
700        let mut iter = fields.avail_regions_by_base.upper_bound(&base);
701        iter.move_prev();
702
703        if !iter.get().is_some() {
704            return Err(Status::NOT_FOUND);
705        }
706
707        let r = iter.get().unwrap();
708        debug_assert!(r.size() > 0);
709        debug_assert!(r.base() <= base);
710
711        let req_end = base + size - 1;
712        let iter_end = r.base() + r.size() - 1;
713        if req_end > iter_end {
714            return Err(Status::NOT_FOUND);
715        }
716
717        let source_key = r.key_size;
718        // iter is dropped here
719
720        self.alloc_from_avail_locked(&mut fields, source_key, base, size)
721    }
722
723    pub fn test_region_intersects(
724        &self,
725        region: RegionSpan,
726        which: TestRegionSet,
727    ) -> Result<bool, Status> {
728        lock!(let mut guard = self.lock_mu());
729        let fields = guard.as_mut().fields_mut();
730        let tree = match which {
731            TestRegionSet::Allocated => fields.allocated_regions_by_base,
732            TestRegionSet::Available => fields.avail_regions_by_base,
733        };
734        self.intersects_locked(tree, &region)
735    }
736
737    pub fn test_region_contained_by(
738        &self,
739        region: RegionSpan,
740        which: TestRegionSet,
741    ) -> Result<bool, Status> {
742        lock!(let mut guard = self.lock_mu());
743        let fields = guard.as_mut().fields_mut();
744        let tree = match which {
745            TestRegionSet::Allocated => fields.allocated_regions_by_base,
746            TestRegionSet::Available => fields.avail_regions_by_base,
747        };
748        self.contained_by_locked(tree, &region)
749    }
750
751    pub fn allocated_region_count(&self) -> usize {
752        lock!(let guard = self.lock_mu());
753        guard.fields().allocated_regions_by_base.len()
754    }
755
756    pub fn available_region_count(&self) -> usize {
757        lock!(let guard = self.lock_mu());
758        guard.fields().avail_regions_by_base.len()
759    }
760
761    /// Walk the allocated regions and call the user provided callback for each
762    /// entry. Stop when out of entries or the callback returns false.
763    ///
764    /// # Warning
765    /// It is absolutely required that the user callback must not call into any other
766    /// `RegionAllocator` public APIs, and should likely not acquire any locks of any
767    /// kind. This method cannot protect against deadlocks and lock inversions that
768    /// are possible by acquiring the allocation lock before calling the user provided
769    /// callback. Because `KMutex` is not recursive, calling back into the allocator
770    /// from within the callback will deadlock.
771    pub fn walk_allocated_regions<F>(&self, mut cb: F)
772    where
773        F: FnMut(&Region) -> bool,
774    {
775        lock!(let guard = self.lock_mu());
776        for region in guard.fields().allocated_regions_by_base.iter() {
777            if !cb(region) {
778                break;
779            }
780        }
781    }
782
783    /// Walk the available regions and call the user provided callback for each
784    /// entry. Stop when out of entries or the callback returns false.
785    ///
786    /// # Warning
787    /// It is absolutely required that the user callback must not call into any other
788    /// `RegionAllocator` public APIs, and should likely not acquire any locks of any
789    /// kind. This method cannot protect against deadlocks and lock inversions that
790    /// are possible by acquiring the allocation lock before calling the user provided
791    /// callback. Because `KMutex` is not recursive, calling back into the allocator
792    /// from within the callback will deadlock.
793    pub fn walk_available_regions<F>(&self, mut cb: F)
794    where
795        F: FnMut(&Region) -> bool,
796    {
797        lock!(let guard = self.lock_mu());
798        for region in guard.fields().avail_regions_by_base.iter() {
799            if !cb(region) {
800                break;
801            }
802        }
803    }
804
805    // Private helpers
806
807    fn add_subtract_sanity_check_locked_mut(
808        &self,
809        allocated_tree: &mut WavlTree<u64, NonNull<Region>, SortByBase, TrackingSize>,
810        region: &RegionSpan,
811    ) -> Result<(), Status> {
812        if self.intersects_locked(allocated_tree, region)? {
813            Err(Status::INVALID_ARGS)
814        } else {
815            Ok(())
816        }
817    }
818
819    /// Release an allocated region back into the available pool.
820    ///
821    /// # Safety
822    ///
823    /// The caller must ensure that `region_ptr` points to a valid `Region` that is currently in the
824    /// allocated set.
825    unsafe fn release_region(&self, region_ptr: NonNull<Region>) {
826        lock!(let mut guard = self.lock_mu());
827        let mut fields = guard.as_mut().fields_mut();
828
829        // SAFETY: `region_ptr` is guaranteed to be a valid pointer in the allocated set.
830        let region = unsafe { region_ptr.as_ref() };
831        let removed = fields.allocated_regions_by_base.erase(&region.base());
832        debug_assert!(removed.is_some());
833
834        self.add_region_to_avail_locked(&mut fields, region_ptr, AllowOverlap::No);
835    }
836
837    fn add_region_to_avail_locked(
838        &self,
839        fields: &mut RegionAllocatorMuFieldsMut<'_>,
840        region_ptr: NonNull<Region>,
841        allow_overlap: AllowOverlap,
842    ) {
843        // SAFETY: `region_ptr` is a valid pointer to a `Region`.
844        let region = unsafe { region_ptr.as_ref() };
845        let mut region_base = region.base();
846        let mut region_end = region_base + region.size();
847        let original_region_base = region_base;
848
849        {
850            let mut before_cursor = fields.avail_regions_by_base.upper_bound(&region_base);
851            before_cursor.move_prev();
852
853            if before_cursor.get().is_some() {
854                let before = before_cursor.get().unwrap();
855                let before_end = before.base() + before.size();
856                let should_merge = match allow_overlap {
857                    AllowOverlap::Yes => before_end >= region_base,
858                    AllowOverlap::No => before_end == region_base,
859                };
860                if should_merge {
861                    region_end = core::cmp::max(region_end, before_end);
862                    region_base = before.base();
863
864                    let removed_ptr = before_cursor.erase().unwrap();
865                    // SAFETY: `removed_ptr` is a valid pointer to a `Region`.
866                    let key = unsafe { removed_ptr.as_ref().key_size };
867                    fields.avail_regions_by_size.erase(&key);
868                    // SAFETY: `removed_ptr` has been removed from all indices and is ready to be
869                    // destroyed.
870                    unsafe {
871                        Self::destroy_region_raw(fields.region_pool, removed_ptr);
872                    }
873                }
874            }
875        } // before_cursor dropped
876
877        let mut after_cursor = fields.avail_regions_by_base.upper_bound(&original_region_base);
878        while after_cursor.get().is_some() {
879            let after = after_cursor.get().unwrap();
880            let should_merge = match allow_overlap {
881                AllowOverlap::Yes => region_end >= after.base(),
882                AllowOverlap::No => region_end == after.base(),
883            };
884            if !should_merge {
885                break;
886            }
887
888            let after_end = after.base() + after.size();
889            region_end = core::cmp::max(region_end, after_end);
890
891            let removed_ptr = after_cursor.erase().unwrap();
892            // SAFETY: `removed_ptr` is a valid pointer to a `Region`.
893            let key = unsafe { removed_ptr.as_ref().key_size };
894            fields.avail_regions_by_size.erase(&key);
895            // SAFETY: `removed_ptr` has been removed from all indices and is ready to be destroyed.
896            unsafe {
897                Self::destroy_region_raw(fields.region_pool, removed_ptr);
898            }
899
900            if allow_overlap != AllowOverlap::Yes {
901                break;
902            }
903        }
904
905        // SAFETY: `region_ptr` is a valid pointer to a `Region`. Exclusivity is guaranteed
906        // because the region is not currently in any of the allocator's trees (it is either
907        // newly allocated or has been erased from the allocated tree in `release_region`).
908        unsafe {
909            let r = &mut *region_ptr.as_ptr();
910            r.span.base = region_base;
911            r.span.size = region_end - region_base;
912            r.update_key();
913        }
914
915        // SAFETY: Inserting a valid region pointer back into the available indices is safe.
916        unsafe {
917            fields.avail_regions_by_base.insert_raw(region_ptr);
918            fields.avail_regions_by_size.insert_raw(region_ptr);
919        }
920    }
921
922    fn alloc_from_avail_locked(
923        &self,
924        fields: &mut RegionAllocatorMuFieldsMut<'_>,
925        source_key: RegionKey,
926        base: u64,
927        size: u64,
928    ) -> Result<UniquePtr<Region>, Status> {
929        let mut source_cursor = fields.avail_regions_by_size.find_cursor(&source_key);
930        let source_ref = source_cursor.get().ok_or(Status::BAD_STATE)?;
931        let source_base = source_ref.base();
932        let source_size = source_ref.size();
933
934        let overhead = base - source_base;
935        let leftover = source_size - size;
936
937        let split_before = base != source_base;
938        let split_after = overhead < leftover;
939
940        if !split_before && !split_after {
941            let region_ptr = source_cursor.erase().unwrap();
942            // SAFETY: `region_ptr` is a valid pointer to a `Region`.
943            let base = unsafe { region_ptr.as_ref().base() };
944            fields.avail_regions_by_base.erase(&base);
945            // SAFETY: Inserting `region_ptr` into the allocated tree is safe as it's been removed
946            // from available trees.
947            unsafe {
948                fields.allocated_regions_by_base.insert_raw(region_ptr);
949            }
950            // SAFETY: `region_ptr` is a valid, uniquely owned allocation, so wrapping it in
951            // `UniquePtr` is safe.
952            Ok(unsafe { UniquePtr::from_raw(region_ptr.as_ptr()) })
953        } else if !split_before {
954            let after_region_ptr = source_cursor.erase().unwrap();
955            // SAFETY: `after_region_ptr` is a valid pointer to a `Region`.
956            let after_base = unsafe { after_region_ptr.as_ref().base() };
957            fields.avail_regions_by_base.erase(&after_base);
958
959            // The allocator lock is held. We allocate a new region raw.
960            let before_region_ptr =
961                self.create_region_raw(fields.region_pool, RegionSpan { base: after_base, size })?;
962
963            // SAFETY: `after_region_ptr` is a valid pointer to a `Region`. Exclusivity is
964            // guaranteed because we have erased the region from both the size and base available
965            // trees (since its base address is changing).
966            unsafe {
967                let after_region = &mut *after_region_ptr.as_ptr();
968
969                after_region.span.base += size;
970                after_region.span.size -= size;
971                after_region.update_key();
972            }
973
974            // SAFETY: Re-inserting `after_region_ptr` back into available indices and inserting
975            // `before_region_ptr` into the allocated index is safe.
976            unsafe {
977                fields.avail_regions_by_size.insert_raw(after_region_ptr);
978                fields.avail_regions_by_base.insert_raw(after_region_ptr);
979                fields.allocated_regions_by_base.insert_raw(before_region_ptr);
980            }
981            // SAFETY: `before_region_ptr` is a valid, uniquely owned allocation, so wrapping it in
982            // `UniquePtr` is safe.
983            Ok(unsafe { UniquePtr::from_raw(before_region_ptr.as_ptr()) })
984        } else if !split_after {
985            let before_region_ptr = source_cursor.erase().unwrap();
986
987            // The allocator lock is held. We allocate a new region raw.
988            let after_region_ptr =
989                self.create_region_raw(fields.region_pool, RegionSpan { base, size })?;
990
991            // SAFETY: `before_region_ptr` is a pointer to a valid `Region`. Exclusivity is
992            // guaranteed because we hold the allocator lock, and although the region remains in
993            // `avail_regions_by_base` (its base address is unchanged), we have erased it from
994            // `avail_regions_by_size` and will not access it through the base tree during mutation.
995            unsafe {
996                let before_region = &mut *before_region_ptr.as_ptr();
997
998                before_region.span.size -= size;
999                before_region.update_key();
1000            }
1001
1002            // SAFETY: Re-inserting `before_region_ptr` back into available size index and inserting
1003            // `after_region_ptr` into the allocated index is safe.
1004            unsafe {
1005                fields.avail_regions_by_size.insert_raw(before_region_ptr);
1006                fields.allocated_regions_by_base.insert_raw(after_region_ptr);
1007            }
1008            // SAFETY: `after_region_ptr` is a valid, uniquely owned allocation, so wrapping it in
1009            // `UniquePtr` is safe.
1010            Ok(unsafe { UniquePtr::from_raw(after_region_ptr.as_ptr()) })
1011        } else {
1012            let before_region_ptr = source_cursor.erase().unwrap();
1013            // SAFETY: `before_region_ptr` is a valid pointer.
1014            let before_base = unsafe { before_region_ptr.as_ref().base() };
1015            let before_size = unsafe { before_region_ptr.as_ref().size() };
1016
1017            let region_base = before_base + overhead;
1018            let region_size = size;
1019
1020            // The allocator lock is held. We allocate two new regions raw.
1021            let region_ptr = self.create_region_raw(
1022                fields.region_pool,
1023                RegionSpan { base: region_base, size: region_size },
1024            )?;
1025            let after_region_ptr = self.create_region_raw(
1026                fields.region_pool,
1027                RegionSpan { base: region_base + region_size, size: before_size - size - overhead },
1028            )?;
1029
1030            // SAFETY: `before_region_ptr` is a valid pointer to a `Region`. Exclusivity is
1031            // guaranteed because we hold the allocator lock, and although the region remains in
1032            // `avail_regions_by_base` (its base address is unchanged), we have erased it from
1033            // `avail_regions_by_size` and will not access it through the base tree during mutation.
1034            unsafe {
1035                let before_region = &mut *before_region_ptr.as_ptr();
1036
1037                before_region.span.size = overhead;
1038                before_region.update_key();
1039            }
1040
1041            // SAFETY: Re-inserting the split regions `before_region_ptr` and `after_region_ptr`
1042            // back into available indices, and inserting `region_ptr` into the allocated index is
1043            // safe.
1044            unsafe {
1045                fields.avail_regions_by_size.insert_raw(before_region_ptr);
1046                fields.avail_regions_by_size.insert_raw(after_region_ptr);
1047                fields.avail_regions_by_base.insert_raw(after_region_ptr);
1048                fields.allocated_regions_by_base.insert_raw(region_ptr);
1049            }
1050            // SAFETY: `region_ptr` is a valid, uniquely owned allocation, so wrapping it in
1051            // `UniquePtr` is safe.
1052            Ok(unsafe { UniquePtr::from_raw(region_ptr.as_ptr()) })
1053        }
1054    }
1055
1056    fn intersects_locked(
1057        &self,
1058        tree: &mut WavlTree<u64, NonNull<Region>, SortByBase, TrackingSize>,
1059        region: &RegionSpan,
1060    ) -> Result<bool, Status> {
1061        region.validate()?;
1062
1063        let mut iter = tree.lower_bound(&region.base);
1064        if let Some(current) = iter.get()
1065            && current.base() - region.base < region.size
1066        {
1067            return Ok(true);
1068        }
1069
1070        iter.move_prev();
1071        if let Some(prev) = iter.get()
1072            && region.base - prev.base() < prev.size()
1073        {
1074            return Ok(true);
1075        }
1076
1077        Ok(false)
1078    }
1079
1080    fn contained_by_locked(
1081        &self,
1082        tree: &mut WavlTree<u64, NonNull<Region>, SortByBase, TrackingSize>,
1083        region: &RegionSpan,
1084    ) -> Result<bool, Status> {
1085        region.validate()?;
1086        let region_end = region.end()?;
1087
1088        let mut iter = tree.upper_bound(&region.base);
1089        iter.move_prev();
1090
1091        if let Some(r) = iter.get() {
1092            let r_end = r.base() + r.size();
1093            if region.base >= r.base() && region_end <= r_end {
1094                return Ok(true);
1095            }
1096        }
1097
1098        Ok(false)
1099    }
1100
1101    /// Create a region by allocating it from the current RegionPool, or from the
1102    /// heap if we have no assigned region pool.
1103    ///
1104    /// The allocator lock must be held when calling this function.
1105    fn create_region_raw(
1106        &self,
1107        region_pool: &mut Option<RefPtr<RegionPool>>,
1108        span: RegionSpan,
1109    ) -> Result<NonNull<Region>, Status> {
1110        let region = Region::new(span, NonNull::from(self));
1111        if let Some(pool) = region_pool {
1112            let ptr = pool.allocator.alloc_raw().map_err(|_| Status::NO_MEMORY)?;
1113            // SAFETY: `ptr` is verified to be valid and uninitialized. Writing to it
1114            // initializes the slot without dropping uninitialized memory.
1115            unsafe {
1116                core::ptr::write(ptr.as_ptr(), region);
1117            }
1118            Ok(ptr)
1119        } else {
1120            let boxed = Box::try_new(region).map_err(|_| Status::NO_MEMORY)?;
1121            let raw = Box::into_raw(boxed);
1122            // SAFETY: `raw` is a valid non-null pointer returned by `Box::into_raw`.
1123            Ok(unsafe { NonNull::new_unchecked(raw) })
1124        }
1125    }
1126
1127    /// Destroy a region by either returning it to the current RegionPool, or to
1128    /// the heap if we have no assigned region pool.
1129    ///
1130    /// # Safety
1131    /// The caller must ensure that `region_ptr` points to a valid `Region` that
1132    /// is no longer in use (i.e. has been removed from all lists and indices) and
1133    /// was allocated by the allocator context associated with `region_pool`.
1134    unsafe fn destroy_region_raw(
1135        region_pool: &mut Option<RefPtr<RegionPool>>,
1136        region_ptr: NonNull<Region>,
1137    ) {
1138        if let Some(pool) = region_pool {
1139            // SAFETY: `region_ptr` was allocated from `pool.allocator`.
1140            // We drop it in place, then return the raw storage to the slab allocator's free list.
1141            unsafe {
1142                core::ptr::drop_in_place(region_ptr.as_ptr());
1143                pool.allocator.return_to_free_list(region_ptr);
1144            }
1145        } else {
1146            // SAFETY: `region_ptr` was allocated as a heap-allocated `Box<Region>`.
1147            // Reconstructing the `Box` from the raw pointer allows its destructor to
1148            // automatically drop the inner `Region` and deallocate the memory correctly.
1149            unsafe {
1150                let _ = Box::from_raw(region_ptr.as_ptr());
1151            }
1152        }
1153    }
1154}
1155
1156#[pin_init::pinned_drop]
1157impl pin_init::PinnedDrop for RegionAllocator {
1158    fn drop(self: core::pin::Pin<&mut Self>) {
1159        // SAFETY: We can obtain a mutable reference to the fields during drop because we are in
1160        // the drop implementation, and no other references can exist.
1161        let this = unsafe { self.get_unchecked_mut() };
1162        let allocated_regions_by_base = this.allocated_regions_by_base.get_inner_mut();
1163        let avail_regions_by_base = this.avail_regions_by_base.get_inner_mut();
1164        let avail_regions_by_size = this.avail_regions_by_size.get_inner_mut();
1165        let region_pool = this.region_pool.get_inner_mut();
1166
1167        debug_assert!(allocated_regions_by_base.is_empty());
1168        debug_assert_eq!(avail_regions_by_base.len(), avail_regions_by_size.len());
1169
1170        avail_regions_by_base.clear();
1171        while let Some(region_ptr) = avail_regions_by_size.pop_front() {
1172            // SAFETY: Popping the regions and destroying them is safe because the allocator itself
1173            // is being dropped, and no other references to these regions exist.
1174            unsafe {
1175                Self::destroy_region_raw(region_pool, region_ptr);
1176            }
1177        }
1178    }
1179}
1180
1181#[cfg(test)]
1182mod tests;