1use crate::util::cache::CachePadded;
2use crate::util::lifo::{Lifo, LifoGuard};
3use crate::{
4 AllocError, CpuId, InterruptControl, NoInterruptControl, PageSize, PhysicalAllocator,
5 RegionInit,
6};
7use core::marker::PhantomData;
8use core::num::NonZeroUsize;
9
10const N1: NonZeroUsize = NonZeroUsize::MIN;
11
12pub struct DepotAllocator<
25 A,
26 S,
27 const SLOTS: usize,
28 const CAP: usize = 128,
29 const DEPOT_CAP: usize = 512,
30 I: InterruptControl = NoInterruptControl,
31> {
32 backend: A,
33 mags: [CachePadded<Lifo<CAP, I>>; SLOTS],
34 depot: CachePadded<Lifo<DEPOT_CAP, I>>,
35 base_frame: PageSize,
36 #[cfg(any(feature = "stats", test))]
39 frames_flushed: core::sync::atomic::AtomicUsize,
40 #[cfg(any(feature = "stats", test))]
42 peak_depot_len: core::sync::atomic::AtomicUsize,
43 _selector: PhantomData<fn() -> S>,
44}
45
46unsafe impl<
50 A: Sync,
51 S,
52 const SLOTS: usize,
53 const CAP: usize,
54 const DEPOT_CAP: usize,
55 I: InterruptControl,
56> Sync for DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
57{
58}
59unsafe impl<
60 A: Send,
61 S,
62 const SLOTS: usize,
63 const CAP: usize,
64 const DEPOT_CAP: usize,
65 I: InterruptControl,
66> Send for DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
67{
68}
69
70impl<A, S, const SLOTS: usize, const CAP: usize, const DEPOT_CAP: usize, I: InterruptControl>
71 DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
72{
73 pub const fn new(base_frame: PageSize, backend: A) -> Self {
78 assert!(SLOTS > 0, "SLOTS must be > 0");
79 assert!(CAP >= 2, "CAP must be >= 2");
80 assert!(DEPOT_CAP >= 1, "DEPOT_CAP must be >= 1");
81 Self {
82 backend,
83 mags: [const { CachePadded::new(Lifo::new()) }; SLOTS],
84 depot: CachePadded::new(Lifo::new()),
85 base_frame,
86 #[cfg(any(feature = "stats", test))]
87 frames_flushed: core::sync::atomic::AtomicUsize::new(0),
88 #[cfg(any(feature = "stats", test))]
89 peak_depot_len: core::sync::atomic::AtomicUsize::new(0),
90 _selector: PhantomData,
91 }
92 }
93
94 #[cfg(any(feature = "stats", test))]
96 pub(crate) fn backend(&self) -> &A {
97 &self.backend
98 }
99
100 #[cfg(any(feature = "stats", test))]
104 pub fn cached_frames(&self) -> usize {
105 let mut total = 0;
106 for mag in &self.mags {
107 total += mag.lock().len();
108 }
109 total + self.depot_len()
110 }
111
112 #[cfg(any(feature = "stats", test))]
115 pub fn depot_len(&self) -> usize {
116 self.depot.lock().len()
117 }
118
119 #[cfg(any(feature = "stats", test))]
121 pub fn peak_depot_len(&self) -> usize {
122 self.peak_depot_len
123 .load(core::sync::atomic::Ordering::Relaxed)
124 }
125
126 #[cfg(any(feature = "stats", test))]
130 pub fn frames_flushed(&self) -> usize {
131 self.frames_flushed
132 .load(core::sync::atomic::Ordering::Relaxed)
133 }
134}
135
136impl<
137 A: PhysicalAllocator,
138 S: CpuId,
139 const SLOTS: usize,
140 const CAP: usize,
141 const DEPOT_CAP: usize,
142 I: InterruptControl,
143> DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
144{
145 #[inline(always)]
148 const fn batch() -> usize {
149 CAP / 2
150 }
151
152 fn depot_to_mag(&self, mag: &mut LifoGuard<'_, CAP, I>, want: usize) -> usize {
156 let mut depot = self.depot.lock();
157 let mut moved = 0;
158 while moved < want {
159 match depot.pop() {
160 Some(addr) => {
161 mag.push(addr);
162 moved += 1;
163 }
164 None => break,
165 }
166 }
167 moved
168 }
169
170 fn push_to_depot(&self, src: &[usize]) -> usize {
173 let mut depot = self.depot.lock();
174 let pushed = depot.push_slice(src);
175 #[cfg(any(feature = "stats", test))]
176 self.peak_depot_len
177 .fetch_max(depot.len(), core::sync::atomic::Ordering::Relaxed);
178 pushed
179 }
180
181 fn alloc_one(&self) -> Result<usize, AllocError> {
182 let current = S::current_cpu() % SLOTS;
183 {
184 let mut mag = self.mags[current].lock();
185 if let Some(addr) = mag.pop() {
186 return Ok(addr);
187 }
188
189 if self.depot_to_mag(&mut mag, Self::batch()) > 0
191 && let Some(addr) = mag.pop()
192 {
193 return Ok(addr);
194 }
195
196 let mut filled = 0usize;
198 while filled < Self::batch() {
199 match self.backend.allocate_physical(self.base_frame, N1) {
200 Ok(addr) => {
201 mag.push(addr);
202 filled += 1;
203 }
204 Err(AllocError::OutOfMemory) => break,
205 Err(e) => return Err(e),
206 }
207 }
208
209 if let Some(addr) = mag.pop() {
210 return Ok(addr);
211 }
212 }
213
214 for offset in 1..SLOTS {
217 let slot = (current + offset) % SLOTS;
218 if let Some(addr) = self.mags[slot].lock().pop() {
219 return Ok(addr);
220 }
221 }
222
223 Err(AllocError::OutOfMemory)
224 }
225
226 unsafe fn free_one(&self, addr: usize) {
230 let mut mag = self.mags[S::current_cpu() % SLOTS].lock();
231 if mag.is_full() {
232 let overflow = mag.take_top(Self::batch());
234 let pushed = self.push_to_depot(overflow);
235 for &a in &overflow[pushed..] {
236 unsafe { self.backend.deallocate_physical(self.base_frame, N1, a) };
239 }
240 }
241 mag.push(addr);
242 }
243
244 fn drain_chunk(&self, want: usize) -> usize {
247 let mut moved = 0;
248 let mut batch = [0usize; CAP];
249 while moved < want {
250 let take = (want - moved).min(CAP);
251 let n = {
252 let mut depot = self.depot.lock();
253 let mut got = 0;
254 while got < take {
255 match depot.pop() {
256 Some(addr) => {
257 batch[got] = addr;
258 got += 1;
259 }
260 None => break,
261 }
262 }
263 got
264 };
265 if n == 0 {
266 break;
267 }
268 for &addr in &batch[..n] {
269 unsafe { self.backend.deallocate_physical(self.base_frame, N1, addr) };
272 }
273 moved += n;
274 }
275 #[cfg(any(feature = "stats", test))]
276 self.frames_flushed
277 .fetch_add(moved, core::sync::atomic::Ordering::Relaxed);
278 moved
279 }
280
281 fn drain_magazine(&self, slot: usize, want: usize) -> usize {
284 let mut moved = 0;
285 let mut batch = [0usize; CAP];
286 while moved < want {
287 let take = (want - moved).min(CAP);
288 let n = {
289 let mut mag = self.mags[slot].lock();
290 let mut got = 0;
291 while got < take {
292 match mag.pop() {
293 Some(addr) => {
294 batch[got] = addr;
295 got += 1;
296 }
297 None => break,
298 }
299 }
300 got
301 };
302 if n == 0 {
303 break;
304 }
305 for &addr in &batch[..n] {
306 unsafe { self.backend.deallocate_physical(self.base_frame, N1, addr) };
309 }
310 moved += n;
311 }
312 #[cfg(any(feature = "stats", test))]
313 self.frames_flushed
314 .fetch_add(moved, core::sync::atomic::Ordering::Relaxed);
315 moved
316 }
317
318 pub fn flush(&self) {
322 let depot = self.depot.lock().len();
323 self.drain_chunk(depot);
324 for slot in 0..SLOTS {
325 let magazine = self.mags[slot].lock().len();
326 self.drain_magazine(slot, magazine);
327 }
328 }
329
330 fn recover_after_oom(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
333 let mut chunk = Self::batch();
334 loop {
335 let moved = self.drain_chunk(chunk);
336 if moved == 0 {
337 break;
338 }
339 match self.backend.allocate_physical(ps, count) {
340 Err(AllocError::OutOfMemory) => {}
341 other => return other,
342 }
343 chunk = chunk.saturating_mul(2);
344 }
345
346 let current = S::current_cpu() % SLOTS;
347 for offset in 0..SLOTS {
348 let slot = (current + offset) % SLOTS;
349 let mut remaining = self.mags[slot].lock().len();
350 while remaining > 0 {
351 let moved = self.drain_magazine(slot, remaining.min(Self::batch()));
352 if moved == 0 {
353 break;
354 }
355 remaining -= moved;
356 match self.backend.allocate_physical(ps, count) {
357 Err(AllocError::OutOfMemory) => {}
358 other => return other,
359 }
360 }
361 }
362
363 Err(AllocError::OutOfMemory)
364 }
365
366 fn alloc_multiframe(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
367 match self.backend.allocate_physical(ps, count) {
368 Err(AllocError::OutOfMemory) => {}
369 other => return other,
370 }
371
372 self.recover_after_oom(ps, count)
373 }
374}
375
376unsafe impl<
377 A: PhysicalAllocator,
378 S: CpuId,
379 const SLOTS: usize,
380 const CAP: usize,
381 const DEPOT_CAP: usize,
382 I: InterruptControl,
383> PhysicalAllocator for DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
384{
385 fn allocate_physical(&self, ps: PageSize, count: NonZeroUsize) -> Result<usize, AllocError> {
386 if ps == self.base_frame && count == N1 {
387 return self.alloc_one();
388 }
389 self.alloc_multiframe(ps, count)
390 }
391
392 unsafe fn deallocate_physical(&self, ps: PageSize, count: NonZeroUsize, phys: usize) {
393 if ps == self.base_frame && count == N1 {
394 unsafe { self.free_one(phys) };
397 } else {
398 unsafe { self.backend.deallocate_physical(ps, count, phys) };
400 }
401 }
402}
403
404unsafe impl<
405 A: RegionInit,
406 S,
407 const SLOTS: usize,
408 const CAP: usize,
409 const DEPOT_CAP: usize,
410 I: InterruptControl,
411> RegionInit for DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
412{
413 unsafe fn try_init(
414 &self,
415 phys_base: usize,
416 span_len: usize,
417 usable: &[crate::allocator::PhysRange],
418 ) -> Result<(), crate::InitError> {
419 unsafe { self.backend.try_init(phys_base, span_len, usable) }
421 }
422
423 unsafe fn add_usable(&self, base: usize, len: usize) {
424 unsafe { self.backend.add_usable(base, len) };
426 }
427}
428
429#[cfg(any(feature = "stats", test))]
430impl<
431 A: crate::AllocatorStats,
432 S,
433 const SLOTS: usize,
434 const CAP: usize,
435 const DEPOT_CAP: usize,
436 I: InterruptControl,
437> crate::AllocatorStats for DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>
438{
439 fn total_bytes(&self) -> usize {
440 self.backend().total_bytes()
441 }
442
443 fn free_bytes(&self) -> usize {
444 self.backend().free_bytes() + self.cached_frames() * self.base_frame.bytes()
445 }
446
447 fn largest_free_bytes(&self) -> usize {
448 self.backend().largest_free_bytes()
449 }
450}