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