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