frame_alloc/implementations/wrappers/regioned.rs
1use crate::strategies::cpu_id::NoCpuId;
2use crate::util::cache::CachePadded;
3use crate::{AllocError, CpuId, InitError, PageSize, PhysRange, PhysicalAllocator, RegionInit};
4use core::marker::PhantomData;
5use core::mem::{ManuallyDrop, MaybeUninit};
6use core::num::NonZeroUsize;
7use core::ptr;
8use core::sync::atomic::AtomicUsize;
9use core::sync::atomic::Ordering::Relaxed;
10
11/// Move `[A; N]` into `[CachePadded<A>; N]` in a `const` context.
12///
13/// `pub(crate)` only so the white-box drop test can reach it; not part of the
14/// public API.
15pub(crate) const fn pad_cells<A, const N: usize>(items: [A; N]) -> [CachePadded<A>; N] {
16 let src = ManuallyDrop::new(items);
17 let src_ptr = &src as *const ManuallyDrop<[A; N]> as *const A;
18 let mut cells: [MaybeUninit<CachePadded<A>>; N] = [const { MaybeUninit::uninit() }; N];
19 let mut i = 0;
20 while i < N {
21 // SAFETY: `src_ptr.add(i)` is in bounds for `i < N` and is read exactly
22 // once across the loop.
23 let a = unsafe { ptr::read(src_ptr.add(i)) };
24 cells[i] = MaybeUninit::new(CachePadded::new(a));
25 i += 1;
26 }
27 // SAFETY: every cell was initialised above, and `[MaybeUninit<T>; N]` shares
28 // layout with `[T; N]`. `cells` is `MaybeUninit`, so its elements are never
29 // dropped — no element is dropped twice.
30 unsafe {
31 ptr::read(&cells as *const [MaybeUninit<CachePadded<A>>; N] as *const [CachePadded<A>; N])
32 }
33}
34
35/// A wrapper that routes over **`REGIONS` disjoint physical spans**, one
36/// unmodified inner allocator `A` per span.
37///
38/// All regions are expected to share the same backend configuration — base
39/// frame, order count, and `max_page`.
40///
41/// **Allocation** starts on the calling CPU's home region
42/// ([`CpuId::current_cpu`] modulo `REGIONS`, or region 0 under the default
43/// [`NoCpuId`]) and work-steals round-robin through the other regions on local
44/// [`OutOfMemory`](AllocError::OutOfMemory).
45///
46/// **Deallocation** and [`add_usable`](Self::add_usable) route by physical
47/// address to the owning region. An address owned by no region is *dropped*
48/// (debug builds panic).
49pub struct RegionedAllocator<const REGIONS: usize, A, S = NoCpuId> {
50 /// One independent inner allocator per disjoint span, each on its own cache line.
51 regions: [CachePadded<A>; REGIONS],
52 /// `[base, end)` physical span per region, used to route a physical address
53 /// back to its owning region. Written once by `init_at`, read-only after;
54 /// `(0, 0)` marks an uninitialised region.
55 bounds: [(AtomicUsize, AtomicUsize); REGIONS],
56 /// Base frame size; mirrors each inner `A`'s own base frame.
57 base_frame: PageSize,
58 _selector: PhantomData<fn() -> S>,
59}
60
61impl<const REGIONS: usize, A, S> RegionedAllocator<REGIONS, A, S> {
62 /// Create a new, uninitialised regioned allocator from `REGIONS` pre-built
63 /// inner allocators.
64 ///
65 /// `base_frame` must match the base frame size of every inner allocator.
66 pub const fn new(base_frame: PageSize, regions: [A; REGIONS]) -> Self {
67 assert!(REGIONS > 0, "REGIONS must be > 0");
68 Self {
69 regions: pad_cells(regions),
70 bounds: [const { (AtomicUsize::new(0), AtomicUsize::new(0)) }; REGIONS],
71 base_frame,
72 _selector: PhantomData,
73 }
74 }
75
76 /// Fallible initialisation of region `idx` over the disjoint physical span
77 /// `[phys_base, phys_base + span_len)`, freeing only `usable`. Delegates to
78 /// the backend's [`RegionInit::try_init`] and records the span for address
79 /// routing.
80 ///
81 /// Call at most once *successfully* per region index, single-threaded and
82 /// before publishing the allocator to concurrent users. On `Err` the region
83 /// is left untouched - a corrected retry is permitted.
84 ///
85 /// # Errors
86 ///
87 /// * [`InitError::Misaligned`] if `phys_base` is not base-frame aligned;
88 /// * [`InitError::InvalidSpan`] if `span_len` is zero, not a base-frame
89 /// multiple, or overflows the address space;
90 /// * [`InitError::AlreadyInitialized`] if region `idx` was already initialised;
91 /// * [`InitError::OverlapsRegion`] if the span overlaps an initialised region;
92 /// * any error returned by the backend's [`RegionInit::try_init`].
93 ///
94 /// # Panics
95 ///
96 /// If `idx >= REGIONS` (a structural bug, like any out-of-bounds index).
97 ///
98 /// # Safety
99 ///
100 /// The `(phys_base, span_len, usable)` arguments must satisfy the backend's
101 /// [`RegionInit::try_init`] safety contract, including the single-threaded
102 /// pre-publication requirement. The spans of distinct regions must be
103 /// disjoint, and no two `try_init_at` calls may race with each other.
104 pub unsafe fn try_init_at(
105 &self,
106 idx: usize,
107 phys_base: usize,
108 span_len: usize,
109 usable: &[PhysRange],
110 ) -> Result<(), InitError>
111 where
112 A: RegionInit,
113 {
114 assert!(
115 idx < REGIONS,
116 "region index {idx} out of range (REGIONS={REGIONS})"
117 );
118 if !phys_base.is_multiple_of(self.base_frame.bytes()) {
119 return Err(InitError::Misaligned {
120 required: self.base_frame.bytes(),
121 });
122 }
123 if span_len == 0 || !span_len.is_multiple_of(self.base_frame.bytes()) {
124 return Err(InitError::InvalidSpan);
125 }
126 let span_end = phys_base
127 .checked_add(span_len)
128 .ok_or(InitError::InvalidSpan)?;
129
130 if self.bounds[idx].0.load(Relaxed) != 0 || self.bounds[idx].1.load(Relaxed) != 0 {
131 return Err(InitError::AlreadyInitialized);
132 }
133 for j in 0..REGIONS {
134 if j == idx {
135 continue;
136 }
137 let lo = self.bounds[j].0.load(Relaxed);
138 let hi = self.bounds[j].1.load(Relaxed);
139 if hi != 0 && phys_base < hi && span_end > lo {
140 return Err(InitError::OverlapsRegion { other: j });
141 }
142 }
143
144 // SAFETY: the caller upholds the backend's `try_init` contract for this
145 // span. On `Err` the backend guarantees it is untouched, and we skip the
146 // bounds stores below, so the region stays uninitialised and retryable.
147 unsafe { self.regions[idx].try_init(phys_base, span_len, usable) }?;
148
149 self.bounds[idx].0.store(phys_base, Relaxed);
150 self.bounds[idx].1.store(span_end, Relaxed);
151 Ok(())
152 }
153
154 /// As [`try_init_at`](Self::try_init_at) but panic on any [`InitError`].
155 ///
156 /// Call once per region index, single-threaded, before publishing the
157 /// allocator to concurrent users. (Same as [`try_init_at`](Self::try_init_at)).
158 ///
159 /// # Safety
160 ///
161 /// `idx < REGIONS`. The `(phys_base, span_len, usable)` arguments must satisfy
162 /// the backend's [`RegionInit::try_init`] contract, including the
163 /// single-threaded pre-publication requirement. The spans of distinct regions
164 /// must be disjoint, and no two `init_at`/`try_init_at` calls may race with
165 /// each other.
166 ///
167 /// # Panics
168 ///
169 /// If `idx >= REGIONS`, or if initialisation returns an [`InitError`].
170 pub unsafe fn init_at(
171 &self,
172 idx: usize,
173 phys_base: usize,
174 span_len: usize,
175 usable: &[PhysRange],
176 ) where
177 A: RegionInit,
178 {
179 // SAFETY: forwarded under the caller's trait-level guarantees.
180 match unsafe { self.try_init_at(idx, phys_base, span_len, usable) } {
181 Ok(()) => {}
182 Err(e) => panic!("RegionedAllocator::init_at failed: {e:?}"),
183 }
184 }
185
186 /// Transition a reserved in-span range to free, routed to the owning region.
187 /// The repeatable post-init counterpart of [`RegionInit::add_usable`]
188 ///
189 /// # Safety
190 ///
191 /// `[base, base + len)` must lie within a single initialised region's span,
192 /// be currently reserved (not already free), exclusively owned, and not
193 /// aliased while registered.
194 pub unsafe fn add_usable(&self, base: usize, len: usize)
195 where
196 A: RegionInit,
197 {
198 let Some(owner) = self.region_of(base) else {
199 debug_assert!(false, "add_usable: base {base:#x} not owned by any region");
200 return;
201 };
202 debug_assert!(
203 base.checked_add(len)
204 .is_some_and(|end| end <= self.bounds[owner].1.load(Relaxed)),
205 "add_usable range escapes its owning region"
206 );
207 // SAFETY: `[base, base + len)` lies in `owner`'s span; the backend's
208 // `add_usable` contract is upheld.
209 unsafe { self.regions[owner].add_usable(base, len) };
210 }
211
212 /// Allocate strictly within region `idx`
213 ///
214 /// # Panics
215 ///
216 /// If `idx >= REGIONS`.
217 pub fn alloc_in_region(
218 &self,
219 idx: usize,
220 ps: PageSize,
221 count: NonZeroUsize,
222 ) -> Result<usize, AllocError>
223 where
224 A: PhysicalAllocator,
225 {
226 assert!(
227 idx < REGIONS,
228 "region index {idx} out of range (REGIONS={REGIONS})"
229 );
230 self.regions[idx].allocate_physical(ps, count)
231 }
232
233 /// Allocate from the first region in `chain` that can satisfy the request,
234 /// trying them in the given order
235 ///
236 /// # Panics
237 ///
238 /// If any index in `chain` is `>= REGIONS`.
239 pub fn alloc_in_chain(
240 &self,
241 chain: &[usize],
242 ps: PageSize,
243 count: NonZeroUsize,
244 ) -> Result<usize, AllocError>
245 where
246 A: PhysicalAllocator,
247 {
248 for &idx in chain {
249 assert!(
250 idx < REGIONS,
251 "region index {idx} in chain out of range (REGIONS={REGIONS})"
252 );
253 match self.regions[idx].allocate_physical(ps, count) {
254 Ok(phys) => return Ok(phys),
255 Err(AllocError::OutOfMemory) => continue,
256 Err(e) => return Err(e),
257 }
258 }
259 Err(AllocError::OutOfMemory)
260 }
261
262 /// Index of the region whose span contains `phys`, or `None` if no
263 /// initialised region owns it.
264 #[inline]
265 fn region_of(&self, phys: usize) -> Option<usize> {
266 for i in 0..REGIONS {
267 let lo = self.bounds[i].0.load(Relaxed);
268 let hi = self.bounds[i].1.load(Relaxed);
269 if phys >= lo && phys < hi {
270 return Some(i);
271 }
272 }
273 None
274 }
275
276 /// The inner per-region allocators, for diagnostics.
277 #[cfg(any(feature = "stats", test))]
278 pub(crate) fn regions(&self) -> impl ExactSizeIterator<Item = &A> {
279 self.regions.iter().map(|r| &**r)
280 }
281
282 /// The recorded `(base, end)` routing span of region `idx`; `(0, 0)` marks an
283 /// uninitialised region. (Test only).
284 #[cfg(test)]
285 pub(crate) fn region_bounds(&self, idx: usize) -> (usize, usize) {
286 (
287 self.bounds[idx].0.load(Relaxed),
288 self.bounds[idx].1.load(Relaxed),
289 )
290 }
291}
292
293unsafe impl<const REGIONS: usize, A, S> PhysicalAllocator for RegionedAllocator<REGIONS, A, S>
294where
295 A: PhysicalAllocator,
296 S: CpuId,
297{
298 fn allocate_physical(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
299 let start = S::current_cpu() % REGIONS;
300 for offset in 0..REGIONS {
301 let i = (start + offset) % REGIONS;
302 match self.regions[i].allocate_physical(ps, count) {
303 Ok(phys) => return Ok(phys),
304 Err(AllocError::OutOfMemory) => continue,
305 Err(e) => return Err(e),
306 }
307 }
308 Err(AllocError::OutOfMemory)
309 }
310
311 unsafe fn deallocate_physical(&self, ps: PageSize, count: NonZeroUsize, phys: usize) {
312 match self.region_of(phys) {
313 // SAFETY: `phys` lies in `owner`'s span; the caller upholds the
314 // per-frame contract of the inner allocator.
315 Some(owner) => unsafe { self.regions[owner].deallocate_physical(ps, count, phys) },
316 None => debug_assert!(
317 false,
318 "deallocate_physical: phys {phys:#x} not owned by any region"
319 ),
320 }
321 }
322}
323
324#[cfg(any(feature = "stats", test))]
325impl<const REGIONS: usize, A: crate::AllocatorStats, S> crate::AllocatorStats
326 for RegionedAllocator<REGIONS, A, S>
327{
328 fn total_bytes(&self) -> usize {
329 self.regions().map(|r| r.total_bytes()).sum()
330 }
331
332 fn free_bytes(&self) -> usize {
333 self.regions().map(|r| r.free_bytes()).sum()
334 }
335
336 fn largest_free_bytes(&self) -> usize {
337 self.regions()
338 .map(|r| r.largest_free_bytes())
339 .max()
340 .unwrap_or(0)
341 }
342}