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, ®ion)?;
396
397 if allow_overlap != AllowOverlap::Yes {
398 let intersects = self.intersects_locked(fields.avail_regions_by_base, ®ion)?;
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(®ion.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(®ion.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(®ion.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, ®ion)
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, ®ion)
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(®ion.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(®ion_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(®ion.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(®ion.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;