1use alloc::rc::Rc;
29use core::cell::RefCell;
30use core::ops::Deref;
31
32use fixedbitset::FixedBitSet;
33use smallvec::SmallVec;
34
35use super::buffer::{AllocError, Allocation, BufferProvider};
36
37#[derive(Debug)]
55struct SendWrap<T>(T);
56
57impl<T: Clone> Clone for SendWrap<T> {
58 fn clone(&self) -> Self {
59 Self(self.0.clone())
60 }
61}
62
63impl<T> Deref for SendWrap<T> {
64 type Target = T;
65 fn deref(&self) -> &T {
66 &self.0
67 }
68}
69
70#[derive(Debug, Clone)]
71struct Slab<const N: usize> {
72 base_addr: u64,
73 used_slots: FixedBitSet,
74 run_starts: FixedBitSet,
75 last_free_run: Option<Allocation>,
76}
77
78impl<const N: usize> Slab<N> {
79 fn new(base_addr: u64, region_len: usize) -> Result<Self, AllocError> {
80 let usable = region_len - (region_len % N);
81 let num_slots = usable / N;
82 let used_slots = FixedBitSet::with_capacity(num_slots);
83 let run_starts = FixedBitSet::with_capacity(num_slots);
84
85 if !base_addr.is_multiple_of(N as u64) {
86 return Err(AllocError::InvalidAlign(base_addr));
87 }
88 if num_slots == 0 {
89 return Err(AllocError::EmptyRegion);
90 }
91
92 Ok(Self {
93 base_addr,
94 used_slots,
95 run_starts,
96 last_free_run: None,
97 })
98 }
99
100 fn addr_of(&self, slot_idx: usize) -> Option<u64> {
101 self.base_addr
102 .checked_add((slot_idx as u64).checked_mul(N as u64)?)
103 }
104
105 fn slot_of(&self, addr: u64) -> usize {
106 let off = (addr - self.base_addr) as usize;
107 off / N
108 }
109
110 fn checked_slot_of(&self, addr: u64, len: usize) -> Result<usize, AllocError> {
111 if addr < self.base_addr {
112 return Err(AllocError::InvalidFree(addr, len));
113 }
114
115 let off = (addr - self.base_addr) as usize;
116 if !off.is_multiple_of(N) {
117 return Err(AllocError::InvalidFree(addr, len));
118 }
119
120 let slot = off / N;
121 if slot >= self.used_slots.len() {
122 return Err(AllocError::InvalidFree(addr, len));
123 }
124
125 Ok(slot)
126 }
127
128 fn live_run_slots_at(&self, start: usize) -> Option<usize> {
129 if start >= self.used_slots.len()
130 || !self.used_slots.contains(start)
131 || !self.run_starts.contains(start)
132 {
133 return None;
134 }
135
136 let mut end = start + 1;
137 while end < self.used_slots.len()
138 && self.used_slots.contains(end)
139 && !self.run_starts.contains(end)
140 {
141 end += 1;
142 }
143
144 Some(end - start)
145 }
146
147 fn maybe_invalidate_last_run(&mut self, alloc: Allocation) {
148 if let Some(run) = &self.last_free_run {
149 let new_end = alloc.addr + alloc.len as u64;
150 let run_end = run.addr + run.len as u64;
151
152 if alloc.addr < run_end && run.addr < new_end {
153 self.last_free_run = None;
154 }
155 }
156 }
157
158 fn find_slots(&mut self, slots_num: usize) -> Option<usize> {
159 debug_assert!(slots_num > 0);
160
161 if let Some(alloc) = self.last_free_run
162 && alloc.len >= slots_num * N
163 {
164 let pos = self.slot_of(alloc.addr);
165 let _ = self.last_free_run.take();
166 return Some(pos);
167 }
168
169 let total = self.used_slots.len();
170 self.used_slots.zeroes().find(|&next_free| {
171 let end = next_free + slots_num;
172 end <= total && self.used_slots.count_zeroes(next_free..end) == slots_num
173 })
174 }
175
176 fn alloc(&mut self, len: usize) -> Result<Allocation, AllocError> {
177 if len == 0 {
178 return Err(AllocError::InvalidArg);
179 }
180
181 let total = self.used_slots.len();
182 let need_slots = len.div_ceil(N);
183 if need_slots > total {
184 return Err(AllocError::OutOfMemory);
185 }
186
187 let idx = self.find_slots(need_slots).ok_or(AllocError::NoSpace)?;
188 self.used_slots.insert_range(idx..idx + need_slots);
189 self.run_starts.insert(idx);
190 let addr = self.addr_of(idx).ok_or(AllocError::Overflow)?;
191
192 let alloc = Allocation {
193 addr,
194 len: need_slots * N,
195 };
196
197 self.maybe_invalidate_last_run(alloc);
198 Ok(alloc)
199 }
200
201 fn dealloc_addr(&mut self, addr: u64) -> Result<(), AllocError> {
202 let start = self.checked_slot_of(addr, 0)?;
203 let run_slots = self
204 .live_run_slots_at(start)
205 .ok_or(AllocError::InvalidFree(addr, 0))?;
206 self.dealloc_run(start, run_slots, addr)
207 }
208
209 fn dealloc_run(&mut self, start: usize, run_slots: usize, addr: u64) -> Result<(), AllocError> {
210 let len = run_slots * N;
211 self.used_slots.remove_range(start..start + run_slots);
212 self.run_starts.set(start, false);
213 self.last_free_run = Some(Allocation { addr, len });
214 Ok(())
215 }
216
217 fn allocation_len(&self, addr: u64) -> Result<usize, AllocError> {
218 let start = self.checked_slot_of(addr, 0)?;
219 let run_slots = self
220 .live_run_slots_at(start)
221 .ok_or(AllocError::InvalidFree(addr, 0))?;
222 Ok(run_slots * N)
223 }
224
225 fn capacity(&self) -> usize {
226 self.used_slots.len() * N
227 }
228
229 fn range(&self) -> core::ops::Range<u64> {
230 self.base_addr..self.base_addr + self.capacity() as u64
231 }
232
233 fn contains(&self, addr: u64) -> bool {
234 self.range().contains(&addr)
235 }
236
237 fn reset(&mut self) {
238 self.used_slots.clear();
239 self.run_starts.clear();
240 self.last_free_run = None;
241 }
242}
243
244#[cfg(test)]
245impl<const N: usize> Slab<N> {
246 fn free_bytes(&self) -> usize {
247 (self.used_slots.len() - self.used_slots.count_ones(..)) * N
248 }
249}
250
251#[inline]
252fn align_up(val: usize, align: usize) -> Result<usize, AllocError> {
253 if align == 0 {
254 return Err(AllocError::InvalidArg);
255 }
256
257 val.checked_next_multiple_of(align)
258 .ok_or(AllocError::Overflow)
259}
260
261#[derive(Debug)]
262struct Inner<const L: usize, const U: usize> {
263 lower: Slab<L>,
264 upper: Slab<U>,
265}
266
267unsafe impl<const L: usize, const U: usize> Send for SendWrap<Rc<RefCell<Inner<L, U>>>> {}
270
271#[derive(Debug, Clone)]
273pub struct BufferPool<const L: usize = 256, const U: usize = 4096> {
274 inner: SendWrap<Rc<RefCell<Inner<L, U>>>>,
275}
276
277impl<const L: usize, const U: usize> BufferPool<L, U> {
278 pub fn new(base_addr: u64, region_len: usize) -> Result<Self, AllocError> {
280 let inner = Inner::<L, U>::new(base_addr, region_len)?;
281 Ok(Self {
282 inner: SendWrap(Rc::new(RefCell::new(inner))),
283 })
284 }
285}
286
287impl BufferPool {
288 pub const fn upper_slot_size() -> usize {
290 4096
291 }
292
293 pub const fn lower_slot_size() -> usize {
295 256
296 }
297}
298
299#[cfg(all(test, loom))]
300#[derive(Debug, Clone)]
301pub struct BufferPoolSync<const L: usize = 256, const U: usize = 4096> {
302 inner: std::sync::Arc<std::sync::Mutex<Inner<L, U>>>,
303}
304
305#[cfg(all(test, loom))]
306impl<const L: usize, const U: usize> BufferPoolSync<L, U> {
307 pub fn new(base_addr: u64, region_len: usize) -> Result<Self, AllocError> {
309 let inner = Inner::<L, U>::new(base_addr, region_len)?;
310 Ok(Self {
311 inner: std::sync::Arc::new(std::sync::Mutex::new(inner)),
312 })
313 }
314}
315
316impl<const L: usize, const U: usize> Inner<L, U> {
317 pub fn new(base_addr: u64, region_len: usize) -> Result<Self, AllocError> {
319 const LOWER_FRACTION: usize = 8;
320
321 let base = usize::try_from(base_addr).map_err(|_| AllocError::Overflow)?;
322 let region_end = base.checked_add(region_len).ok_or(AllocError::Overflow)?;
323
324 let lower_base = align_up(base, L)?;
325 let usable = region_end
326 .checked_sub(lower_base)
327 .ok_or(AllocError::EmptyRegion)?;
328
329 let lower_region = usable / LOWER_FRACTION;
330 let lower = Slab::<L>::new(lower_base as u64, lower_region)?;
331
332 let upper_base = lower_base
333 .checked_add(lower.capacity())
334 .ok_or(AllocError::Overflow)?;
335
336 let upper_base = align_up(upper_base, U)?;
337 let upper_region = region_end
338 .checked_sub(upper_base)
339 .ok_or(AllocError::EmptyRegion)?;
340
341 let upper = Slab::<U>::new(upper_base as u64, upper_region)?;
342 Ok(Self { lower, upper })
343 }
344
345 pub fn alloc(&mut self, len: usize) -> Result<Allocation, AllocError> {
347 if len <= L {
348 match self.lower.alloc(len) {
349 Ok(alloc) => return Ok(alloc),
350 Err(AllocError::NoSpace) => {}
351 Err(e) => return Err(e),
352 }
353 }
354
355 self.upper.alloc(len)
357 }
358
359 pub fn dealloc_addr(&mut self, addr: u64) -> Result<(), AllocError> {
361 if self.lower.contains(addr) {
362 self.lower.dealloc_addr(addr)
363 } else {
364 self.upper.dealloc_addr(addr)
365 }
366 }
367
368 pub fn allocation_len(&self, addr: u64) -> Result<usize, AllocError> {
370 if self.lower.contains(addr) {
371 self.lower.allocation_len(addr)
372 } else {
373 self.upper.allocation_len(addr)
374 }
375 }
376}
377
378impl<const L: usize, const U: usize> BufferProvider for BufferPool<L, U> {
379 fn max_alloc_len(&self) -> usize {
380 U
381 }
382
383 fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
384 self.inner.borrow_mut().alloc(len)
385 }
386
387 fn alloc_sg(&self, total_len: usize) -> Result<SmallVec<[Allocation; 4]>, AllocError> {
388 Ok(smallvec::smallvec![self.alloc(total_len)?])
389 }
390
391 fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
392 self.inner.borrow_mut().dealloc_addr(addr)
393 }
394
395 fn reset(&self) {
396 let mut inner = self.inner.borrow_mut();
397 inner.lower.reset();
398 inner.upper.reset();
399 }
400}
401
402impl<const L: usize, const U: usize> BufferPool<L, U> {
403 pub fn dealloc_addr(&self, addr: u64) -> Result<(), AllocError> {
405 self.inner.borrow_mut().dealloc_addr(addr)
406 }
407
408 pub fn allocation_len(&self, addr: u64) -> Result<usize, AllocError> {
410 self.inner.borrow().allocation_len(addr)
411 }
412}
413
414#[cfg(all(test, loom))]
415impl<const L: usize, const U: usize> BufferProvider for BufferPoolSync<L, U> {
416 fn max_alloc_len(&self) -> usize {
417 U
418 }
419
420 fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
421 self.inner.lock().expect("poisoned mutex").alloc(len)
422 }
423
424 fn alloc_sg(&self, total_len: usize) -> Result<SmallVec<[Allocation; 4]>, AllocError> {
425 Ok(smallvec::smallvec![self.alloc(total_len)?])
426 }
427
428 fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
429 self.inner
430 .lock()
431 .expect("poisoned mutex")
432 .dealloc_addr(addr)
433 }
434}
435
436struct RecycleList {
443 base_addr: u64,
444 slot_size: usize,
445 count: usize,
446 free: SmallVec<[u64; 64]>,
448 allocated: FixedBitSet,
450}
451
452unsafe impl Send for SendWrap<Rc<RefCell<RecycleList>>> {}
455
456impl RecycleList {
457 fn new(base_addr: u64, region_len: usize, slot_size: usize) -> Result<Self, AllocError> {
458 if slot_size == 0 {
459 return Err(AllocError::InvalidArg);
460 }
461
462 let count = region_len / slot_size;
463 if count == 0 {
464 return Err(AllocError::EmptyRegion);
465 }
466
467 let mut free = SmallVec::with_capacity(count);
468 for i in 0..count {
469 free.push(base_addr + (i * slot_size) as u64);
470 }
471
472 Ok(Self {
473 base_addr,
474 slot_size,
475 count,
476 free,
477 allocated: FixedBitSet::with_capacity(count),
478 })
479 }
480
481 fn end(&self) -> u64 {
482 self.base_addr + (self.count * self.slot_size) as u64
483 }
484
485 fn contains(&self, addr: u64) -> bool {
486 (self.base_addr..self.end()).contains(&addr)
487 }
488
489 fn slot_of(&self, addr: u64) -> Result<usize, AllocError> {
491 if !self.contains(addr) {
492 return Err(AllocError::InvalidFree(addr, 0));
493 }
494
495 let off = addr - self.base_addr;
496 if !off.is_multiple_of(self.slot_size as u64) {
497 return Err(AllocError::InvalidFree(addr, 0));
498 }
499
500 Ok((off / self.slot_size as u64) as usize)
501 }
502
503 fn live_slot_of(&self, addr: u64) -> Result<usize, AllocError> {
505 let slot = self.slot_of(addr)?;
506 if !self.allocated.contains(slot) {
507 return Err(AllocError::InvalidFree(addr, 0));
508 }
509 Ok(slot)
510 }
511
512 fn alloc(&mut self, len: usize) -> Result<Allocation, AllocError> {
513 if len == 0 {
514 return Err(AllocError::InvalidArg);
515 }
516 if len > self.slot_size {
517 return Err(AllocError::OutOfMemory);
518 }
519
520 let addr = self.free.pop().ok_or(AllocError::NoSpace)?;
521 self.allocated
524 .insert(((addr - self.base_addr) / self.slot_size as u64) as usize);
525
526 Ok(Allocation {
527 addr,
528 len: self.slot_size,
529 })
530 }
531
532 fn dealloc_addr(&mut self, addr: u64) -> Result<(), AllocError> {
533 let slot = self.live_slot_of(addr)?;
534 self.allocated.set(slot, false);
535 self.free.push(addr);
536 Ok(())
537 }
538
539 fn allocation_len(&self, addr: u64) -> Result<usize, AllocError> {
540 self.live_slot_of(addr)?;
541 Ok(self.slot_size)
542 }
543
544 fn restore_allocated(&mut self, allocated: &[u64]) -> Result<(), AllocError> {
550 self.allocated.clear();
551 for &addr in allocated {
552 let slot = self.slot_of(addr)?;
553 if self.allocated.contains(slot) {
554 return Err(AllocError::InvalidFree(addr, self.slot_size));
555 }
556 self.allocated.insert(slot);
557 }
558 self.rebuild_free();
559 Ok(())
560 }
561
562 fn reset(&mut self) {
563 self.allocated.clear();
564 self.rebuild_free();
565 }
566
567 fn rebuild_free(&mut self) {
569 self.free.clear();
570 for i in 0..self.count {
571 if !self.allocated.contains(i) {
572 self.free.push(self.base_addr + (i * self.slot_size) as u64);
573 }
574 }
575 }
576
577 fn slot_addr(&self, index: usize) -> Option<u64> {
578 (index < self.count).then(|| self.base_addr + (index * self.slot_size) as u64)
579 }
580
581 fn num_free(&self) -> usize {
582 self.free.len()
583 }
584}
585
586#[derive(Clone)]
594pub struct RecyclePool {
595 inner: SendWrap<Rc<RefCell<RecycleList>>>,
596}
597
598impl RecyclePool {
599 pub fn new(base_addr: u64, region_len: usize, slot_size: usize) -> Result<Self, AllocError> {
604 if slot_size == 0 {
605 return Err(AllocError::InvalidArg);
606 }
607
608 let base = usize::try_from(base_addr).map_err(|_| AllocError::Overflow)?;
609 let region_end = base.checked_add(region_len).ok_or(AllocError::Overflow)?;
610 let aligned = align_up(base, slot_size)?;
611 let usable = region_end
612 .checked_sub(aligned)
613 .ok_or(AllocError::EmptyRegion)?;
614 let list = RecycleList::new(aligned as u64, usable, slot_size)?;
615
616 Ok(Self {
617 inner: SendWrap(Rc::new(RefCell::new(list))),
618 })
619 }
620
621 pub fn restore_allocated(&self, allocated: &[u64]) -> Result<(), AllocError> {
624 self.inner.borrow_mut().restore_allocated(allocated)
625 }
626
627 pub fn slot_addr(&self, index: usize) -> Option<u64> {
631 self.inner.borrow().slot_addr(index)
632 }
633
634 pub fn num_free(&self) -> usize {
636 self.inner.borrow().num_free()
637 }
638
639 pub fn dealloc_addr(&self, addr: u64) -> Result<(), AllocError> {
641 self.inner.borrow_mut().dealloc_addr(addr)
642 }
643
644 pub fn allocation_len(&self, addr: u64) -> Result<usize, AllocError> {
646 self.inner.borrow().allocation_len(addr)
647 }
648
649 pub fn base_addr(&self) -> u64 {
651 self.inner.borrow().base_addr
652 }
653
654 pub fn slot_size(&self) -> usize {
656 self.inner.borrow().slot_size
657 }
658
659 pub fn count(&self) -> usize {
661 self.inner.borrow().count
662 }
663}
664
665impl BufferProvider for RecyclePool {
666 fn max_alloc_len(&self) -> usize {
667 self.inner.borrow().slot_size
668 }
669
670 fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
671 self.inner.borrow_mut().alloc(len)
672 }
673
674 fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
675 self.inner.borrow_mut().dealloc_addr(addr)
676 }
677
678 fn reset(&self) {
679 self.inner.borrow_mut().reset()
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686
687 fn make_pool<const L: usize, const U: usize>(size: usize) -> BufferPool<L, U> {
688 let base = align_up(0x10000, L.max(U)).unwrap() as u64;
689 BufferPool::<L, U>::new(base, size).unwrap()
690 }
691
692 fn make_recycle_pool(slot_count: usize, slot_size: usize) -> RecyclePool {
693 let base = 0x80000u64;
694 RecyclePool::new(base, slot_count * slot_size, slot_size).unwrap()
695 }
696
697 #[test]
698 fn test_pool_new_success() {
699 let pool = BufferPool::<256, 4096>::new(0x10000, 1024 * 1024).unwrap();
700 assert!(pool.inner.borrow().lower.capacity() > 0);
701 assert!(pool.inner.borrow().upper.capacity() > 0);
702 }
703
704 #[test]
705 fn test_pool_alloc_small_to_lower() {
706 let pool = make_pool::<256, 4096>(1024 * 1024);
707 let alloc = pool.alloc(128).unwrap();
708
709 assert!(pool.inner.borrow().lower.contains(alloc.addr));
711 assert_eq!(alloc.len, 256);
712 }
713
714 #[test]
715 fn test_pool_alloc_large_to_upper() {
716 let pool = make_pool::<256, 4096>(1024 * 1024);
717 let alloc = pool.alloc(1500).unwrap();
718
719 assert!(pool.inner.borrow().upper.contains(alloc.addr));
721 assert_eq!(alloc.len, 4096);
722 }
723
724 #[test]
725 fn test_pool_alloc_fallback_to_upper() {
726 let pool = make_pool::<256, 4096>(1024 * 1024);
727
728 let mut allocations = Vec::new();
730 while pool.inner.borrow().lower.free_bytes() > 0 {
731 allocations.push(pool.inner.borrow_mut().lower.alloc(256).unwrap());
732 }
733
734 let alloc = pool.alloc(128).unwrap();
736 assert!(pool.inner.borrow().upper.contains(alloc.addr));
737 }
738
739 #[test]
740 fn test_pool_free_from_lower() {
741 let pool = make_pool::<256, 4096>(1024 * 1024);
742 let alloc = pool.alloc(128).unwrap();
743
744 let free_before = pool.inner.borrow().lower.free_bytes();
745 pool.dealloc(alloc.addr).unwrap();
746 assert_eq!(
747 pool.inner.borrow().lower.free_bytes(),
748 free_before + alloc.len
749 );
750 }
751
752 #[test]
753 fn test_pool_free_from_upper() {
754 let pool = make_pool::<256, 4096>(1024 * 1024);
755 let alloc = pool.alloc(1500).unwrap();
756
757 let free_before = pool.inner.borrow().upper.free_bytes();
758 pool.dealloc(alloc.addr).unwrap();
759 assert_eq!(
760 pool.inner.borrow().upper.free_bytes(),
761 free_before + alloc.len
762 );
763 }
764
765 #[test]
766 fn test_pool_stress_many_allocations() {
767 let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
768 let mut allocations = Vec::new();
769
770 for i in 0..100 {
772 let size = if i % 2 == 0 { 128 } else { 1500 };
773 allocations.push(pool.alloc(size).unwrap());
774 }
775
776 for i in (0..100).step_by(2) {
778 pool.dealloc(allocations[i].addr).unwrap();
779 }
780
781 for i in 0..50 {
783 let size = if i % 2 == 0 { 128 } else { 1500 };
784 let _alloc = pool.alloc(size).unwrap();
785 }
786 }
787
788 #[test]
789 fn test_pool_mixed_workload() {
790 let pool = make_pool::<256, 4096>(2 * 1024 * 1024);
791
792 let desc_buf = pool.alloc(64).unwrap(); let rx_buf1 = pool.alloc(1500).unwrap(); let rx_buf2 = pool.alloc(1500).unwrap(); let tx_buf = pool.alloc(4096).unwrap(); pool.dealloc(rx_buf1.addr).unwrap();
800 let rx_buf3 = pool.alloc(1500).unwrap();
801
802 assert_eq!(rx_buf3.addr, rx_buf1.addr);
804
805 pool.dealloc(desc_buf.addr).unwrap();
806 pool.dealloc(rx_buf2.addr).unwrap();
807 pool.dealloc(rx_buf3.addr).unwrap();
808 pool.dealloc(tx_buf.addr).unwrap();
809 }
810
811 #[test]
812 fn test_pool_zero_allocation_error() {
813 let pool = make_pool::<256, 4096>(1024 * 1024);
814 let result = pool.alloc(0);
815 assert!(matches!(result, Err(AllocError::InvalidArg)));
816 }
817
818 #[test]
819 fn test_pool_too_large_allocation() {
820 let pool = make_pool::<256, 4096>(1024 * 1024);
821 let result = pool.alloc(2 * 1024 * 1024); assert!(matches!(result, Err(AllocError::OutOfMemory)));
823 }
824
825 #[test]
826 fn test_align_up_helper() {
827 assert_eq!(align_up(0, 256).unwrap(), 0);
828 assert_eq!(align_up(1, 256).unwrap(), 256);
829 assert_eq!(align_up(256, 256).unwrap(), 256);
830 assert_eq!(align_up(257, 256).unwrap(), 512);
831 assert_eq!(align_up(511, 256).unwrap(), 512);
832 assert_eq!(align_up(512, 256).unwrap(), 512);
833 assert!(matches!(align_up(1, 0), Err(AllocError::InvalidArg)));
834 assert!(matches!(
835 align_up(usize::MAX, 256),
836 Err(AllocError::Overflow)
837 ));
838 }
839
840 #[test]
841 fn test_recycle_pool_alignment_subtracts_padding() {
842 let pool = RecyclePool::new(0x80001, 8192, 4096).unwrap();
843
844 assert_eq!(pool.base_addr(), 0x81000);
845 assert_eq!(pool.count(), 1);
846 }
847
848 #[test]
850 fn test_pool_boundary_allocation() {
851 let pool = make_pool::<256, 4096>(1024 * 1024);
852
853 let alloc = pool.alloc(256).unwrap();
855 assert!(pool.inner.borrow().lower.contains(alloc.addr));
856
857 let alloc2 = pool.alloc(257).unwrap();
859 assert!(pool.inner.borrow().upper.contains(alloc2.addr));
860 }
861
862 #[test]
863 fn test_buffer_pool_reset_returns_to_initial_state() {
864 let pool = make_pool::<256, 4096>(0x20000);
865
866 let a1 = pool.inner.borrow_mut().alloc(128).unwrap();
868 let a2 = pool.inner.borrow_mut().alloc(4096).unwrap();
869 assert!(a1.len > 0);
870 assert!(a2.len > 0);
871
872 pool.reset();
873
874 let inner = pool.inner.borrow();
875 assert_eq!(inner.lower.free_bytes(), inner.lower.capacity());
876 assert_eq!(inner.upper.free_bytes(), inner.upper.capacity());
877 }
878
879 #[test]
880 fn test_buffer_pool_reset_allows_reallocation() {
881 let pool = make_pool::<256, 4096>(0x20000);
882
883 let mut allocs = Vec::new();
885 for _ in 0..5 {
886 allocs.push(pool.inner.borrow_mut().alloc(256).unwrap());
887 }
888
889 pool.reset();
890
891 let a = pool.inner.borrow_mut().alloc(256).unwrap();
893 assert!(a.len > 0);
894 }
895
896 #[test]
897 fn test_pool_dealloc_addr_routes_to_correct_tier() {
898 let pool = make_pool::<256, 4096>(0x20000);
899 let lower = pool.alloc(128).unwrap();
900 let upper = pool.alloc(1024).unwrap();
901
902 assert_eq!(pool.allocation_len(lower.addr).unwrap(), 256);
903 assert_eq!(pool.allocation_len(upper.addr).unwrap(), 4096);
904
905 pool.dealloc_addr(lower.addr).unwrap();
906 pool.dealloc_addr(upper.addr).unwrap();
907 }
908
909 #[test]
910 fn test_buffer_pool_alloc_sg_uses_one_contiguous_run() {
911 let pool = make_pool::<256, 4096>(0x20000);
912 let sgs = pool.alloc_sg(4096 * 2 + 1).unwrap();
913
914 assert_eq!(sgs.len(), 1);
915 assert_eq!(sgs[0].len, 4096 * 3);
916
917 for sg in sgs {
918 pool.dealloc(sg.addr).unwrap();
919 }
920 }
921
922 #[test]
923 fn test_buffer_pool_alloc_sg_large_run() {
924 let pool = make_pool::<256, 4096>(0x20000);
925 let sgs = pool.alloc_sg(8192).unwrap();
926
927 assert_eq!(sgs.len(), 1);
928 assert_eq!(sgs[0].len, 8192);
929
930 for sg in sgs {
931 pool.dealloc(sg.addr).unwrap();
932 }
933 }
934
935 #[test]
936 fn test_recycle_pool_alloc_sg_splits() {
937 let pool = make_recycle_pool(8, 4096);
938 let sgs = pool.alloc_sg(4096 * 2 + 1).unwrap();
939
940 assert_eq!(sgs.len(), 3);
941 assert_eq!(sgs[0].len, 4096);
942 assert_eq!(sgs[1].len, 4096);
943 assert_eq!(sgs[2].len, 4096);
944
945 for sg in sgs {
946 pool.dealloc(sg.addr).unwrap();
947 }
948 }
949
950 #[test]
951 fn test_recycle_pool_restore_allocated_removes_from_free_list() {
952 let pool = make_recycle_pool(4, 4096);
953 assert_eq!(pool.num_free(), 4);
954
955 let addrs = [0x80000, 0x81000]; pool.restore_allocated(&addrs).unwrap();
957 assert_eq!(pool.num_free(), 2);
958
959 let a1 = pool.alloc(4096).unwrap();
961 let a2 = pool.alloc(4096).unwrap();
962 assert!(pool.alloc(4096).is_err());
963
964 let mut got = [a1.addr, a2.addr];
966 got.sort();
967 assert_eq!(got, [0x82000, 0x83000]);
968 }
969
970 #[test]
971 fn test_recycle_pool_restore_allocated_invalid_addr_returns_error() {
972 let pool = make_recycle_pool(4, 4096);
973 let result = pool.restore_allocated(&[0xDEAD]);
974 assert!(result.is_err());
975 }
976
977 #[test]
978 fn test_recycle_pool_restore_allocated_then_dealloc_roundtrip() {
979 let pool = make_recycle_pool(4, 4096);
980 let addr = 0x81000u64;
981
982 pool.restore_allocated(&[addr]).unwrap();
983 assert_eq!(pool.num_free(), 3);
984
985 pool.dealloc(addr).unwrap();
987 assert_eq!(pool.num_free(), 4);
988 }
989
990 #[test]
991 fn test_recycle_pool_restore_allocated_all_slots() {
992 let pool = make_recycle_pool(4, 4096);
993 let addrs: Vec<u64> = (0..4).map(|i| 0x80000 + i * 4096).collect();
994
995 pool.restore_allocated(&addrs).unwrap();
996 assert_eq!(pool.num_free(), 0);
997 assert!(pool.alloc(4096).is_err());
998 }
999
1000 #[test]
1001 fn test_recycle_pool_restore_allocated_empty_list_is_noop() {
1002 let pool = make_recycle_pool(4, 4096);
1003 pool.restore_allocated(&[]).unwrap();
1004 assert_eq!(pool.num_free(), 4);
1005 }
1006
1007 #[test]
1008 fn test_recycle_pool_restore_allocated_resets_first() {
1009 let pool = make_recycle_pool(4, 4096);
1010
1011 let _ = pool.alloc(4096).unwrap();
1013 let _ = pool.alloc(4096).unwrap();
1014 assert_eq!(pool.num_free(), 2);
1015
1016 pool.restore_allocated(&[0x80000]).unwrap();
1018 assert_eq!(pool.num_free(), 3);
1019 }
1020
1021 #[test]
1022 fn test_recycle_pool_dealloc_out_of_range() {
1023 let pool = make_recycle_pool(4, 4096);
1024 let _ = pool.alloc(4096).unwrap();
1025
1026 assert!(matches!(
1027 pool.dealloc(0xDEAD),
1028 Err(AllocError::InvalidFree(0xDEAD, 0))
1029 ));
1030 }
1031
1032 #[test]
1033 fn test_recycle_pool_dealloc_misaligned() {
1034 let pool = make_recycle_pool(4, 4096);
1035 let _ = pool.alloc(4096).unwrap();
1036
1037 assert!(matches!(
1038 pool.dealloc(0x80001),
1039 Err(AllocError::InvalidFree(0x80001, 0))
1040 ));
1041 }
1042
1043 #[test]
1044 fn test_recycle_pool_dealloc_double_free() {
1045 let pool = make_recycle_pool(4, 4096);
1046 let a = pool.alloc(4096).unwrap();
1047 pool.dealloc(a.addr).unwrap();
1048
1049 assert!(matches!(
1051 pool.dealloc(a.addr),
1052 Err(AllocError::InvalidFree(_, _))
1053 ));
1054 }
1055
1056 #[test]
1057 fn test_recycle_pool_alloc_sg_rolls_back_on_failure() {
1058 let pool = make_recycle_pool(2, 4096);
1059
1060 assert!(matches!(pool.alloc_sg(4096 * 3), Err(AllocError::NoSpace)));
1061 assert_eq!(pool.num_free(), 2);
1062
1063 let alloc = pool.alloc(4096).unwrap();
1064 assert_eq!(pool.num_free(), 1);
1065 pool.dealloc(alloc.addr).unwrap();
1066 }
1067
1068 #[test]
1069 fn test_recycle_pool_dealloc_addr_and_allocation_len() {
1070 let pool = make_recycle_pool(4, 4096);
1071 let alloc = pool.alloc(4096).unwrap();
1072
1073 assert_eq!(pool.allocation_len(alloc.addr).unwrap(), 4096);
1074 pool.dealloc_addr(alloc.addr).unwrap();
1075 assert!(matches!(
1076 pool.allocation_len(alloc.addr),
1077 Err(AllocError::InvalidFree(_, 0))
1078 ));
1079 }
1080
1081 #[test]
1082 fn test_recycle_pool_random_order_dealloc() {
1083 let pool = make_recycle_pool(8, 4096);
1084
1085 let mut allocs: Vec<Allocation> = (0..8).map(|_| pool.alloc(4096).unwrap()).collect();
1086 assert_eq!(pool.num_free(), 0);
1087
1088 allocs.reverse();
1090 for a in &allocs {
1091 pool.dealloc(a.addr).unwrap();
1092 }
1093 assert_eq!(pool.num_free(), 8);
1094
1095 let reallocs: Vec<Allocation> = (0..8).map(|_| pool.alloc(4096).unwrap()).collect();
1097 assert_eq!(pool.num_free(), 0);
1098
1099 let mut addrs: Vec<u64> = reallocs.iter().map(|a| a.addr).collect();
1101 addrs.sort();
1102 addrs.dedup();
1103 assert_eq!(addrs.len(), 8);
1104 }
1105
1106 #[test]
1107 fn test_recycle_pool_interleaved_alloc_dealloc_order() {
1108 let pool = make_recycle_pool(4, 4096);
1109
1110 let a0 = pool.alloc(4096).unwrap();
1111 let a1 = pool.alloc(4096).unwrap();
1112 let a2 = pool.alloc(4096).unwrap();
1113 let a3 = pool.alloc(4096).unwrap();
1114 assert_eq!(pool.num_free(), 0);
1115
1116 pool.dealloc(a2.addr).unwrap();
1118 pool.dealloc(a0.addr).unwrap();
1119 assert_eq!(pool.num_free(), 2);
1120
1121 let b0 = pool.alloc(4096).unwrap();
1123 assert_eq!(b0.addr, a0.addr);
1124 let b1 = pool.alloc(4096).unwrap();
1125 assert_eq!(b1.addr, a2.addr);
1126
1127 pool.dealloc(a1.addr).unwrap();
1129 pool.dealloc(b0.addr).unwrap();
1130 pool.dealloc(b1.addr).unwrap();
1131 pool.dealloc(a3.addr).unwrap();
1132 assert_eq!(pool.num_free(), 4);
1133
1134 let mut final_addrs: Vec<u64> = (0..4).map(|_| pool.alloc(4096).unwrap().addr).collect();
1136 final_addrs.sort();
1137 let expected: Vec<u64> = (0..4).map(|i| 0x80000 + i * 4096).collect();
1138 assert_eq!(final_addrs, expected);
1139 }
1140
1141 #[test]
1142 fn test_recycle_pool_dealloc_order_independent_of_alloc_order() {
1143 let pool = make_recycle_pool(6, 256);
1144
1145 let allocs: Vec<Allocation> = (0..6).map(|_| pool.alloc(256).unwrap()).collect();
1147
1148 let order = [4, 1, 5, 0, 3, 2];
1150 for &i in &order {
1151 pool.dealloc(allocs[i].addr).unwrap();
1152 }
1153 assert_eq!(pool.num_free(), 6);
1154
1155 let mut realloc_addrs: Vec<u64> = (0..6).map(|_| pool.alloc(256).unwrap().addr).collect();
1157 realloc_addrs.sort();
1158
1159 let mut orig_addrs: Vec<u64> = allocs.iter().map(|a| a.addr).collect();
1160 orig_addrs.sort();
1161
1162 assert_eq!(realloc_addrs, orig_addrs);
1163 }
1164}
1165
1166#[cfg(test)]
1167mod fuzz {
1168 use quickcheck::{Arbitrary, Gen, QuickCheck};
1169
1170 use super::*;
1171
1172 const MAX_OPS: usize = 10;
1173 const MAX_ALLOC_SIZE: usize = 8192;
1174
1175 #[derive(Clone, Debug)]
1176 enum Op {
1177 Alloc(usize),
1178 AllocSg(usize),
1179 Dealloc(usize),
1180 }
1181
1182 impl Arbitrary for Op {
1183 fn arbitrary(g: &mut Gen) -> Self {
1184 match u8::arbitrary(g) % 3 {
1185 0 => Op::Alloc(usize::arbitrary(g) % MAX_ALLOC_SIZE + 1),
1186 1 => Op::AllocSg(usize::arbitrary(g) % MAX_ALLOC_SIZE + 1),
1187 2 => Op::Dealloc(usize::arbitrary(g)),
1188 _ => unreachable!(),
1189 }
1190 }
1191 }
1192
1193 #[derive(Clone, Debug)]
1194 struct Scenario {
1195 pool_size: usize,
1196 ops: Vec<Op>,
1197 }
1198
1199 impl Arbitrary for Scenario {
1200 fn arbitrary(g: &mut Gen) -> Self {
1201 let pool_size = (usize::arbitrary(g) % (4 * 1024 * 1024)) + (1024 * 1024);
1202 let num_ops = usize::arbitrary(g) % MAX_OPS + 1;
1203 let ops = (0..num_ops).map(|_| Op::arbitrary(g)).collect();
1204
1205 Scenario { pool_size, ops }
1206 }
1207 }
1208
1209 fn run_scenario(s: Scenario) -> bool {
1210 let base = align_up(0x10000, 4096).unwrap() as u64;
1211 let pool = match BufferPool::<256, 4096>::new(base, s.pool_size) {
1212 Ok(p) => p,
1213 Err(_) => return true,
1214 };
1215
1216 let mut allocations: Vec<Allocation> = Vec::new();
1217
1218 for op in &s.ops {
1219 match op {
1220 Op::Alloc(size) => match pool.alloc(*size) {
1221 Ok(alloc) => {
1222 assert!(alloc.len >= *size);
1223 allocations.push(alloc);
1224 }
1225 Err(AllocError::NoSpace | AllocError::OutOfMemory) => {}
1226 Err(_) => {
1227 return false;
1228 }
1229 },
1230 Op::AllocSg(size) => match pool.alloc_sg(*size) {
1231 Ok(sgs) => {
1232 let total: usize = sgs.iter().map(|sg| sg.len).sum();
1233 assert!(total >= *size);
1234 allocations.extend(sgs);
1235 }
1236 Err(AllocError::NoSpace | AllocError::OutOfMemory) => {}
1237 Err(_) => {
1238 return false;
1239 }
1240 },
1241 Op::Dealloc(idx) => {
1242 if allocations.is_empty() {
1243 continue;
1244 }
1245
1246 let idx = idx % allocations.len();
1247 let alloc = allocations.swap_remove(idx);
1248
1249 match pool.dealloc(alloc.addr) {
1250 Ok(_) => {}
1251 Err(_) => return false,
1252 }
1253 }
1254 }
1255
1256 if check_pool_invariants(&pool, &allocations).is_err() {
1257 return false;
1258 }
1259 }
1260
1261 for alloc in &allocations {
1263 if pool.dealloc(alloc.addr).is_err() {
1264 return false;
1265 }
1266 }
1267
1268 check_pool_invariants(&pool, &allocations).is_ok()
1269 }
1270
1271 fn check_slab_invariants<const N: usize>(slab: &Slab<N>) -> Result<(), &'static str> {
1272 let used = slab.used_slots.count_ones(..);
1273 let free = slab.used_slots.count_zeroes(..);
1274 if used + free != slab.used_slots.len() {
1275 return Err("used + free != total slots");
1276 }
1277
1278 let expected_free = free * N;
1279 if slab.free_bytes() != expected_free {
1280 return Err("free_bytes doesn't match bitmap");
1281 }
1282
1283 if let Some(alloc) = slab.last_free_run {
1284 if alloc.len == 0 || alloc.len % N != 0 {
1285 return Err("last_free_run has invalid length");
1286 }
1287 if !slab.contains(alloc.addr) {
1288 return Err("last_free_run addr outside range");
1289 }
1290 }
1291
1292 Ok(())
1293 }
1294
1295 fn check_pool_invariants<const L: usize, const U: usize>(
1296 pool: &BufferPool<L, U>,
1297 allocations: &[Allocation],
1298 ) -> Result<(), &'static str> {
1299 check_slab_invariants(&pool.inner.borrow().lower)?;
1300 check_slab_invariants(&pool.inner.borrow().upper)?;
1301
1302 if pool.inner.borrow().lower.range().end > pool.inner.borrow().upper.range().start {
1303 return Err("lower and upper ranges overlap");
1304 }
1305
1306 let mut seen = std::collections::HashSet::new();
1307
1308 for alloc in allocations {
1309 if !pool.inner.borrow().lower.contains(alloc.addr)
1310 && !pool.inner.borrow().upper.contains(alloc.addr)
1311 {
1312 return Err("allocation address outside pool ranges");
1313 }
1314
1315 if alloc.len % L != 0 && alloc.len % U != 0 {
1316 return Err("allocation length not aligned to any tier");
1317 }
1318
1319 if !seen.insert(alloc.addr) {
1320 return Err("duplicate allocation address in tracking");
1321 }
1322 }
1323
1324 Ok(())
1325 }
1326
1327 #[test]
1328 fn prop_allocator_invariants() {
1329 #[cfg(miri)]
1330 let tests = 10;
1331 #[cfg(not(miri))]
1332 let tests = 1000;
1333
1334 QuickCheck::new()
1335 .tests(tests)
1336 .quickcheck(run_scenario as fn(Scenario) -> bool);
1337 }
1338}