Skip to main content

fbl/
slab_allocator.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//! Slab-style allocator for Fuchsia/Zircon objects.
6//!
7//! `SlabAllocator` is a utility class implementing a slab-style memory allocator
8//! for a given object type `T`. It can dispense:
9//! - **Managed pointer types** (`UniquePtr<T>`, `RefPtr<T>`): Automatically returned to
10//!   the allocator when they are dropped.
11//! - **Unmanaged pointer types** (`*mut T`): Must be manually returned to the allocator.
12//!
13//! # Allocator Flavors
14//!
15//! In C++, this allocator supported three flavors: `INSTANCED`, `STATIC`, and `MANUAL_DELETE`.
16//! In Rust, these flavors are mapped as follows:
17//!
18//! 1. **Instanced Allocators**:
19//!    - Multiple instances of the allocator can coexist, each with independent quotas.
20//!    - Objects carry a pointer back to their originating allocator to find their way home on drop.
21//!    - Required trait: `InstancedSlabAllocated`. Helper macro: `impl_instanced_slab_allocatable!`.
22//!    - Allocated via `new_unique` and `new_ref`.
23//!
24//! 2. **Static Allocators**:
25//!    - A single process-wide global allocator for a given type.
26//!    - Objects carry no storage overhead and locate their allocator via trait definitions.
27//!    - Required trait: `StaticSlabAllocated`. Helper macro: `impl_static_slab_allocatable!`.
28//!    - Allocated via standard constructors (e.g. `UniquePtr::try_new`, `make_ref_counted!`).
29//!
30//! 3. **Manual Delete (Unmanaged) Allocations**:
31//!    - Objects pay no storage overhead for tracking their allocator origin.
32//!    - Memory is allocated as raw pointers and must be explicitly returned using `delete` or
33//!      `return_to_free_list`.
34//!    - Allocated via `alloc_raw`.
35//!
36//! # Memory Limits and Allocation Behavior
37//!
38//! Slabs of size `SLAB_SIZE` (default 16KB) are allocated from the heap using `kalloc::alloc`.
39//! These slabs are carved into properly aligned regions just large enough to hold an instance
40//! of `T` (or a free-list link node).
41//!
42//! Allocation operations:
43//! 1. Reuse nodes from the internal free list.
44//! 2. If the free list is empty, carve out memory from the currently active slab.
45//! 3. If the active slab is full and `slab_count < max_slabs`, allocate a new slab.
46//! 4. If all limits are reached, return `Err(AllocError)`.
47//!
48//! Allocation is O(1) in the steady state, and O(kalloc::alloc) when a new slab is needed.
49//! Setting the slab limit to 1 and passing `alloc_initial = true` during `try_new` ensures
50//! O(1) performance for all allocations.
51//!
52//! # Thread Safety
53//!
54//! The allocator uses a generic lock parameter `L` (implementing `RawLock`) to synchronize access,
55//! which defaults to `RawMutex`.
56
57use crate::recyclable::Recyclable;
58use crate::ref_counted::HasRefCount;
59use crate::ref_ptr::RefPtr;
60use crate::singly_linked_list::{SinglyLinkedList, SinglyLinkedListNode};
61use crate::unique_ptr::UniquePtr;
62use core::alloc::Layout;
63use core::cmp::max;
64use core::marker::PhantomData;
65use core::mem::{align_of, size_of};
66use core::pin::Pin;
67use core::ptr::{NonNull, drop_in_place, write};
68use kalloc::AllocError;
69pub use ksync::RawLock;
70use ksync::{KCell, KMutex, RawMutex, guarded, lock};
71use pin_init::{pin_data, pin_init, pinned_drop};
72
73mod sealed {
74    pub trait Sealed {}
75    impl Sealed for super::RawMutex {}
76}
77
78/// Helper trait to restrict SlabAllocator lock to RawMutex.
79/// TODO(https://fxbug.dev/541903019): Support generic mutex types.
80pub trait IsRawMutex: sealed::Sealed {}
81impl IsRawMutex for RawMutex {}
82
83/// The default slab size in bytes (16KB).
84pub const DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE: usize = 16384;
85
86#[derive(crate::SinglyLinkedListContainable)]
87#[repr(C)]
88struct SlabHeader {
89    #[sll_node]
90    node: SinglyLinkedListNode<SlabHeader>,
91    bytes_used: usize,
92}
93
94impl SlabHeader {
95    fn new(bytes_used: usize) -> Self {
96        assert!(
97            bytes_used >= size_of::<Self>(),
98            "bytes_used ({}) must be at least as large as SlabHeader ({})",
99            bytes_used,
100            size_of::<Self>()
101        );
102        Self { node: SinglyLinkedListNode::new(), bytes_used }
103    }
104
105    fn allocate(&mut self, alloc_size: usize, slab_size: usize) -> Option<NonNull<u8>> {
106        if self.bytes_used + alloc_size > slab_size {
107            return None;
108        }
109        let self_ptr = self as *mut SlabHeader as *mut u8;
110        // SAFETY: `self.bytes_used` is guaranteed to be within `slab_size`.
111        let ret = unsafe { self_ptr.add(self.bytes_used) };
112        self.bytes_used += alloc_size;
113        // SAFETY: `self_ptr` is derived from `&mut self` which is non-null.
114        // `self.bytes_used` is positive, so `ret` is also non-null.
115        Some(unsafe { NonNull::new_unchecked(ret) })
116    }
117}
118
119#[derive(crate::SinglyLinkedListContainable)]
120#[repr(C)]
121struct FreeListEntry {
122    #[sll_node]
123    node: SinglyLinkedListNode<FreeListEntry>,
124}
125
126/// A slab-style allocator for a given object type `T`.
127///
128/// # Generics
129///
130/// * `T`: The type of object allocated by this allocator.
131/// * `L`: The synchronization primitive (defaults to `RawMutex`).
132/// * `SLAB_SIZE`: The size of each memory slab in bytes (defaults to 16KB).
133/// * `TRACK_OBJECT_COUNT`: Enable allocation tracking (e.g. `obj_count`, `max_obj_count`).
134///
135/// # Examples
136///
137/// ## Instanced Allocation with `UniquePtr`
138///
139/// ```rust
140/// use fbl::{
141///     SlabAllocator, RawMutex, impl_instanced_slab_allocatable, UniquePtr,
142///     DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
143/// };
144/// use core::cell::Cell;
145///
146/// use fbl::SlabOrigin;
147/// struct MyObject {
148///     value: i32,
149///     // Required field for tracking origin
150///     slab_origin: SlabOrigin<MyObject, RawMutex, DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE>,
151/// }
152///
153/// impl_instanced_slab_allocatable!(MyObject, RawMutex, DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE);
154///
155/// fn example() {
156///     let allocator = SlabAllocator::<
157///         MyObject, RawMutex, DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE
158///     >::try_new(4, true, RawMutex::INIT).unwrap();
159///     let mut list = std::collections::VecDeque::new();
160///
161///     for i in 0..10 {
162///         let obj = allocator.new_unique(MyObject {
163///             value: i,
164///             slab_origin: SlabOrigin::new(),
165///         }).unwrap();
166///         list.push_front(obj);
167///     }
168///     // Memory is automatically returned to the allocator when elements are dropped.
169/// }
170/// ```
171///
172/// ## Static Allocation with `UniquePtr`
173///
174/// ```rust
175/// use fbl::{
176///     SlabAllocator, RawMutex, impl_static_slab_allocatable, UniquePtr,
177///     DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE
178/// };
179///
180/// struct MyObject {
181///     value: i32,
182/// }
183///
184/// static MY_ALLOCATOR: SlabAllocator<MyObject, RawMutex, DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE> =
185///     SlabAllocator::const_new(64, RawMutex::INIT);
186///
187/// impl_static_slab_allocatable!(
188///     MyObject, RawMutex, DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE, MY_ALLOCATOR);
189///
190/// fn example() {
191///     let obj = UniquePtr::try_new(MyObject { value: 42 }).unwrap();
192///     // obj will automatically recycle back to MY_ALLOCATOR.
193/// }
194/// ```
195#[guarded]
196#[pin_data(PinnedDrop)]
197pub struct SlabAllocator<
198    T,
199    L: RawLock + IsRawMutex = RawMutex,
200    const SLAB_SIZE: usize = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
201    const TRACK_OBJECT_COUNT: bool = false,
202> {
203    #[mutex]
204    // TODO(https://fxbug.dev/541903019): Currently generic mutex types are not supported and so
205    // this is forced to be RawMutex. The IsRawMutex trait bound above ensures that no other type is
206    // attempted to be used.
207    mu: KMutex<RawMutex>,
208
209    #[guarded_by(mu)]
210    free_list: SinglyLinkedList<NonNull<FreeListEntry>>,
211    #[guarded_by(mu)]
212    slab_list: SinglyLinkedList<NonNull<SlabHeader>>,
213    #[guarded_by(mu)]
214    slab_count: usize,
215    // Note: `obj_count` and `max_obj_count` are not used if `TRACK_OBJECT_COUNT` is false,
216    // but they are always declared for simplicity of the struct definition.
217    #[guarded_by(mu)]
218    obj_count: usize,
219    #[guarded_by(mu)]
220    max_obj_count: usize,
221
222    max_slabs: usize,
223    _phantom: PhantomData<(T, L)>,
224}
225
226unsafe impl<
227    T,
228    L: RawLock + IsRawMutex + Sync,
229    const SLAB_SIZE: usize,
230    const TRACK_OBJECT_COUNT: bool,
231> Sync for SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
232{
233}
234unsafe impl<
235    T,
236    L: RawLock + IsRawMutex + Send,
237    const SLAB_SIZE: usize,
238    const TRACK_OBJECT_COUNT: bool,
239> Send for SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
240{
241}
242
243/// A helper type to store the originating slab allocator for instanced allocations.
244///
245/// This type wraps `Option<NonNull<SlabAllocator<...>>>` and provides safe `Send` and `Sync`
246/// implementations, allowing the containing object to be shared across threads.
247pub struct SlabOrigin<
248    T,
249    L: RawLock + IsRawMutex = RawMutex,
250    const SLAB_SIZE: usize = DEFAULT_SLAB_ALLOCATOR_SLAB_SIZE,
251    const TRACK_OBJECT_COUNT: bool = false,
252> {
253    origin: Option<NonNull<SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>>>,
254}
255
256impl<T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>
257    SlabOrigin<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
258{
259    /// Creates a new, uninitialized `SlabOrigin`.
260    pub const fn new() -> Self {
261        Self { origin: None }
262    }
263
264    /// Sets the origin allocator.
265    pub fn set(&mut self, origin: NonNull<SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>>) {
266        self.origin = Some(origin);
267    }
268
269    /// Gets the origin allocator.
270    pub fn get(&self) -> Option<NonNull<SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>>> {
271        self.origin
272    }
273}
274
275impl<T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool> Default
276    for SlabOrigin<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
277{
278    fn default() -> Self {
279        Self::new()
280    }
281}
282
283// SAFETY: SlabOrigin only holds a pointer to SlabAllocator which is Send/Sync if L is Send/Sync.
284unsafe impl<
285    T,
286    L: RawLock + IsRawMutex + Sync,
287    const SLAB_SIZE: usize,
288    const TRACK_OBJECT_COUNT: bool,
289> Sync for SlabOrigin<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
290{
291}
292unsafe impl<
293    T,
294    L: RawLock + IsRawMutex + Send,
295    const SLAB_SIZE: usize,
296    const TRACK_OBJECT_COUNT: bool,
297> Send for SlabOrigin<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
298{
299}
300
301/// Trait implemented by types that can be allocated from an instanced slab allocator.
302///
303/// Implementing this trait allows `UniquePtr` and `RefPtr` to automatically return
304/// their memory to the originating allocator on drop.
305pub trait InstancedSlabAllocated<
306    L: RawLock + IsRawMutex,
307    const SLAB_SIZE: usize,
308    const TRACK_OBJECT_COUNT: bool = false,
309>: Sized
310{
311    /// Returns the address of the originating slab allocator.
312    fn slab_origin(&self)
313    -> Option<NonNull<SlabAllocator<Self, L, SLAB_SIZE, TRACK_OBJECT_COUNT>>>;
314    /// Sets the originating slab allocator.
315    fn set_slab_origin(
316        &mut self,
317        origin: NonNull<SlabAllocator<Self, L, SLAB_SIZE, TRACK_OBJECT_COUNT>>,
318    );
319}
320
321/// Trait implemented by types that can be allocated from a static slab allocator.
322///
323/// Implementing this trait allows `UniquePtr` and `RefPtr` to automatically return
324/// their memory to the global static allocator on drop.
325pub trait StaticSlabAllocated<
326    L: RawLock + IsRawMutex,
327    const SLAB_SIZE: usize,
328    const TRACK_OBJECT_COUNT: bool = false,
329>: Sized
330{
331    /// Returns a static reference to the global slab allocator.
332    fn get_allocator() -> &'static SlabAllocator<Self, L, SLAB_SIZE, TRACK_OBJECT_COUNT>;
333}
334
335/// Macro to implement `InstancedSlabAllocated` and `Recyclable` for a struct.
336///
337/// This assumes the struct contains a field `slab_origin` of type
338/// `SlabOrigin<Self, Lock, SLAB_SIZE, TRACK_OBJECT_COUNT>`.
339#[macro_export]
340macro_rules! impl_instanced_slab_allocatable {
341    ($ty:ty, $lock:ty, $slab_size:expr) => {
342        $crate::impl_instanced_slab_allocatable!($ty, $lock, $slab_size, false);
343    };
344    ($ty:ty, $lock:ty, $slab_size:expr, $track_obj_count:expr) => {
345        impl $crate::InstancedSlabAllocated<$lock, $slab_size, $track_obj_count> for $ty {
346            fn slab_origin(
347                &self,
348            ) -> Option<
349                ::core::ptr::NonNull<
350                    $crate::SlabAllocator<Self, $lock, $slab_size, $track_obj_count>,
351                >,
352            > {
353                self.slab_origin.get()
354            }
355
356            fn set_slab_origin(
357                &mut self,
358                origin: ::core::ptr::NonNull<
359                    $crate::SlabAllocator<Self, $lock, $slab_size, $track_obj_count>,
360                >,
361            ) {
362                self.slab_origin.set(origin);
363            }
364        }
365
366        unsafe impl $crate::Recyclable for $ty {
367            unsafe fn recycle(ptr: ::core::ptr::NonNull<Self>) {
368                // SAFETY: The pointer is guaranteed to be non-null and to point to a valid,
369                // initialized instance of `Self` allocated from this slab allocator.
370                let origin = unsafe { ptr.as_ref().slab_origin() };
371                if let Some(origin) = origin {
372                    // SAFETY: The allocator instance must outlive the allocated objects.
373                    // Dropping in-place before returning the raw memory prevents use-after-free
374                    // and ensures proper cleanup of fields.
375                    unsafe {
376                        ::core::ptr::drop_in_place(ptr.as_ptr());
377                        origin.as_ref().return_to_free_list(ptr);
378                    }
379                }
380            }
381        }
382    };
383}
384
385/// Macro to implement `StaticSlabAllocated` and `Recyclable` for a struct.
386#[macro_export]
387macro_rules! impl_static_slab_allocatable {
388    ($ty:ty, $lock:ty, $slab_size:expr, $allocator:expr) => {
389        $crate::impl_static_slab_allocatable!($ty, $lock, $slab_size, $allocator, false);
390    };
391    ($ty:ty, $lock:ty, $slab_size:expr, $allocator:expr, $track_obj_count:expr) => {
392        impl $crate::StaticSlabAllocated<$lock, $slab_size, $track_obj_count> for $ty {
393            fn get_allocator()
394            -> &'static $crate::SlabAllocator<Self, $lock, $slab_size, $track_obj_count> {
395                &$allocator
396            }
397        }
398
399        unsafe impl $crate::Recyclable for $ty {
400            fn allocate(value: Self) -> Result<::core::ptr::NonNull<Self>, ::kalloc::AllocError> {
401                let allocator = <Self as $crate::StaticSlabAllocated<
402                    $lock,
403                    $slab_size,
404                    $track_obj_count,
405                >>::get_allocator();
406                let ptr = allocator.alloc_raw()?;
407                // SAFETY: `ptr` points to valid, uninitialized memory allocated from the slab.
408                // Writing to it initializes the memory.
409                unsafe {
410                    ::core::ptr::write(ptr.as_ptr(), value);
411                }
412                Ok(ptr)
413            }
414
415            unsafe fn recycle(ptr: ::core::ptr::NonNull<Self>) {
416                let allocator = <Self as $crate::StaticSlabAllocated<
417                    $lock,
418                    $slab_size,
419                    $track_obj_count,
420                >>::get_allocator();
421                // SAFETY: The global static allocator is guaranteed to live forever (static
422                // lifetime).  Dropping the object in-place before returning memory prevents
423                // use-after-free.
424                unsafe {
425                    ::core::ptr::drop_in_place(ptr.as_ptr());
426                    allocator.return_to_free_list(ptr);
427                }
428            }
429        }
430    };
431}
432
433impl<T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>
434    SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
435{
436    pub const ALLOC_ALIGN: usize = if align_of::<FreeListEntry>() > align_of::<T>() {
437        align_of::<FreeListEntry>()
438    } else {
439        align_of::<T>()
440    };
441    pub const ALLOC_SIZE: usize = {
442        let raw_size = if size_of::<FreeListEntry>() > size_of::<T>() {
443            size_of::<FreeListEntry>()
444        } else {
445            size_of::<T>()
446        };
447        match Layout::from_size_align(raw_size, Self::ALLOC_ALIGN) {
448            Ok(layout) => layout.pad_to_align().size(),
449            Err(_) => panic!("invalid layout"),
450        }
451    };
452    pub const STORAGE_OFFSET: usize =
453        match Layout::from_size_align(size_of::<SlabHeader>(), Self::ALLOC_ALIGN) {
454            Ok(layout) => layout.pad_to_align().size(),
455            Err(_) => panic!("invalid layout"),
456        };
457
458    /// The number of objects of type `T` that can fit in a single slab.
459    pub const ALLOCS_PER_SLAB: usize = (SLAB_SIZE - Self::STORAGE_OFFSET) / Self::ALLOC_SIZE;
460
461    const _ASSERT: () = {
462        assert!(
463            Self::ALLOC_SIZE % Self::ALLOC_ALIGN == 0,
464            "Allocation size must be a multiple of alignment"
465        );
466        assert!(
467            size_of::<SlabHeader>() < SLAB_SIZE,
468            "SLAB_SIZE too small to hold slab bookkeeping"
469        );
470        assert!(
471            SLAB_SIZE >= Self::STORAGE_OFFSET + Self::ALLOC_SIZE,
472            "SLAB_SIZE too small to hold even 1 allocation"
473        );
474    };
475
476    /// Pre-allocates the first slab.
477    ///
478    /// This can be used to guarantee O(1) execution times for all future allocations
479    /// if `max_slabs` is at least 1.
480    pub fn preallocate(&self) -> Result<(), AllocError> {
481        let ptr = self.alloc_raw()?;
482        // SAFETY: `ptr` was just allocated and is valid.
483        unsafe {
484            self.return_to_free_list(ptr);
485        }
486        if TRACK_OBJECT_COUNT {
487            lock!(let guard = self.lock_mu());
488            let fields = guard.fields_mut();
489            *fields.obj_count = 0;
490            *fields.max_obj_count = 0;
491        }
492        Ok(())
493    }
494
495    /// Creates a new `SlabAllocator` that can be initialized in const contexts.
496    ///
497    /// Note: Slabs are not pre-allocated during const-construction.
498    pub const fn const_new(max_slabs: usize, lock: RawMutex) -> Self {
499        let _ = Self::_ASSERT;
500        Self {
501            mu: KMutex::new(lock),
502            free_list: KCell::new(SinglyLinkedList::new()),
503            slab_list: KCell::new(SinglyLinkedList::new()),
504            slab_count: KCell::new(0),
505            obj_count: KCell::new(0),
506            max_obj_count: KCell::new(0),
507            max_slabs,
508            _phantom: PhantomData,
509        }
510    }
511
512    /// Creates a new `PinInit` initializer for `SlabAllocator` for dynamic initialization.
513    pub fn init(max_slabs: usize) -> impl pin_init::PinInit<Self, core::convert::Infallible> {
514        pin_init!(Self {
515            mu <- KMutex::init(),
516            free_list: SinglyLinkedList::new().into(),
517            slab_list: SinglyLinkedList::new().into(),
518            slab_count: 0.into(),
519            obj_count: 0.into(),
520            max_obj_count: 0.into(),
521            max_slabs,
522            _phantom: PhantomData,
523        })
524    }
525
526    #[inline(always)]
527    fn slab_layout() -> Layout {
528        let alloc_align = Self::ALLOC_ALIGN;
529        let slab_align = max(align_of::<SlabHeader>(), alloc_align);
530        // SAFETY: SLAB_SIZE is non-zero (checked by static assert), and slab_align
531        // is a valid power of 2 (align_of is always a power of 2, and max of two powers
532        // of 2 is also a power of 2).
533        unsafe { Layout::from_size_align_unchecked(SLAB_SIZE, slab_align) }
534    }
535
536    fn alloc_slab() -> Result<NonNull<SlabHeader>, AllocError> {
537        let layout = Self::slab_layout();
538        // SAFETY: `layout` is guaranteed to have a non-zero size (`SLAB_SIZE`) and valid alignment.
539        let slab_mem = unsafe { kalloc::alloc(layout).ok_or(AllocError)? };
540        let slab_ptr = slab_mem.cast::<SlabHeader>();
541
542        // SAFETY: `slab_ptr` is validly allocated with appropriate size and alignment.
543        unsafe {
544            write(slab_ptr.as_ptr(), SlabHeader::new(Self::STORAGE_OFFSET));
545        }
546        Ok(slab_ptr)
547    }
548
549    /// # Safety
550    ///
551    /// `slab_ptr` must have been allocated by this allocator and not yet deallocated.
552    unsafe fn dealloc_slab(slab_ptr: NonNull<SlabHeader>) {
553        let layout = Self::slab_layout();
554        // SAFETY: The caller guarantees `slab_ptr` is valid.
555        unsafe {
556            drop_in_place(slab_ptr.as_ptr());
557            kalloc::dealloc(slab_ptr.cast::<u8>().as_ptr(), layout);
558        }
559    }
560
561    /// Allocates raw, uninitialized memory for a single object of type `T`.
562    pub fn alloc_raw(&self) -> Result<NonNull<T>, AllocError> {
563        lock!(let guard = self.lock_mu());
564        let mut fields = guard.fields_mut();
565
566        // 1. Try free list
567        if let Some(entry_ptr) = fields.free_list.pop_front() {
568            fields.record_allocation();
569            return Ok(entry_ptr.cast::<T>());
570        }
571
572        // 2. Try active slab
573        if let Some(active_slab) = fields.slab_list.front_mut() {
574            if let Some(mem) = active_slab.allocate(Self::ALLOC_SIZE, SLAB_SIZE) {
575                let ptr = mem.cast::<T>();
576                fields.record_allocation();
577                return Ok(ptr);
578            }
579        }
580
581        // 3. Try allocate new slab
582        if *fields.slab_count < self.max_slabs {
583            let mut slab_ptr = Self::alloc_slab()?;
584            *fields.slab_count += 1;
585            // SAFETY: `slab_ptr` is newly initialized.
586            unsafe {
587                fields.slab_list.push_front_raw(slab_ptr);
588            }
589
590            // SAFETY: Allocate from this new slab.
591            let active_slab = unsafe { slab_ptr.as_mut() };
592            let mem = active_slab.allocate(Self::ALLOC_SIZE, SLAB_SIZE).unwrap();
593            let ptr = mem.cast::<T>();
594            fields.record_allocation();
595            return Ok(ptr);
596        }
597
598        Err(AllocError)
599    }
600
601    /// Returns raw memory to the free list.
602    ///
603    /// # Safety
604    ///
605    /// `ptr` must have been previously allocated from this allocator, and must not have
606    /// been returned already.
607    pub unsafe fn return_to_free_list(&self, ptr: NonNull<T>) {
608        let entry_ptr = ptr.cast::<FreeListEntry>();
609        // SAFETY: The memory block is large and aligned enough to hold a `FreeListEntry`.
610        unsafe {
611            write(entry_ptr.as_ptr(), FreeListEntry { node: SinglyLinkedListNode::new() });
612        }
613
614        lock!(let guard = self.lock_mu());
615        let mut fields = guard.fields_mut();
616        // SAFETY: `entry_ptr` is a valid NonNull pointer.
617        unsafe {
618            fields.free_list.push_front_raw(entry_ptr);
619        }
620        fields.record_deallocation();
621    }
622
623    /// Constructs an object in a `UniquePtr` using memory allocated from this instanced allocator.
624    pub fn new_unique(&self, value: T) -> Result<UniquePtr<T>, AllocError>
625    where
626        T: Recyclable + InstancedSlabAllocated<L, SLAB_SIZE, TRACK_OBJECT_COUNT>,
627    {
628        let ptr = self.alloc_raw()?;
629        // SAFETY: `ptr` points to valid, uninitialized memory suitable for `T`.
630        unsafe {
631            write(ptr.as_ptr(), value);
632            (&mut *ptr.as_ptr()).set_slab_origin(NonNull::from(self));
633            Ok(UniquePtr::from_raw(ptr.as_ptr()))
634        }
635    }
636
637    /// Constructs a ref-counted object in a `RefPtr` using memory allocated from this instanced
638    /// allocator.
639    pub fn new_ref(&self, value: T) -> Result<RefPtr<T>, AllocError>
640    where
641        T: HasRefCount + Recyclable + InstancedSlabAllocated<L, SLAB_SIZE, TRACK_OBJECT_COUNT>,
642    {
643        let ptr = self.alloc_raw()?;
644        // SAFETY: `ptr` is valid and uninitialized.
645        unsafe {
646            write(ptr.as_ptr(), value);
647            (&mut *ptr.as_ptr()).set_slab_origin(NonNull::from(self));
648            (*ptr.as_ptr()).ref_count().adopt();
649            Ok(RefPtr::from_raw(ptr.as_ptr()))
650        }
651    }
652
653    /// Destructs and deallocates an unmanaged object.
654    ///
655    /// # Safety
656    ///
657    /// `ptr` must point to a valid object allocated from this allocator.
658    pub unsafe fn delete(&self, ptr: NonNull<T>) {
659        // SAFETY: The caller guarantees that `ptr` points to a valid object.
660        unsafe {
661            drop_in_place(ptr.as_ptr());
662            self.return_to_free_list(ptr);
663        }
664    }
665
666    /// Returns the number of currently allocated objects.
667    pub fn obj_count(&self) -> usize {
668        const {
669            assert!(TRACK_OBJECT_COUNT, "Error accessing obj_count: Object counter not enabled");
670        }
671        lock!(let guard = self.lock_mu());
672        *guard.fields().obj_count
673    }
674
675    /// Returns the maximum number of objects allocated simultaneously over the life of the
676    /// allocator.
677    pub fn max_obj_count(&self) -> usize {
678        const {
679            assert!(
680                TRACK_OBJECT_COUNT,
681                "Error accessing max_obj_count: Object counter not enabled"
682            );
683        }
684        lock!(let guard = self.lock_mu());
685        *guard.fields().max_obj_count
686    }
687
688    /// Returns the number of slabs allocated.
689    pub fn slab_count(&self) -> usize {
690        lock!(let guard = self.lock_mu());
691        *guard.fields().slab_count
692    }
693
694    /// Returns the maximum number of slabs this allocator is allowed to allocate.
695    pub fn max_slabs(&self) -> usize {
696        self.max_slabs
697    }
698
699    /// Resets the maximum object count tracker to the current object count.
700    pub fn reset_max_obj_count(&self) {
701        const {
702            assert!(
703                TRACK_OBJECT_COUNT,
704                "Error performing reset_max_obj_count: Object counter not enabled"
705            );
706        }
707        lock!(let guard = self.lock_mu());
708        let fields = guard.fields_mut();
709        *fields.max_obj_count = *fields.obj_count;
710    }
711}
712
713impl<'b, T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>
714    SlabAllocatorMuFieldsMut<'b, T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
715{
716    #[inline(always)]
717    fn record_allocation(&mut self) {
718        if TRACK_OBJECT_COUNT {
719            *self.obj_count += 1;
720            *self.max_obj_count = max(*self.max_obj_count, *self.obj_count);
721        }
722    }
723
724    #[inline(always)]
725    fn record_deallocation(&mut self) {
726        if TRACK_OBJECT_COUNT {
727            *self.obj_count -= 1;
728        }
729    }
730}
731
732#[pinned_drop]
733impl<T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool> PinnedDrop
734    for SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>
735{
736    fn drop(self: Pin<&mut Self>) {
737        // SAFETY: We can safely get the mutable reference to the fields inside drop.
738        let me = unsafe { self.get_unchecked_mut() };
739        let free_list = me.free_list.get_inner_mut();
740        let slab_list = me.slab_list.get_inner_mut();
741        // Verify there are no outstanding allocations in debug builds
742        #[cfg(debug_assertions)]
743        {
744            let obj_count = me.obj_count.get_inner_mut();
745            if TRACK_OBJECT_COUNT {
746                debug_assert_eq!(
747                    *obj_count, 0,
748                    "SlabAllocator destroyed with outstanding allocations!"
749                );
750            } else {
751                // If tracking is disabled, perform a slow counting check to verify leak-free drop
752                let free_list_size = free_list.iter().count();
753                let mut allocated_count = 0;
754                for slab in slab_list.iter() {
755                    let bytes_used = slab.bytes_used - Self::STORAGE_OFFSET;
756                    allocated_count += bytes_used / Self::ALLOC_SIZE;
757                }
758                debug_assert_eq!(
759                    free_list_size, allocated_count,
760                    "SlabAllocator destroyed with outstanding allocations!"
761                );
762            }
763        }
764
765        // Clear free list first so it doesn't assert on drop.
766        // Note: free list entries are raw pointers to slab memory, so popping them is a no-op.
767        free_list.clear();
768
769        while let Some(slab_ptr) = slab_list.pop_front() {
770            // Drop the SlabHeader and deallocate slab memory
771            // SAFETY: `slab_ptr` is a valid pointer to `SlabHeader` from `slab_list`.
772            unsafe {
773                Self::dealloc_slab(slab_ptr);
774            }
775        }
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use super::*;
782    use crate::RefCounted;
783    use alloc::vec::Vec;
784    use core::cmp::min;
785    use core::ptr::write;
786    use core::sync::atomic::{AtomicUsize, Ordering};
787    use lock_api::RawMutex as _;
788    use pin_init::stack_pin_init;
789    extern crate alloc;
790
791    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
792    enum ConstructType {
793        Default,
794        LvalueRef,
795        RvalueRef,
796        LThenRRef,
797    }
798
799    trait TestConstructors {
800        fn new_default() -> Self;
801        fn new_lvalue(val: usize) -> Self;
802        fn new_rvalue(val: usize) -> Self;
803        fn new_l_then_r(a: usize, b: usize) -> Self;
804        fn ctype(&self) -> ConstructType;
805    }
806
807    // Helper trait to allow compile-time conditional tracking assertions inside generic test
808    // runners.
809    trait MaybeTracked {
810        fn maybe_obj_count(&self) -> usize;
811        fn maybe_max_obj_count(&self) -> usize;
812        fn maybe_reset_max_obj_count(&self);
813    }
814
815    impl<T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize> MaybeTracked
816        for SlabAllocator<T, L, SLAB_SIZE, true>
817    {
818        fn maybe_obj_count(&self) -> usize {
819            self.obj_count()
820        }
821        fn maybe_max_obj_count(&self) -> usize {
822            self.max_obj_count()
823        }
824        fn maybe_reset_max_obj_count(&self) {
825            self.reset_max_obj_count()
826        }
827    }
828
829    impl<T, L: RawLock + IsRawMutex, const SLAB_SIZE: usize> MaybeTracked
830        for SlabAllocator<T, L, SLAB_SIZE, false>
831    {
832        fn maybe_obj_count(&self) -> usize {
833            0
834        }
835        fn maybe_max_obj_count(&self) -> usize {
836            0
837        }
838        fn maybe_reset_max_obj_count(&self) {}
839    }
840
841    // Generic test runners with passed counter reference to avoid cross-test pollution
842    fn run_instanced_unique_test<T, L, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>(
843        allocated_obj_count: &'static AtomicUsize,
844        max_slabs: usize,
845        test_allocs: usize,
846    ) where
847        T: Recyclable
848            + InstancedSlabAllocated<L, SLAB_SIZE, TRACK_OBJECT_COUNT>
849            + TestConstructors
850            + 'static,
851        L: RawLock + IsRawMutex + 'static,
852        SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>: MaybeTracked,
853    {
854        allocated_obj_count.store(0, Ordering::SeqCst);
855        let init = SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::init(max_slabs);
856        stack_pin_init!(let allocator = init);
857
858        assert_eq!(allocator.slab_count(), 0);
859        if TRACK_OBJECT_COUNT {
860            assert_eq!(allocator.maybe_obj_count(), 0);
861            assert_eq!(allocator.maybe_max_obj_count(), 0);
862        }
863
864        let max_allocs =
865            SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::ALLOCS_PER_SLAB * max_slabs;
866        let mut ref_list = Vec::new();
867
868        for i in 0..test_allocs {
869            let expected_count = min(i, max_allocs);
870            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count);
871
872            if TRACK_OBJECT_COUNT {
873                assert_eq!(allocator.maybe_obj_count(), expected_count);
874                assert_eq!(allocator.maybe_max_obj_count(), expected_count);
875            }
876
877            let val = match i % 4 {
878                0 => T::new_default(),
879                1 => T::new_lvalue(i),
880                2 => T::new_rvalue(i),
881                _ => T::new_l_then_r(i, i),
882            };
883
884            let ptr = allocator.new_unique(val);
885
886            if i < max_allocs {
887                let obj = ptr.expect("Allocation failed when it should not have!");
888                ref_list.push(obj);
889            } else {
890                assert!(ptr.is_err(), "Allocation succeeded when it should not have!");
891            }
892
893            let expected_count_after = min(i + 1, max_allocs);
894            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count_after);
895
896            if TRACK_OBJECT_COUNT {
897                assert_eq!(allocator.maybe_obj_count(), expected_count_after);
898                assert_eq!(allocator.maybe_max_obj_count(), expected_count_after);
899            }
900        }
901
902        let mut max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
903        let total_allocated = ref_list.len();
904
905        for (i, obj) in ref_list.into_iter().enumerate() {
906            let current_expected = total_allocated - i;
907            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
908
909            if TRACK_OBJECT_COUNT {
910                assert_eq!(allocator.maybe_obj_count(), current_expected);
911                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
912            }
913
914            match i % 4 {
915                0 => assert_eq!(obj.ctype(), ConstructType::Default),
916                1 => assert_eq!(obj.ctype(), ConstructType::LvalueRef),
917                2 => assert_eq!(obj.ctype(), ConstructType::RvalueRef),
918                _ => assert_eq!(obj.ctype(), ConstructType::LThenRRef),
919            }
920
921            drop(obj); // This returns memory to the free list
922
923            if TRACK_OBJECT_COUNT {
924                if i % 2 == 1 {
925                    allocator.maybe_reset_max_obj_count();
926                    max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
927                }
928                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
929            }
930        }
931
932        assert_eq!(allocated_obj_count.load(Ordering::SeqCst), 0);
933        if TRACK_OBJECT_COUNT {
934            assert_eq!(allocator.maybe_obj_count(), 0);
935            assert_eq!(allocator.maybe_max_obj_count(), total_allocated % 2);
936            allocator.maybe_reset_max_obj_count();
937            assert_eq!(allocator.maybe_max_obj_count(), 0);
938        }
939    }
940
941    fn run_instanced_ref_test<T, L, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>(
942        allocated_obj_count: &'static AtomicUsize,
943        max_slabs: usize,
944        test_allocs: usize,
945    ) where
946        T: HasRefCount
947            + Recyclable
948            + InstancedSlabAllocated<L, SLAB_SIZE, TRACK_OBJECT_COUNT>
949            + TestConstructors
950            + 'static,
951        L: RawLock + IsRawMutex + 'static,
952        SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>: MaybeTracked,
953    {
954        allocated_obj_count.store(0, Ordering::SeqCst);
955        let init = SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::init(max_slabs);
956        stack_pin_init!(let allocator = init);
957
958        assert_eq!(allocator.slab_count(), 0);
959        if TRACK_OBJECT_COUNT {
960            assert_eq!(allocator.maybe_obj_count(), 0);
961            assert_eq!(allocator.maybe_max_obj_count(), 0);
962        }
963
964        let max_allocs =
965            SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::ALLOCS_PER_SLAB * max_slabs;
966        let mut ref_list = Vec::new();
967
968        for i in 0..test_allocs {
969            let expected_count = min(i, max_allocs);
970            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count);
971
972            if TRACK_OBJECT_COUNT {
973                assert_eq!(allocator.maybe_obj_count(), expected_count);
974                assert_eq!(allocator.maybe_max_obj_count(), expected_count);
975            }
976
977            let val = match i % 4 {
978                0 => T::new_default(),
979                1 => T::new_lvalue(i),
980                2 => T::new_rvalue(i),
981                _ => T::new_l_then_r(i, i),
982            };
983
984            let ptr = allocator.new_ref(val);
985
986            if i < max_allocs {
987                let obj = ptr.expect("Allocation failed when it should not have!");
988                ref_list.push(obj);
989            } else {
990                assert!(ptr.is_err(), "Allocation succeeded when it should not have!");
991            }
992
993            let expected_count_after = min(i + 1, max_allocs);
994            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count_after);
995
996            if TRACK_OBJECT_COUNT {
997                assert_eq!(allocator.maybe_obj_count(), expected_count_after);
998                assert_eq!(allocator.maybe_max_obj_count(), expected_count_after);
999            }
1000        }
1001
1002        let mut max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1003        let total_allocated = ref_list.len();
1004
1005        for (i, obj) in ref_list.into_iter().enumerate() {
1006            let current_expected = total_allocated - i;
1007            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1008
1009            if TRACK_OBJECT_COUNT {
1010                assert_eq!(allocator.maybe_obj_count(), current_expected);
1011                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1012            }
1013
1014            match i % 4 {
1015                0 => assert_eq!(obj.ctype(), ConstructType::Default),
1016                1 => assert_eq!(obj.ctype(), ConstructType::LvalueRef),
1017                2 => assert_eq!(obj.ctype(), ConstructType::RvalueRef),
1018                _ => assert_eq!(obj.ctype(), ConstructType::LThenRRef),
1019            }
1020
1021            // Test cloning
1022            {
1023                let _clone = obj.clone();
1024                assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1025            }
1026
1027            drop(obj); // This returns memory to the free list
1028
1029            if TRACK_OBJECT_COUNT {
1030                if i % 2 == 1 {
1031                    allocator.maybe_reset_max_obj_count();
1032                    max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1033                }
1034                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1035            }
1036        }
1037
1038        assert_eq!(allocated_obj_count.load(Ordering::SeqCst), 0);
1039        if TRACK_OBJECT_COUNT {
1040            assert_eq!(allocator.maybe_obj_count(), 0);
1041            assert_eq!(allocator.maybe_max_obj_count(), total_allocated % 2);
1042            allocator.maybe_reset_max_obj_count();
1043            assert_eq!(allocator.maybe_max_obj_count(), 0);
1044        }
1045    }
1046
1047    fn run_unmanaged_test<T, L, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>(
1048        allocated_obj_count: &'static AtomicUsize,
1049        max_slabs: usize,
1050        test_allocs: usize,
1051    ) where
1052        T: TestConstructors + 'static,
1053        L: RawLock + IsRawMutex + 'static,
1054        SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>: MaybeTracked,
1055    {
1056        allocated_obj_count.store(0, Ordering::SeqCst);
1057        let init = SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::init(max_slabs);
1058        stack_pin_init!(let allocator = init);
1059
1060        assert_eq!(allocator.slab_count(), 0);
1061        if TRACK_OBJECT_COUNT {
1062            assert_eq!(allocator.maybe_obj_count(), 0);
1063            assert_eq!(allocator.maybe_max_obj_count(), 0);
1064        }
1065
1066        let max_allocs =
1067            SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::ALLOCS_PER_SLAB * max_slabs;
1068        let mut ref_list = Vec::new();
1069
1070        for i in 0..test_allocs {
1071            let expected_count = min(i, max_allocs);
1072            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count);
1073
1074            if TRACK_OBJECT_COUNT {
1075                assert_eq!(allocator.maybe_obj_count(), expected_count);
1076                assert_eq!(allocator.maybe_max_obj_count(), expected_count);
1077            }
1078
1079            let ptr = allocator.alloc_raw();
1080
1081            if i < max_allocs {
1082                let p = ptr.expect("Allocation failed when it should not have!");
1083                // SAFETY: We initialize the newly allocated raw memory.
1084                unsafe {
1085                    let val = match i % 4 {
1086                        0 => T::new_default(),
1087                        1 => T::new_lvalue(i),
1088                        2 => T::new_rvalue(i),
1089                        _ => T::new_l_then_r(i, i),
1090                    };
1091                    write(p.as_ptr(), val);
1092                }
1093                ref_list.push(p);
1094            } else {
1095                assert!(ptr.is_err(), "Allocation succeeded when it should not have!");
1096            }
1097
1098            let expected_count_after = min(i + 1, max_allocs);
1099            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count_after);
1100
1101            if TRACK_OBJECT_COUNT {
1102                assert_eq!(allocator.maybe_obj_count(), expected_count_after);
1103                assert_eq!(allocator.maybe_max_obj_count(), expected_count_after);
1104            }
1105        }
1106
1107        let mut max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1108        let total_allocated = ref_list.len();
1109
1110        for (i, p) in ref_list.into_iter().enumerate() {
1111            let current_expected = total_allocated - i;
1112            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1113
1114            if TRACK_OBJECT_COUNT {
1115                assert_eq!(allocator.maybe_obj_count(), current_expected);
1116                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1117            }
1118
1119            // SAFETY: `p` is a valid pointer to T
1120            unsafe {
1121                match i % 4 {
1122                    0 => assert_eq!(p.as_ref().ctype(), ConstructType::Default),
1123                    1 => assert_eq!(p.as_ref().ctype(), ConstructType::LvalueRef),
1124                    2 => assert_eq!(p.as_ref().ctype(), ConstructType::RvalueRef),
1125                    _ => assert_eq!(p.as_ref().ctype(), ConstructType::LThenRRef),
1126                }
1127
1128                allocator.delete(p);
1129            }
1130
1131            if TRACK_OBJECT_COUNT {
1132                if i % 2 == 1 {
1133                    allocator.maybe_reset_max_obj_count();
1134                    max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1135                }
1136                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1137            }
1138        }
1139
1140        assert_eq!(allocated_obj_count.load(Ordering::SeqCst), 0);
1141        if TRACK_OBJECT_COUNT {
1142            assert_eq!(allocator.maybe_obj_count(), 0);
1143            assert_eq!(allocator.maybe_max_obj_count(), total_allocated % 2);
1144            allocator.maybe_reset_max_obj_count();
1145            assert_eq!(allocator.maybe_max_obj_count(), 0);
1146        }
1147    }
1148
1149    fn run_static_unique_test<T, L, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>(
1150        allocated_obj_count: &'static AtomicUsize,
1151        allocator: &'static SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>,
1152        max_slabs: usize,
1153        test_allocs: usize,
1154    ) where
1155        T: Recyclable
1156            + StaticSlabAllocated<L, SLAB_SIZE, TRACK_OBJECT_COUNT>
1157            + TestConstructors
1158            + 'static,
1159        L: RawLock + IsRawMutex + 'static,
1160        SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>: MaybeTracked,
1161    {
1162        allocated_obj_count.store(0, Ordering::SeqCst);
1163
1164        if TRACK_OBJECT_COUNT {
1165            allocator.maybe_reset_max_obj_count();
1166            assert_eq!(allocator.maybe_obj_count(), 0);
1167            assert_eq!(allocator.maybe_max_obj_count(), 0);
1168        }
1169
1170        let max_allocs =
1171            SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::ALLOCS_PER_SLAB * max_slabs;
1172        let mut ref_list = Vec::new();
1173
1174        for i in 0..test_allocs {
1175            let expected_count = min(i, max_allocs);
1176            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count);
1177
1178            if TRACK_OBJECT_COUNT {
1179                assert_eq!(allocator.maybe_obj_count(), expected_count);
1180                assert_eq!(allocator.maybe_max_obj_count(), expected_count);
1181            }
1182
1183            let val = match i % 4 {
1184                0 => T::new_default(),
1185                1 => T::new_lvalue(i),
1186                2 => T::new_rvalue(i),
1187                _ => T::new_l_then_r(i, i),
1188            };
1189
1190            let ptr = UniquePtr::try_new(val);
1191
1192            if i < max_allocs {
1193                let obj = ptr.expect("Allocation failed when it should not have!");
1194                ref_list.push(obj);
1195            } else {
1196                assert!(ptr.is_err(), "Allocation succeeded when it should not have!");
1197            }
1198
1199            let expected_count_after = min(i + 1, max_allocs);
1200            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count_after);
1201
1202            if TRACK_OBJECT_COUNT {
1203                assert_eq!(allocator.maybe_obj_count(), expected_count_after);
1204                assert_eq!(allocator.maybe_max_obj_count(), expected_count_after);
1205            }
1206        }
1207
1208        let mut max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1209        let total_allocated = ref_list.len();
1210
1211        for (i, obj) in ref_list.into_iter().enumerate() {
1212            let current_expected = total_allocated - i;
1213            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1214
1215            if TRACK_OBJECT_COUNT {
1216                assert_eq!(allocator.maybe_obj_count(), current_expected);
1217                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1218            }
1219
1220            match i % 4 {
1221                0 => assert_eq!(obj.ctype(), ConstructType::Default),
1222                1 => assert_eq!(obj.ctype(), ConstructType::LvalueRef),
1223                2 => assert_eq!(obj.ctype(), ConstructType::RvalueRef),
1224                _ => assert_eq!(obj.ctype(), ConstructType::LThenRRef),
1225            }
1226
1227            drop(obj); // This returns memory to the free list
1228
1229            if TRACK_OBJECT_COUNT {
1230                if i % 2 == 1 {
1231                    allocator.maybe_reset_max_obj_count();
1232                    max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1233                }
1234                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1235            }
1236        }
1237
1238        assert_eq!(allocated_obj_count.load(Ordering::SeqCst), 0);
1239        if TRACK_OBJECT_COUNT {
1240            assert_eq!(allocator.maybe_obj_count(), 0);
1241            assert_eq!(allocator.maybe_max_obj_count(), total_allocated % 2);
1242            allocator.maybe_reset_max_obj_count();
1243            assert_eq!(allocator.maybe_max_obj_count(), 0);
1244        }
1245    }
1246
1247    fn run_static_ref_test<T, L, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>(
1248        allocated_obj_count: &'static AtomicUsize,
1249        allocator: &'static SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>,
1250        max_slabs: usize,
1251        test_allocs: usize,
1252    ) where
1253        T: HasRefCount
1254            + Recyclable
1255            + StaticSlabAllocated<L, SLAB_SIZE, TRACK_OBJECT_COUNT>
1256            + TestConstructors
1257            + 'static,
1258        L: RawLock + IsRawMutex + 'static,
1259        SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>: MaybeTracked,
1260    {
1261        allocated_obj_count.store(0, Ordering::SeqCst);
1262
1263        if TRACK_OBJECT_COUNT {
1264            allocator.maybe_reset_max_obj_count();
1265            assert_eq!(allocator.maybe_obj_count(), 0);
1266            assert_eq!(allocator.maybe_max_obj_count(), 0);
1267        }
1268
1269        let max_allocs =
1270            SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::ALLOCS_PER_SLAB * max_slabs;
1271        let mut ref_list = Vec::new();
1272
1273        for i in 0..test_allocs {
1274            let expected_count = min(i, max_allocs);
1275            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count);
1276
1277            if TRACK_OBJECT_COUNT {
1278                assert_eq!(allocator.maybe_obj_count(), expected_count);
1279                assert_eq!(allocator.maybe_max_obj_count(), expected_count);
1280            }
1281
1282            let val = match i % 4 {
1283                0 => T::new_default(),
1284                1 => T::new_lvalue(i),
1285                2 => T::new_rvalue(i),
1286                _ => T::new_l_then_r(i, i),
1287            };
1288
1289            // SAFETY: The object is not yet adopted.
1290            let ptr = unsafe { RefPtr::try_new(val) };
1291
1292            if i < max_allocs {
1293                let obj = ptr.expect("Allocation failed when it should not have!");
1294                ref_list.push(obj);
1295            } else {
1296                assert!(ptr.is_err(), "Allocation succeeded when it should not have!");
1297            }
1298
1299            let expected_count_after = min(i + 1, max_allocs);
1300            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count_after);
1301
1302            if TRACK_OBJECT_COUNT {
1303                assert_eq!(allocator.maybe_obj_count(), expected_count_after);
1304                assert_eq!(allocator.maybe_max_obj_count(), expected_count_after);
1305            }
1306        }
1307
1308        let mut max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1309        let total_allocated = ref_list.len();
1310
1311        for (i, obj) in ref_list.into_iter().enumerate() {
1312            let current_expected = total_allocated - i;
1313            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1314
1315            if TRACK_OBJECT_COUNT {
1316                assert_eq!(allocator.maybe_obj_count(), current_expected);
1317                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1318            }
1319
1320            match i % 4 {
1321                0 => assert_eq!(obj.ctype(), ConstructType::Default),
1322                1 => assert_eq!(obj.ctype(), ConstructType::LvalueRef),
1323                2 => assert_eq!(obj.ctype(), ConstructType::RvalueRef),
1324                _ => assert_eq!(obj.ctype(), ConstructType::LThenRRef),
1325            }
1326
1327            // Test cloning
1328            {
1329                let _clone = obj.clone();
1330                assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1331            }
1332
1333            drop(obj); // This returns memory to the free list
1334
1335            if TRACK_OBJECT_COUNT {
1336                if i % 2 == 1 {
1337                    allocator.maybe_reset_max_obj_count();
1338                    max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1339                }
1340                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1341            }
1342        }
1343
1344        assert_eq!(allocated_obj_count.load(Ordering::SeqCst), 0);
1345        if TRACK_OBJECT_COUNT {
1346            assert_eq!(allocator.maybe_obj_count(), 0);
1347            assert_eq!(allocator.maybe_max_obj_count(), total_allocated % 2);
1348            allocator.maybe_reset_max_obj_count();
1349            assert_eq!(allocator.maybe_max_obj_count(), 0);
1350        }
1351    }
1352
1353    fn run_static_unmanaged_test<T, L, const SLAB_SIZE: usize, const TRACK_OBJECT_COUNT: bool>(
1354        allocated_obj_count: &'static AtomicUsize,
1355        allocator: &'static SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>,
1356        max_slabs: usize,
1357        test_allocs: usize,
1358    ) where
1359        T: TestConstructors + 'static,
1360        L: RawLock + IsRawMutex + 'static,
1361        SlabAllocator<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>: MaybeTracked,
1362    {
1363        allocated_obj_count.store(0, Ordering::SeqCst);
1364
1365        if TRACK_OBJECT_COUNT {
1366            allocator.maybe_reset_max_obj_count();
1367            assert_eq!(allocator.maybe_obj_count(), 0);
1368            assert_eq!(allocator.maybe_max_obj_count(), 0);
1369        }
1370
1371        let max_allocs =
1372            SlabAllocator::<T, L, SLAB_SIZE, TRACK_OBJECT_COUNT>::ALLOCS_PER_SLAB * max_slabs;
1373        let mut ref_list = Vec::new();
1374
1375        for i in 0..test_allocs {
1376            let expected_count = min(i, max_allocs);
1377            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count);
1378
1379            if TRACK_OBJECT_COUNT {
1380                assert_eq!(allocator.maybe_obj_count(), expected_count);
1381                assert_eq!(allocator.maybe_max_obj_count(), expected_count);
1382            }
1383
1384            let ptr = allocator.alloc_raw();
1385
1386            if i < max_allocs {
1387                let p = ptr.expect("Allocation failed when it should not have!");
1388                // SAFETY: We initialize the newly allocated raw memory.
1389                unsafe {
1390                    let val = match i % 4 {
1391                        0 => T::new_default(),
1392                        1 => T::new_lvalue(i),
1393                        2 => T::new_rvalue(i),
1394                        _ => T::new_l_then_r(i, i),
1395                    };
1396                    write(p.as_ptr(), val);
1397                }
1398                ref_list.push(p);
1399            } else {
1400                assert!(ptr.is_err(), "Allocation succeeded when it should not have!");
1401            }
1402
1403            let expected_count_after = min(i + 1, max_allocs);
1404            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), expected_count_after);
1405
1406            if TRACK_OBJECT_COUNT {
1407                assert_eq!(allocator.maybe_obj_count(), expected_count_after);
1408                assert_eq!(allocator.maybe_max_obj_count(), expected_count_after);
1409            }
1410        }
1411
1412        let mut max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1413        let total_allocated = ref_list.len();
1414
1415        for (i, p) in ref_list.into_iter().enumerate() {
1416            let current_expected = total_allocated - i;
1417            assert_eq!(allocated_obj_count.load(Ordering::SeqCst), current_expected);
1418
1419            if TRACK_OBJECT_COUNT {
1420                assert_eq!(allocator.maybe_obj_count(), current_expected);
1421                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1422            }
1423
1424            // SAFETY: `p` is a valid pointer to T
1425            unsafe {
1426                match i % 4 {
1427                    0 => assert_eq!(p.as_ref().ctype(), ConstructType::Default),
1428                    1 => assert_eq!(p.as_ref().ctype(), ConstructType::LvalueRef),
1429                    2 => assert_eq!(p.as_ref().ctype(), ConstructType::RvalueRef),
1430                    _ => assert_eq!(p.as_ref().ctype(), ConstructType::LThenRRef),
1431                }
1432
1433                allocator.delete(p);
1434            }
1435
1436            if TRACK_OBJECT_COUNT {
1437                if i % 2 == 1 {
1438                    allocator.maybe_reset_max_obj_count();
1439                    max_obj_count = allocated_obj_count.load(Ordering::SeqCst);
1440                }
1441                assert_eq!(allocator.maybe_max_obj_count(), max_obj_count);
1442            }
1443        }
1444
1445        assert_eq!(allocated_obj_count.load(Ordering::SeqCst), 0);
1446        if TRACK_OBJECT_COUNT {
1447            assert_eq!(allocator.maybe_obj_count(), 0);
1448            assert_eq!(allocator.maybe_max_obj_count(), total_allocated % 2);
1449            allocator.maybe_reset_max_obj_count();
1450            assert_eq!(allocator.maybe_max_obj_count(), 0);
1451        }
1452    }
1453
1454    macro_rules! define_instanced_unique_test {
1455        ($name:ident, $lock:ty, $lock_init:expr, $slab_size:expr, $track_obj_count:expr, $max_slabs:expr) => {
1456            #[test]
1457            fn $name() {
1458                static ALLOCATED_OBJ_COUNT: AtomicUsize = AtomicUsize::new(0);
1459                struct Obj {
1460                    ctype: ConstructType,
1461                    slab_origin: SlabOrigin<Obj, $lock, $slab_size, $track_obj_count>,
1462                    _payload: [u8; 13],
1463                }
1464                impl TestConstructors for Obj {
1465                    fn new_default() -> Self {
1466                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1467                        Self {
1468                            ctype: ConstructType::Default,
1469                            slab_origin: SlabOrigin::new(),
1470                            _payload: [0; 13],
1471                        }
1472                    }
1473                    fn new_lvalue(_val: usize) -> Self {
1474                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1475                        Self {
1476                            ctype: ConstructType::LvalueRef,
1477                            slab_origin: SlabOrigin::new(),
1478                            _payload: [0; 13],
1479                        }
1480                    }
1481                    fn new_rvalue(_val: usize) -> Self {
1482                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1483                        Self {
1484                            ctype: ConstructType::RvalueRef,
1485                            slab_origin: SlabOrigin::new(),
1486                            _payload: [0; 13],
1487                        }
1488                    }
1489                    fn new_l_then_r(_a: usize, _b: usize) -> Self {
1490                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1491                        Self {
1492                            ctype: ConstructType::LThenRRef,
1493                            slab_origin: SlabOrigin::new(),
1494                            _payload: [0; 13],
1495                        }
1496                    }
1497                    fn ctype(&self) -> ConstructType {
1498                        self.ctype
1499                    }
1500                }
1501                impl Drop for Obj {
1502                    fn drop(&mut self) {
1503                        ALLOCATED_OBJ_COUNT.fetch_sub(1, Ordering::SeqCst);
1504                    }
1505                }
1506                crate::impl_instanced_slab_allocatable!(Obj, $lock, $slab_size, $track_obj_count);
1507
1508                let max_allocs =
1509                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::ALLOCS_PER_SLAB
1510                        * $max_slabs;
1511
1512                run_instanced_unique_test::<Obj, $lock, $slab_size, $track_obj_count>(
1513                    &ALLOCATED_OBJ_COUNT,
1514                    $max_slabs,
1515                    1,
1516                );
1517                run_instanced_unique_test::<Obj, $lock, $slab_size, $track_obj_count>(
1518                    &ALLOCATED_OBJ_COUNT,
1519                    $max_slabs,
1520                    max_allocs / 2,
1521                );
1522                run_instanced_unique_test::<Obj, $lock, $slab_size, $track_obj_count>(
1523                    &ALLOCATED_OBJ_COUNT,
1524                    $max_slabs,
1525                    max_allocs + 4,
1526                );
1527            }
1528        };
1529    }
1530
1531    macro_rules! define_instanced_ref_test {
1532        ($name:ident, $lock:ty, $lock_init:expr, $slab_size:expr, $track_obj_count:expr, $max_slabs:expr) => {
1533            #[test]
1534            fn $name() {
1535                static ALLOCATED_OBJ_COUNT: AtomicUsize = AtomicUsize::new(0);
1536                #[crate::ref_counted]
1537                #[repr(C)]
1538                struct Obj {
1539                    ctype: ConstructType,
1540                    slab_origin: SlabOrigin<Obj, $lock, $slab_size, $track_obj_count>,
1541                    _payload: [u8; 13],
1542                }
1543                impl TestConstructors for Obj {
1544                    fn new_default() -> Self {
1545                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1546                        Self {
1547                            ref_count: RefCounted::new(),
1548                            __fbl_ref_counted_guard: (),
1549                            ctype: ConstructType::Default,
1550                            slab_origin: SlabOrigin::new(),
1551                            _payload: [0; 13],
1552                        }
1553                    }
1554                    fn new_lvalue(_val: usize) -> Self {
1555                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1556                        Self {
1557                            ref_count: RefCounted::new(),
1558                            __fbl_ref_counted_guard: (),
1559                            ctype: ConstructType::LvalueRef,
1560                            slab_origin: SlabOrigin::new(),
1561                            _payload: [0; 13],
1562                        }
1563                    }
1564                    fn new_rvalue(_val: usize) -> Self {
1565                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1566                        Self {
1567                            ref_count: RefCounted::new(),
1568                            __fbl_ref_counted_guard: (),
1569                            ctype: ConstructType::RvalueRef,
1570                            slab_origin: SlabOrigin::new(),
1571                            _payload: [0; 13],
1572                        }
1573                    }
1574                    fn new_l_then_r(_a: usize, _b: usize) -> Self {
1575                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1576                        Self {
1577                            ref_count: RefCounted::new(),
1578                            __fbl_ref_counted_guard: (),
1579                            ctype: ConstructType::LThenRRef,
1580                            slab_origin: SlabOrigin::new(),
1581                            _payload: [0; 13],
1582                        }
1583                    }
1584                    fn ctype(&self) -> ConstructType {
1585                        self.ctype
1586                    }
1587                }
1588                impl Drop for Obj {
1589                    fn drop(&mut self) {
1590                        ALLOCATED_OBJ_COUNT.fetch_sub(1, Ordering::SeqCst);
1591                    }
1592                }
1593                crate::impl_instanced_slab_allocatable!(Obj, $lock, $slab_size, $track_obj_count);
1594
1595                let max_allocs =
1596                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::ALLOCS_PER_SLAB
1597                        * $max_slabs;
1598
1599                run_instanced_ref_test::<Obj, $lock, $slab_size, $track_obj_count>(
1600                    &ALLOCATED_OBJ_COUNT,
1601                    $max_slabs,
1602                    1,
1603                );
1604                run_instanced_ref_test::<Obj, $lock, $slab_size, $track_obj_count>(
1605                    &ALLOCATED_OBJ_COUNT,
1606                    $max_slabs,
1607                    max_allocs / 2,
1608                );
1609                run_instanced_ref_test::<Obj, $lock, $slab_size, $track_obj_count>(
1610                    &ALLOCATED_OBJ_COUNT,
1611                    $max_slabs,
1612                    max_allocs + 4,
1613                );
1614            }
1615        };
1616    }
1617
1618    macro_rules! define_unmanaged_test {
1619        ($name:ident, $lock:ty, $lock_init:expr, $slab_size:expr, $track_obj_count:expr, $max_slabs:expr) => {
1620            #[test]
1621            fn $name() {
1622                static ALLOCATED_OBJ_COUNT: AtomicUsize = AtomicUsize::new(0);
1623                struct Obj {
1624                    ctype: ConstructType,
1625                    _payload: [u8; 13],
1626                }
1627                impl TestConstructors for Obj {
1628                    fn new_default() -> Self {
1629                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1630                        Self { ctype: ConstructType::Default, _payload: [0; 13] }
1631                    }
1632                    fn new_lvalue(_val: usize) -> Self {
1633                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1634                        Self { ctype: ConstructType::LvalueRef, _payload: [0; 13] }
1635                    }
1636                    fn new_rvalue(_val: usize) -> Self {
1637                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1638                        Self { ctype: ConstructType::RvalueRef, _payload: [0; 13] }
1639                    }
1640                    fn new_l_then_r(_a: usize, _b: usize) -> Self {
1641                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1642                        Self { ctype: ConstructType::LThenRRef, _payload: [0; 13] }
1643                    }
1644                    fn ctype(&self) -> ConstructType {
1645                        self.ctype
1646                    }
1647                }
1648                impl Drop for Obj {
1649                    fn drop(&mut self) {
1650                        ALLOCATED_OBJ_COUNT.fetch_sub(1, Ordering::SeqCst);
1651                    }
1652                }
1653
1654                let max_allocs =
1655                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::ALLOCS_PER_SLAB
1656                        * $max_slabs;
1657
1658                run_unmanaged_test::<Obj, $lock, $slab_size, $track_obj_count>(
1659                    &ALLOCATED_OBJ_COUNT,
1660                    $max_slabs,
1661                    1,
1662                );
1663                run_unmanaged_test::<Obj, $lock, $slab_size, $track_obj_count>(
1664                    &ALLOCATED_OBJ_COUNT,
1665                    $max_slabs,
1666                    max_allocs / 2,
1667                );
1668                run_unmanaged_test::<Obj, $lock, $slab_size, $track_obj_count>(
1669                    &ALLOCATED_OBJ_COUNT,
1670                    $max_slabs,
1671                    max_allocs + 4,
1672                );
1673            }
1674        };
1675    }
1676
1677    macro_rules! define_static_unique_test {
1678        ($name:ident, $lock:ty, $lock_init:expr, $slab_size:expr, $track_obj_count:expr, $max_slabs:expr) => {
1679            #[test]
1680            fn $name() {
1681                static ALLOCATED_OBJ_COUNT: AtomicUsize = AtomicUsize::new(0);
1682                struct Obj {
1683                    ctype: ConstructType,
1684                    _payload: [u8; 13],
1685                }
1686                impl TestConstructors for Obj {
1687                    fn new_default() -> Self {
1688                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1689                        Self { ctype: ConstructType::Default, _payload: [0; 13] }
1690                    }
1691                    fn new_lvalue(_val: usize) -> Self {
1692                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1693                        Self { ctype: ConstructType::LvalueRef, _payload: [0; 13] }
1694                    }
1695                    fn new_rvalue(_val: usize) -> Self {
1696                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1697                        Self { ctype: ConstructType::RvalueRef, _payload: [0; 13] }
1698                    }
1699                    fn new_l_then_r(_a: usize, _b: usize) -> Self {
1700                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1701                        Self { ctype: ConstructType::LThenRRef, _payload: [0; 13] }
1702                    }
1703                    fn ctype(&self) -> ConstructType {
1704                        self.ctype
1705                    }
1706                }
1707                impl Drop for Obj {
1708                    fn drop(&mut self) {
1709                        ALLOCATED_OBJ_COUNT.fetch_sub(1, Ordering::SeqCst);
1710                    }
1711                }
1712
1713                static ALLOCATOR: SlabAllocator<Obj, $lock, $slab_size, $track_obj_count> =
1714                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::const_new(
1715                        $max_slabs, $lock_init,
1716                    );
1717
1718                crate::impl_static_slab_allocatable!(
1719                    Obj,
1720                    $lock,
1721                    $slab_size,
1722                    ALLOCATOR,
1723                    $track_obj_count
1724                );
1725
1726                let max_allocs =
1727                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::ALLOCS_PER_SLAB
1728                        * $max_slabs;
1729
1730                run_static_unique_test::<Obj, $lock, $slab_size, $track_obj_count>(
1731                    &ALLOCATED_OBJ_COUNT,
1732                    &ALLOCATOR,
1733                    $max_slabs,
1734                    1,
1735                );
1736                run_static_unique_test::<Obj, $lock, $slab_size, $track_obj_count>(
1737                    &ALLOCATED_OBJ_COUNT,
1738                    &ALLOCATOR,
1739                    $max_slabs,
1740                    max_allocs / 2,
1741                );
1742                run_static_unique_test::<Obj, $lock, $slab_size, $track_obj_count>(
1743                    &ALLOCATED_OBJ_COUNT,
1744                    &ALLOCATOR,
1745                    $max_slabs,
1746                    max_allocs + 4,
1747                );
1748            }
1749        };
1750    }
1751
1752    macro_rules! define_static_ref_test {
1753        ($name:ident, $lock:ty, $lock_init:expr, $slab_size:expr, $track_obj_count:expr, $max_slabs:expr) => {
1754            #[test]
1755            fn $name() {
1756                static ALLOCATED_OBJ_COUNT: AtomicUsize = AtomicUsize::new(0);
1757                #[crate::ref_counted]
1758                #[repr(C)]
1759                struct Obj {
1760                    ctype: ConstructType,
1761                    _payload: [u8; 13],
1762                }
1763                impl TestConstructors for Obj {
1764                    fn new_default() -> Self {
1765                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1766                        Self {
1767                            ref_count: RefCounted::new(),
1768                            __fbl_ref_counted_guard: (),
1769                            ctype: ConstructType::Default,
1770                            _payload: [0; 13],
1771                        }
1772                    }
1773                    fn new_lvalue(_val: usize) -> Self {
1774                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1775                        Self {
1776                            ref_count: RefCounted::new(),
1777                            __fbl_ref_counted_guard: (),
1778                            ctype: ConstructType::LvalueRef,
1779                            _payload: [0; 13],
1780                        }
1781                    }
1782                    fn new_rvalue(_val: usize) -> Self {
1783                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1784                        Self {
1785                            ref_count: RefCounted::new(),
1786                            __fbl_ref_counted_guard: (),
1787                            ctype: ConstructType::RvalueRef,
1788                            _payload: [0; 13],
1789                        }
1790                    }
1791                    fn new_l_then_r(_a: usize, _b: usize) -> Self {
1792                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1793                        Self {
1794                            ref_count: RefCounted::new(),
1795                            __fbl_ref_counted_guard: (),
1796                            ctype: ConstructType::LThenRRef,
1797                            _payload: [0; 13],
1798                        }
1799                    }
1800                    fn ctype(&self) -> ConstructType {
1801                        self.ctype
1802                    }
1803                }
1804                impl Drop for Obj {
1805                    fn drop(&mut self) {
1806                        ALLOCATED_OBJ_COUNT.fetch_sub(1, Ordering::SeqCst);
1807                    }
1808                }
1809
1810                static ALLOCATOR: SlabAllocator<Obj, $lock, $slab_size, $track_obj_count> =
1811                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::const_new(
1812                        $max_slabs, $lock_init,
1813                    );
1814
1815                crate::impl_static_slab_allocatable!(
1816                    Obj,
1817                    $lock,
1818                    $slab_size,
1819                    ALLOCATOR,
1820                    $track_obj_count
1821                );
1822
1823                let max_allocs =
1824                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::ALLOCS_PER_SLAB
1825                        * $max_slabs;
1826
1827                // Test make_ref_counted! macro works with static allocator
1828                {
1829                    ALLOCATED_OBJ_COUNT.store(1, Ordering::SeqCst);
1830                    let obj = crate::make_ref_counted!(Obj {
1831                        ctype: ConstructType::Default,
1832                        _payload: [0; 13],
1833                    })
1834                    .unwrap();
1835                    assert_eq!(ALLOCATED_OBJ_COUNT.load(Ordering::SeqCst), 1);
1836                    drop(obj);
1837                    assert_eq!(ALLOCATED_OBJ_COUNT.load(Ordering::SeqCst), 0);
1838                }
1839
1840                run_static_ref_test::<Obj, $lock, $slab_size, $track_obj_count>(
1841                    &ALLOCATED_OBJ_COUNT,
1842                    &ALLOCATOR,
1843                    $max_slabs,
1844                    1,
1845                );
1846                run_static_ref_test::<Obj, $lock, $slab_size, $track_obj_count>(
1847                    &ALLOCATED_OBJ_COUNT,
1848                    &ALLOCATOR,
1849                    $max_slabs,
1850                    max_allocs / 2,
1851                );
1852                run_static_ref_test::<Obj, $lock, $slab_size, $track_obj_count>(
1853                    &ALLOCATED_OBJ_COUNT,
1854                    &ALLOCATOR,
1855                    $max_slabs,
1856                    max_allocs + 4,
1857                );
1858            }
1859        };
1860    }
1861
1862    macro_rules! define_static_unmanaged_test {
1863        ($name:ident, $lock:ty, $lock_init:expr, $slab_size:expr, $track_obj_count:expr, $max_slabs:expr) => {
1864            #[test]
1865            fn $name() {
1866                static ALLOCATED_OBJ_COUNT: AtomicUsize = AtomicUsize::new(0);
1867                struct Obj {
1868                    ctype: ConstructType,
1869                    _payload: [u8; 13],
1870                }
1871                impl TestConstructors for Obj {
1872                    fn new_default() -> Self {
1873                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1874                        Self { ctype: ConstructType::Default, _payload: [0; 13] }
1875                    }
1876                    fn new_lvalue(_val: usize) -> Self {
1877                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1878                        Self { ctype: ConstructType::LvalueRef, _payload: [0; 13] }
1879                    }
1880                    fn new_rvalue(_val: usize) -> Self {
1881                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1882                        Self { ctype: ConstructType::RvalueRef, _payload: [0; 13] }
1883                    }
1884                    fn new_l_then_r(_a: usize, _b: usize) -> Self {
1885                        ALLOCATED_OBJ_COUNT.fetch_add(1, Ordering::SeqCst);
1886                        Self { ctype: ConstructType::LThenRRef, _payload: [0; 13] }
1887                    }
1888                    fn ctype(&self) -> ConstructType {
1889                        self.ctype
1890                    }
1891                }
1892                impl Drop for Obj {
1893                    fn drop(&mut self) {
1894                        ALLOCATED_OBJ_COUNT.fetch_sub(1, Ordering::SeqCst);
1895                    }
1896                }
1897
1898                static ALLOCATOR: SlabAllocator<Obj, $lock, $slab_size, $track_obj_count> =
1899                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::const_new(
1900                        $max_slabs, $lock_init,
1901                    );
1902
1903                let max_allocs =
1904                    SlabAllocator::<Obj, $lock, $slab_size, $track_obj_count>::ALLOCS_PER_SLAB
1905                        * $max_slabs;
1906
1907                run_static_unmanaged_test::<Obj, $lock, $slab_size, $track_obj_count>(
1908                    &ALLOCATED_OBJ_COUNT,
1909                    &ALLOCATOR,
1910                    $max_slabs,
1911                    1,
1912                );
1913                run_static_unmanaged_test::<Obj, $lock, $slab_size, $track_obj_count>(
1914                    &ALLOCATED_OBJ_COUNT,
1915                    &ALLOCATOR,
1916                    $max_slabs,
1917                    max_allocs / 2,
1918                );
1919                run_static_unmanaged_test::<Obj, $lock, $slab_size, $track_obj_count>(
1920                    &ALLOCATED_OBJ_COUNT,
1921                    &ALLOCATOR,
1922                    $max_slabs,
1923                    max_allocs + 4,
1924                );
1925            }
1926        };
1927    }
1928
1929    define_unmanaged_test!(unmanaged_single_slab_mutex, RawMutex, RawMutex::INIT, 1024, false, 1);
1930    define_unmanaged_test!(unmanaged_multi_slab_mutex, RawMutex, RawMutex::INIT, 1024, false, 4);
1931    define_instanced_unique_test!(
1932        unique_ptr_single_slab_mutex,
1933        RawMutex,
1934        RawMutex::INIT,
1935        1024,
1936        false,
1937        1
1938    );
1939    define_instanced_unique_test!(
1940        unique_ptr_multi_slab_mutex,
1941        RawMutex,
1942        RawMutex::INIT,
1943        1024,
1944        false,
1945        4
1946    );
1947    define_instanced_ref_test!(ref_ptr_single_slab_mutex, RawMutex, RawMutex::INIT, 1024, false, 1);
1948    define_instanced_ref_test!(ref_ptr_multi_slab_mutex, RawMutex, RawMutex::INIT, 1024, false, 4);
1949
1950    // Counted versions
1951    define_unmanaged_test!(
1952        counted_unmanaged_single_slab_mutex,
1953        RawMutex,
1954        RawMutex::INIT,
1955        1024,
1956        true,
1957        1
1958    );
1959    define_unmanaged_test!(
1960        counted_unmanaged_multi_slab_mutex,
1961        RawMutex,
1962        RawMutex::INIT,
1963        1024,
1964        true,
1965        4
1966    );
1967    define_instanced_unique_test!(
1968        counted_unique_ptr_single_slab_mutex,
1969        RawMutex,
1970        RawMutex::INIT,
1971        1024,
1972        true,
1973        1
1974    );
1975    define_instanced_unique_test!(
1976        counted_unique_ptr_multi_slab_mutex,
1977        RawMutex,
1978        RawMutex::INIT,
1979        1024,
1980        true,
1981        4
1982    );
1983    define_instanced_ref_test!(
1984        counted_ref_ptr_single_slab_mutex,
1985        RawMutex,
1986        RawMutex::INIT,
1987        1024,
1988        true,
1989        1
1990    );
1991    define_instanced_ref_test!(
1992        counted_ref_ptr_multi_slab_mutex,
1993        RawMutex,
1994        RawMutex::INIT,
1995        1024,
1996        true,
1997        4
1998    );
1999
2000    // Static versions
2001    define_static_unmanaged_test!(static_unmanaged_mutex, RawMutex, RawMutex::INIT, 1024, false, 4);
2002    define_static_unique_test!(static_unique_ptr_mutex, RawMutex, RawMutex::INIT, 1024, false, 4);
2003    define_static_ref_test!(static_ref_ptr_mutex, RawMutex, RawMutex::INIT, 1024, false, 4);
2004
2005    // Counted Static versions
2006    define_static_unmanaged_test!(
2007        counted_static_unmanaged_mutex,
2008        RawMutex,
2009        RawMutex::INIT,
2010        1024,
2011        true,
2012        4
2013    );
2014    define_static_unique_test!(
2015        counted_static_unique_ptr_mutex,
2016        RawMutex,
2017        RawMutex::INIT,
2018        1024,
2019        true,
2020        4
2021    );
2022    define_static_ref_test!(counted_static_ref_ptr_mutex, RawMutex, RawMutex::INIT, 1024, true, 4);
2023
2024    #[test]
2025    fn test_constructor_statistics_fix() {
2026        // With TRACK_OBJECT_COUNT = true, and preallocate.
2027        // The obj_count and max_obj_count should remain exactly 0 after construction and preallocation.
2028        let init = SlabAllocator::<TestObjectDummy, RawMutex, 1024, true>::init(1);
2029        stack_pin_init!(let allocator = init);
2030        allocator.preallocate().unwrap();
2031        assert_eq!(allocator.obj_count(), 0);
2032        assert_eq!(allocator.max_obj_count(), 0);
2033    }
2034
2035    #[cfg(debug_assertions)]
2036    #[test]
2037    #[should_panic(expected = "SlabAllocator destroyed with outstanding allocations!")]
2038    fn test_leak_detector_counted() {
2039        let init = SlabAllocator::<u32, RawMutex, 1024, true>::init(1);
2040        stack_pin_init!(let allocator = init);
2041        let _ptr = allocator.alloc_raw().unwrap();
2042    }
2043
2044    #[cfg(debug_assertions)]
2045    #[test]
2046    #[should_panic(expected = "SlabAllocator destroyed with outstanding allocations!")]
2047    fn test_leak_detector_uncounted() {
2048        let init = SlabAllocator::<u32, RawMutex, 1024, false>::init(1);
2049        stack_pin_init!(let allocator = init);
2050        let _ptr = allocator.alloc_raw().unwrap();
2051    }
2052
2053    struct TestObjectDummy {
2054        _val: u32,
2055        slab_origin: SlabOrigin<TestObjectDummy, RawMutex, 1024, true>,
2056    }
2057    crate::impl_instanced_slab_allocatable!(TestObjectDummy, RawMutex, 1024, true);
2058}