Skip to main content

optirs_gpu/memory/allocation/
slab_allocator.rs

1// Slab allocator for GPU memory management
2//
3// This module implements a slab allocator optimized for fixed-size allocations.
4// Slab allocation is highly efficient for objects of the same size and provides
5// excellent cache locality and minimal fragmentation.
6
7use std::collections::{HashMap, VecDeque};
8use std::ptr::NonNull;
9use std::sync::{Arc, Mutex};
10use std::time::Instant;
11
12/// Slab allocator for fixed-size objects
13pub struct SlabAllocator {
14    /// Cache configurations indexed by object size
15    caches: HashMap<usize, SlabCache>,
16    /// Configuration
17    config: SlabConfig,
18    /// Memory pool for backing slabs
19    memory_pool: MemoryPool,
20}
21
22/// Slab cache for objects of a specific size
23pub struct SlabCache {
24    /// Object size for this cache
25    object_size: usize,
26    /// List of slabs (pages)
27    slabs: Vec<Slab>,
28    /// Partially filled slabs
29    partial_slabs: VecDeque<usize>,
30    /// Full slabs
31    full_slabs: Vec<usize>,
32    /// Empty slabs
33    empty_slabs: VecDeque<usize>,
34    /// Cache statistics
35    stats: CacheStats,
36    /// Cache configuration
37    config: CacheConfig,
38}
39
40/// Individual slab (page) containing multiple objects
41pub struct Slab {
42    /// Base address of the slab
43    base_ptr: NonNull<u8>,
44    /// Size of the slab in bytes
45    slab_size: usize,
46    /// Object size
47    object_size: usize,
48    /// Number of objects in this slab
49    object_count: usize,
50    /// Free object list (indices)
51    free_objects: VecDeque<usize>,
52    /// Allocated object count
53    allocated_count: usize,
54    /// Allocation bitmap for fast lookups
55    allocation_bitmap: Vec<u64>,
56    /// Slab creation time
57    created_at: Instant,
58    /// Last allocation time
59    last_alloc: Option<Instant>,
60    /// Last deallocation time
61    last_dealloc: Option<Instant>,
62    /// Access frequency counter
63    access_count: u64,
64}
65
66impl Slab {
67    pub fn new(base_ptr: NonNull<u8>, slab_size: usize, object_size: usize) -> Self {
68        let object_count = slab_size / object_size;
69        let bitmap_size = object_count.div_ceil(64); // Round up to nearest 64-bit word
70
71        let mut free_objects = VecDeque::with_capacity(object_count);
72        for i in 0..object_count {
73            free_objects.push_back(i);
74        }
75
76        Self {
77            base_ptr,
78            slab_size,
79            object_size,
80            object_count,
81            free_objects,
82            allocated_count: 0,
83            allocation_bitmap: vec![0; bitmap_size],
84            created_at: Instant::now(),
85            last_alloc: None,
86            last_dealloc: None,
87            access_count: 0,
88        }
89    }
90
91    /// Allocate an object from this slab
92    pub fn allocate(&mut self) -> Option<NonNull<u8>> {
93        if let Some(object_index) = self.free_objects.pop_front() {
94            // Mark object as allocated in bitmap
95            let word_index = object_index / 64;
96            let bit_index = object_index % 64;
97            self.allocation_bitmap[word_index] |= 1u64 << bit_index;
98
99            self.allocated_count += 1;
100            self.last_alloc = Some(Instant::now());
101            self.access_count += 1;
102
103            // Calculate object address
104            let object_offset = object_index * self.object_size;
105            let object_ptr =
106                unsafe { NonNull::new_unchecked(self.base_ptr.as_ptr().add(object_offset)) };
107
108            Some(object_ptr)
109        } else {
110            None
111        }
112    }
113
114    /// Deallocate an object in this slab
115    pub fn deallocate(&mut self, ptr: NonNull<u8>) -> Result<(), SlabError> {
116        // Calculate object index from pointer
117        let ptr_addr = ptr.as_ptr() as usize;
118        let base_addr = self.base_ptr.as_ptr() as usize;
119
120        if ptr_addr < base_addr || ptr_addr >= base_addr + self.slab_size {
121            return Err(SlabError::InvalidPointer(
122                "Pointer not in this slab".to_string(),
123            ));
124        }
125
126        let offset = ptr_addr - base_addr;
127        if !offset.is_multiple_of(self.object_size) {
128            return Err(SlabError::InvalidPointer(
129                "Pointer not aligned to object boundary".to_string(),
130            ));
131        }
132
133        let object_index = offset / self.object_size;
134        if object_index >= self.object_count {
135            return Err(SlabError::InvalidPointer(
136                "Object index out of bounds".to_string(),
137            ));
138        }
139
140        // Check if object is actually allocated
141        let word_index = object_index / 64;
142        let bit_index = object_index % 64;
143        if (self.allocation_bitmap[word_index] & (1u64 << bit_index)) == 0 {
144            return Err(SlabError::DoubleFree("Object already free".to_string()));
145        }
146
147        // Mark as free
148        self.allocation_bitmap[word_index] &= !(1u64 << bit_index);
149        self.free_objects.push_back(object_index);
150        self.allocated_count -= 1;
151        self.last_dealloc = Some(Instant::now());
152
153        Ok(())
154    }
155
156    /// Check if slab is full
157    pub fn is_full(&self) -> bool {
158        self.allocated_count == self.object_count
159    }
160
161    /// Check if slab is empty
162    pub fn is_empty(&self) -> bool {
163        self.allocated_count == 0
164    }
165
166    /// Check if slab is partially filled
167    pub fn is_partial(&self) -> bool {
168        self.allocated_count > 0 && self.allocated_count < self.object_count
169    }
170
171    /// Get utilization ratio (0.0 to 1.0)
172    pub fn get_utilization(&self) -> f64 {
173        self.allocated_count as f64 / self.object_count as f64
174    }
175
176    /// Get slab statistics
177    pub fn get_stats(&self) -> SlabStats {
178        SlabStats {
179            total_objects: self.object_count,
180            allocated_objects: self.allocated_count,
181            free_objects: self.object_count - self.allocated_count,
182            utilization: self.get_utilization(),
183            access_count: self.access_count,
184            age: self.created_at.elapsed(),
185        }
186    }
187}
188
189/// Memory pool for backing slab storage
190pub struct MemoryPool {
191    /// Base address of the memory pool
192    base_ptr: NonNull<u8>,
193    /// Total size of the memory pool
194    total_size: usize,
195    /// Current allocation offset
196    current_offset: usize,
197    /// Free regions for reuse
198    free_regions: VecDeque<FreeRegion>,
199    /// Allocation alignment
200    alignment: usize,
201}
202
203/// Free memory region
204#[derive(Debug, Clone)]
205pub struct FreeRegion {
206    pub offset: usize,
207    pub size: usize,
208    pub freed_at: Instant,
209}
210
211impl MemoryPool {
212    pub fn new(base_ptr: NonNull<u8>, total_size: usize, alignment: usize) -> Self {
213        Self {
214            base_ptr,
215            total_size,
216            current_offset: 0,
217            free_regions: VecDeque::new(),
218            alignment,
219        }
220    }
221
222    /// Allocate a slab from the memory pool
223    pub fn allocate_slab(&mut self, size: usize) -> Option<NonNull<u8>> {
224        let aligned_size = (size + self.alignment - 1) & !(self.alignment - 1);
225
226        // Try to reuse a free region first
227        if let Some(region_index) = self.find_suitable_free_region(aligned_size) {
228            // `region_index` was just found in this exact deque with no
229            // mutation in between, so removal is guaranteed to succeed;
230            // `?` reports allocation failure defensively rather than
231            // panicking if that invariant were ever violated.
232            let region = self.free_regions.remove(region_index)?;
233            let ptr = unsafe { NonNull::new_unchecked(self.base_ptr.as_ptr().add(region.offset)) };
234
235            // If region is larger than needed, split it
236            if region.size > aligned_size {
237                let remaining_region = FreeRegion {
238                    offset: region.offset + aligned_size,
239                    size: region.size - aligned_size,
240                    freed_at: region.freed_at,
241                };
242                self.free_regions.push_back(remaining_region);
243            }
244
245            return Some(ptr);
246        }
247
248        // Allocate from the end of the pool
249        if self.current_offset + aligned_size <= self.total_size {
250            let ptr =
251                unsafe { NonNull::new_unchecked(self.base_ptr.as_ptr().add(self.current_offset)) };
252            self.current_offset += aligned_size;
253            Some(ptr)
254        } else {
255            None
256        }
257    }
258
259    /// Free a slab back to the memory pool
260    pub fn free_slab(&mut self, ptr: NonNull<u8>, size: usize) {
261        let base_addr = self.base_ptr.as_ptr() as usize;
262        let ptr_addr = ptr.as_ptr() as usize;
263
264        if ptr_addr >= base_addr && ptr_addr < base_addr + self.total_size {
265            let offset = ptr_addr - base_addr;
266            let region = FreeRegion {
267                offset,
268                size,
269                freed_at: Instant::now(),
270            };
271
272            // Insert in sorted order to facilitate coalescing
273            let insert_pos = self
274                .free_regions
275                .binary_search_by_key(&offset, |r| r.offset)
276                .unwrap_or_else(|pos| pos);
277
278            self.free_regions.insert(insert_pos, region);
279
280            // Try to coalesce adjacent regions
281            self.coalesce_free_regions();
282        }
283    }
284
285    fn find_suitable_free_region(&self, size: usize) -> Option<usize> {
286        self.free_regions
287            .iter()
288            .position(|region| region.size >= size)
289    }
290
291    fn coalesce_free_regions(&mut self) {
292        let mut i = 0;
293        while i < self.free_regions.len().saturating_sub(1) {
294            let current_end = self.free_regions[i].offset + self.free_regions[i].size;
295            if current_end == self.free_regions[i + 1].offset {
296                // Coalesce regions. The while condition just proved
297                // `i + 1 < self.free_regions.len()`, so removal is
298                // guaranteed to succeed; the `else` stops coalescing
299                // defensively rather than panicking if that invariant were
300                // ever violated.
301                let Some(next_region) = self.free_regions.remove(i + 1) else {
302                    break;
303                };
304                self.free_regions[i].size += next_region.size;
305            } else {
306                i += 1;
307            }
308        }
309    }
310
311    pub fn get_usage(&self) -> MemoryPoolUsage {
312        let free_size = self.free_regions.iter().map(|r| r.size).sum::<usize>();
313        let allocated_size = self.current_offset - free_size;
314
315        MemoryPoolUsage {
316            total_size: self.total_size,
317            allocated_size,
318            free_size,
319            current_offset: self.current_offset,
320            free_regions: self.free_regions.len(),
321        }
322    }
323}
324
325/// Cache configuration
326#[derive(Debug, Clone)]
327pub struct CacheConfig {
328    /// Objects per slab
329    pub objects_per_slab: usize,
330    /// Maximum number of empty slabs to keep
331    pub max_empty_slabs: usize,
332    /// Enable slab coloring for cache performance
333    pub enable_coloring: bool,
334    /// Color offset for cache line alignment
335    pub color_offset: usize,
336    /// Enable object construction/destruction
337    pub enable_ctor_dtor: bool,
338    /// Object constructor function
339    pub constructor: Option<fn(*mut u8)>,
340    /// Object destructor function  
341    pub destructor: Option<fn(*mut u8)>,
342}
343
344impl Default for CacheConfig {
345    fn default() -> Self {
346        Self {
347            objects_per_slab: 64,
348            max_empty_slabs: 3,
349            enable_coloring: true,
350            color_offset: 0,
351            enable_ctor_dtor: false,
352            constructor: None,
353            destructor: None,
354        }
355    }
356}
357
358/// Slab allocator configuration
359#[derive(Debug, Clone)]
360pub struct SlabConfig {
361    /// Default slab size
362    pub default_slab_size: usize,
363    /// Memory alignment requirement
364    pub alignment: usize,
365    /// Enable statistics collection
366    pub enable_stats: bool,
367    /// Enable debugging features
368    pub enable_debug: bool,
369    /// Memory reclamation threshold
370    pub reclaim_threshold: f64,
371    /// Enable automatic reclamation
372    pub auto_reclaim: bool,
373}
374
375impl Default for SlabConfig {
376    fn default() -> Self {
377        Self {
378            default_slab_size: 4096, // 4KB page size
379            alignment: 256,
380            enable_stats: true,
381            enable_debug: false,
382            reclaim_threshold: 0.8,
383            auto_reclaim: true,
384        }
385    }
386}
387
388/// Cache statistics
389#[derive(Debug, Clone, Default)]
390pub struct CacheStats {
391    pub total_allocations: u64,
392    pub total_deallocations: u64,
393    pub cache_hits: u64,
394    pub cache_misses: u64,
395    pub slab_allocations: u64,
396    pub slab_deallocations: u64,
397    pub objects_allocated: u64,
398    pub objects_free: u64,
399    pub average_utilization: f64,
400}
401
402/// Slab statistics
403#[derive(Debug, Clone, Default)]
404pub struct SlabStats {
405    pub total_objects: usize,
406    pub allocated_objects: usize,
407    pub free_objects: usize,
408    pub utilization: f64,
409    pub access_count: u64,
410    pub age: std::time::Duration,
411}
412
413/// Memory pool usage statistics
414#[derive(Debug, Clone)]
415pub struct MemoryPoolUsage {
416    pub total_size: usize,
417    pub allocated_size: usize,
418    pub free_size: usize,
419    pub current_offset: usize,
420    pub free_regions: usize,
421}
422
423impl SlabCache {
424    pub fn new(object_size: usize, config: CacheConfig) -> Self {
425        Self {
426            object_size,
427            slabs: Vec::new(),
428            partial_slabs: VecDeque::new(),
429            full_slabs: Vec::new(),
430            empty_slabs: VecDeque::new(),
431            stats: CacheStats::default(),
432            config,
433        }
434    }
435
436    /// Allocate an object from this cache
437    pub fn allocate(&mut self, memory_pool: &mut MemoryPool) -> Result<NonNull<u8>, SlabError> {
438        self.stats.total_allocations += 1;
439
440        // Try partial slabs first
441        if let Some(&slab_index) = self.partial_slabs.front() {
442            if let Some(ptr) = self.slabs[slab_index].allocate() {
443                self.stats.cache_hits += 1;
444                self.stats.objects_allocated += 1;
445
446                // Move to full slabs if now full
447                if self.slabs[slab_index].is_full() {
448                    self.partial_slabs.pop_front();
449                    self.full_slabs.push(slab_index);
450                }
451
452                // Apply constructor if enabled
453                if self.config.enable_ctor_dtor {
454                    if let Some(ctor) = self.config.constructor {
455                        ctor(ptr.as_ptr());
456                    }
457                }
458
459                return Ok(ptr);
460            }
461        }
462
463        // Try empty slabs
464        if let Some(slab_index) = self.empty_slabs.pop_front() {
465            if let Some(ptr) = self.slabs[slab_index].allocate() {
466                self.stats.cache_hits += 1;
467                self.stats.objects_allocated += 1;
468                self.partial_slabs.push_back(slab_index);
469
470                if self.config.enable_ctor_dtor {
471                    if let Some(ctor) = self.config.constructor {
472                        ctor(ptr.as_ptr());
473                    }
474                }
475
476                return Ok(ptr);
477            }
478        }
479
480        // Need to allocate a new slab
481        self.stats.cache_misses += 1;
482        self.allocate_new_slab(memory_pool)?;
483
484        // Try allocation again with new slab
485        if let Some(&slab_index) = self.partial_slabs.back() {
486            if let Some(ptr) = self.slabs[slab_index].allocate() {
487                self.stats.objects_allocated += 1;
488
489                if self.config.enable_ctor_dtor {
490                    if let Some(ctor) = self.config.constructor {
491                        ctor(ptr.as_ptr());
492                    }
493                }
494
495                return Ok(ptr);
496            }
497        }
498
499        Err(SlabError::OutOfMemory(
500            "Failed to allocate after creating new slab".to_string(),
501        ))
502    }
503
504    /// Deallocate an object back to this cache
505    pub fn deallocate(&mut self, ptr: NonNull<u8>) -> Result<(), SlabError> {
506        // Apply destructor if enabled
507        if self.config.enable_ctor_dtor {
508            if let Some(dtor) = self.config.destructor {
509                dtor(ptr.as_ptr());
510            }
511        }
512
513        // Find which slab contains this pointer
514        let mut slab_index = None;
515        for (i, slab) in self.slabs.iter().enumerate() {
516            let base_addr = slab.base_ptr.as_ptr() as usize;
517            let ptr_addr = ptr.as_ptr() as usize;
518
519            if ptr_addr >= base_addr && ptr_addr < base_addr + slab.slab_size {
520                slab_index = Some(i);
521                break;
522            }
523        }
524
525        let slab_index = slab_index.ok_or_else(|| {
526            SlabError::InvalidPointer("Pointer not found in any slab".to_string())
527        })?;
528
529        let was_full = self.slabs[slab_index].is_full();
530        self.slabs[slab_index].deallocate(ptr)?;
531
532        self.stats.total_deallocations += 1;
533        self.stats.objects_allocated -= 1;
534        self.stats.objects_free += 1;
535
536        // Update slab lists based on new state
537        if was_full {
538            // Remove from full slabs, add to partial
539            if let Some(pos) = self.full_slabs.iter().position(|&i| i == slab_index) {
540                self.full_slabs.remove(pos);
541                self.partial_slabs.push_back(slab_index);
542            }
543        } else if self.slabs[slab_index].is_empty() {
544            // Remove from partial, add to empty
545            if let Some(pos) = self.partial_slabs.iter().position(|&i| i == slab_index) {
546                self.partial_slabs.remove(pos);
547                self.empty_slabs.push_back(slab_index);
548            }
549        }
550
551        Ok(())
552    }
553
554    fn allocate_new_slab(&mut self, memory_pool: &mut MemoryPool) -> Result<(), SlabError> {
555        let slab_size = self.calculate_slab_size();
556
557        let slab_ptr = memory_pool.allocate_slab(slab_size).ok_or_else(|| {
558            SlabError::OutOfMemory("Cannot allocate slab from memory pool".to_string())
559        })?;
560
561        let slab = Slab::new(slab_ptr, slab_size, self.object_size);
562        let slab_index = self.slabs.len();
563
564        self.slabs.push(slab);
565        self.partial_slabs.push_back(slab_index);
566        self.stats.slab_allocations += 1;
567
568        Ok(())
569    }
570
571    fn calculate_slab_size(&self) -> usize {
572        // Calculate optimal slab size based on object size and configuration
573        let objects_per_slab = self.config.objects_per_slab;
574        let base_size = objects_per_slab * self.object_size;
575
576        // Add coloring offset if enabled
577        if self.config.enable_coloring {
578            base_size + self.config.color_offset
579        } else {
580            base_size
581        }
582    }
583
584    /// Get cache statistics
585    pub fn get_stats(&self) -> &CacheStats {
586        &self.stats
587    }
588
589    /// Get detailed cache information
590    pub fn get_cache_info(&self) -> CacheInfo {
591        let total_objects = self.slabs.iter().map(|s| s.object_count).sum();
592        let allocated_objects = self.slabs.iter().map(|s| s.allocated_count).sum();
593        let average_utilization = if total_objects > 0 {
594            allocated_objects as f64 / total_objects as f64
595        } else {
596            0.0
597        };
598
599        CacheInfo {
600            object_size: self.object_size,
601            total_slabs: self.slabs.len(),
602            partial_slabs: self.partial_slabs.len(),
603            full_slabs: self.full_slabs.len(),
604            empty_slabs: self.empty_slabs.len(),
605            total_objects,
606            allocated_objects,
607            free_objects: total_objects - allocated_objects,
608            average_utilization,
609            memory_overhead: self.calculate_memory_overhead(),
610        }
611    }
612
613    fn calculate_memory_overhead(&self) -> f64 {
614        let useful_memory: usize = self
615            .slabs
616            .iter()
617            .map(|s| s.allocated_count * s.object_size)
618            .sum();
619
620        let total_memory: usize = self.slabs.iter().map(|s| s.slab_size).sum();
621
622        if total_memory > 0 {
623            1.0 - (useful_memory as f64 / total_memory as f64)
624        } else {
625            0.0
626        }
627    }
628
629    /// Reclaim empty slabs
630    pub fn reclaim_empty_slabs(&mut self, memory_pool: &mut MemoryPool) -> usize {
631        let mut reclaimed = 0;
632        let keep_count = self.config.max_empty_slabs;
633
634        while self.empty_slabs.len() > keep_count {
635            if let Some(slab_index) = self.empty_slabs.pop_front() {
636                let slab = &self.slabs[slab_index];
637                memory_pool.free_slab(slab.base_ptr, slab.slab_size);
638                reclaimed += 1;
639                self.stats.slab_deallocations += 1;
640            }
641        }
642
643        reclaimed
644    }
645}
646
647/// Cache information
648#[derive(Debug, Clone)]
649pub struct CacheInfo {
650    pub object_size: usize,
651    pub total_slabs: usize,
652    pub partial_slabs: usize,
653    pub full_slabs: usize,
654    pub empty_slabs: usize,
655    pub total_objects: usize,
656    pub allocated_objects: usize,
657    pub free_objects: usize,
658    pub average_utilization: f64,
659    pub memory_overhead: f64,
660}
661
662impl SlabAllocator {
663    pub fn new(base_ptr: NonNull<u8>, total_size: usize, config: SlabConfig) -> Self {
664        let memory_pool = MemoryPool::new(base_ptr, total_size, config.alignment);
665
666        Self {
667            caches: HashMap::new(),
668            memory_pool,
669            config,
670        }
671    }
672
673    /// Allocate object of specified size
674    pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, SlabError> {
675        if size == 0 {
676            return Err(SlabError::InvalidSize(
677                "Cannot allocate zero bytes".to_string(),
678            ));
679        }
680
681        // Round up size to alignment boundary
682        let aligned_size = (size + self.config.alignment - 1) & !(self.config.alignment - 1);
683
684        // Get or create cache for this size. `entry().or_insert_with()`
685        // already hands back the `&mut SlabCache` for the (possibly
686        // freshly-inserted) entry, so no separate `get_mut` + expect is
687        // needed to prove the key is present.
688        let cache = self
689            .caches
690            .entry(aligned_size)
691            .or_insert_with(|| SlabCache::new(aligned_size, CacheConfig::default()));
692        cache.allocate(&mut self.memory_pool)
693    }
694
695    /// Deallocate object
696    pub fn deallocate(&mut self, ptr: NonNull<u8>, size: usize) -> Result<(), SlabError> {
697        let aligned_size = (size + self.config.alignment - 1) & !(self.config.alignment - 1);
698
699        let cache = self
700            .caches
701            .get_mut(&aligned_size)
702            .ok_or_else(|| SlabError::InvalidPointer("No cache found for this size".to_string()))?;
703
704        cache.deallocate(ptr)
705    }
706
707    /// Get allocator statistics
708    pub fn get_stats(&self) -> SlabAllocatorStats {
709        let mut total_caches = 0;
710        let mut total_slabs = 0;
711        let mut total_objects = 0;
712        let mut allocated_objects = 0;
713        let mut total_allocations = 0;
714        let mut total_deallocations = 0;
715
716        for cache in self.caches.values() {
717            total_caches += 1;
718            let info = cache.get_cache_info();
719            total_slabs += info.total_slabs;
720            total_objects += info.total_objects;
721            allocated_objects += info.allocated_objects;
722
723            let stats = cache.get_stats();
724            total_allocations += stats.total_allocations;
725            total_deallocations += stats.total_deallocations;
726        }
727
728        let memory_usage = self.memory_pool.get_usage();
729
730        SlabAllocatorStats {
731            total_caches,
732            total_slabs,
733            total_objects,
734            allocated_objects,
735            free_objects: total_objects - allocated_objects,
736            total_allocations,
737            total_deallocations,
738            memory_usage,
739            cache_efficiency: if total_allocations > 0 {
740                allocated_objects as f64 / total_allocations as f64
741            } else {
742                0.0
743            },
744        }
745    }
746
747    /// Get information about all caches
748    pub fn get_all_cache_info(&self) -> Vec<(usize, CacheInfo)> {
749        self.caches
750            .iter()
751            .map(|(&size, cache)| (size, cache.get_cache_info()))
752            .collect()
753    }
754
755    /// Reclaim memory from empty slabs
756    pub fn reclaim_memory(&mut self) -> usize {
757        let mut total_reclaimed = 0;
758
759        for cache in self.caches.values_mut() {
760            total_reclaimed += cache.reclaim_empty_slabs(&mut self.memory_pool);
761        }
762
763        total_reclaimed
764    }
765
766    /// Destroy cache for specific size
767    pub fn destroy_cache(&mut self, size: usize) -> Result<(), SlabError> {
768        let aligned_size = (size + self.config.alignment - 1) & !(self.config.alignment - 1);
769
770        if let Some(mut cache) = self.caches.remove(&aligned_size) {
771            // Reclaim all slabs from this cache
772            cache.reclaim_empty_slabs(&mut self.memory_pool);
773            Ok(())
774        } else {
775            Err(SlabError::InvalidSize("Cache not found".to_string()))
776        }
777    }
778
779    /// Get memory pool usage
780    pub fn get_memory_usage(&self) -> MemoryPoolUsage {
781        self.memory_pool.get_usage()
782    }
783}
784
785// Safety: SlabAllocator manages GPU memory pointers via NonNull<u8>. While NonNull is not Send/Sync by default,
786// it's safe to share SlabAllocator across threads when protected by Arc<Mutex<>> because:
787// 1. The pointers point to GPU memory managed by the GPU driver
788// 2. The Mutex provides exclusive access for all mutable operations
789// 3. No thread-local state is maintained
790unsafe impl Send for SlabAllocator {}
791unsafe impl Sync for SlabAllocator {}
792
793/// Slab allocator statistics
794#[derive(Debug, Clone)]
795pub struct SlabAllocatorStats {
796    pub total_caches: usize,
797    pub total_slabs: usize,
798    pub total_objects: usize,
799    pub allocated_objects: usize,
800    pub free_objects: usize,
801    pub total_allocations: u64,
802    pub total_deallocations: u64,
803    pub memory_usage: MemoryPoolUsage,
804    pub cache_efficiency: f64,
805}
806
807/// Slab allocator errors
808#[derive(Debug, Clone)]
809pub enum SlabError {
810    InvalidSize(String),
811    OutOfMemory(String),
812    InvalidPointer(String),
813    DoubleFree(String),
814    CorruptedSlab(String),
815}
816
817impl std::fmt::Display for SlabError {
818    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
819        match self {
820            SlabError::InvalidSize(msg) => write!(f, "Invalid size: {}", msg),
821            SlabError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
822            SlabError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
823            SlabError::DoubleFree(msg) => write!(f, "Double free: {}", msg),
824            SlabError::CorruptedSlab(msg) => write!(f, "Corrupted slab: {}", msg),
825        }
826    }
827}
828
829impl std::error::Error for SlabError {}
830
831/// Thread-safe slab allocator wrapper
832pub struct ThreadSafeSlabAllocator {
833    allocator: Arc<Mutex<SlabAllocator>>,
834}
835
836impl ThreadSafeSlabAllocator {
837    pub fn new(base_ptr: NonNull<u8>, total_size: usize, config: SlabConfig) -> Self {
838        let allocator = SlabAllocator::new(base_ptr, total_size, config);
839        Self {
840            allocator: Arc::new(Mutex::new(allocator)),
841        }
842    }
843
844    pub fn allocate(&self, size: usize) -> Result<NonNull<u8>, SlabError> {
845        let mut allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
846        allocator.allocate(size)
847    }
848
849    pub fn deallocate(&self, ptr: NonNull<u8>, size: usize) -> Result<(), SlabError> {
850        let mut allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
851        allocator.deallocate(ptr, size)
852    }
853
854    pub fn get_stats(&self) -> SlabAllocatorStats {
855        let allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
856        allocator.get_stats()
857    }
858
859    pub fn reclaim_memory(&self) -> usize {
860        let mut allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
861        allocator.reclaim_memory()
862    }
863}
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868
869    #[test]
870    fn test_slab_creation() {
871        let size = 4096;
872        let memory = vec![0u8; size];
873        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
874
875        let slab = Slab::new(ptr, size, 64);
876        assert_eq!(slab.object_count, size / 64);
877        assert!(slab.is_empty());
878        assert!(!slab.is_full());
879    }
880
881    #[test]
882    fn test_slab_allocation() {
883        let size = 4096;
884        let memory = vec![0u8; size];
885        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
886
887        let mut slab = Slab::new(ptr, size, 64);
888
889        let alloc1 = slab.allocate();
890        assert!(alloc1.is_some());
891        assert!(slab.is_partial());
892
893        let alloc2 = slab.allocate();
894        assert!(alloc2.is_some());
895        assert_ne!(
896            alloc1.expect("unwrap failed"),
897            alloc2.expect("unwrap failed")
898        );
899    }
900
901    #[test]
902    fn test_slab_deallocation() {
903        let size = 4096;
904        let memory = vec![0u8; size];
905        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
906
907        let mut slab = Slab::new(ptr, size, 64);
908
909        let alloc_ptr = slab.allocate().expect("unwrap failed");
910        let result = slab.deallocate(alloc_ptr);
911        assert!(result.is_ok());
912    }
913
914    #[test]
915    fn test_memory_pool() {
916        let size = 1024 * 1024;
917        let memory = vec![0u8; size];
918        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
919
920        let mut pool = MemoryPool::new(ptr, size, 256);
921
922        let slab1 = pool.allocate_slab(4096);
923        assert!(slab1.is_some());
924
925        let slab2 = pool.allocate_slab(4096);
926        assert!(slab2.is_some());
927
928        assert_ne!(slab1.expect("unwrap failed"), slab2.expect("unwrap failed"));
929    }
930
931    #[test]
932    fn test_slab_cache() {
933        let size = 1024 * 1024;
934        let memory = vec![0u8; size];
935        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
936
937        let mut pool = MemoryPool::new(ptr, size, 256);
938        let config = CacheConfig::default();
939        let mut cache = SlabCache::new(64, config);
940
941        let alloc1 = cache.allocate(&mut pool);
942        assert!(alloc1.is_ok());
943
944        let alloc2 = cache.allocate(&mut pool);
945        assert!(alloc2.is_ok());
946    }
947
948    #[test]
949    fn test_slab_allocator() {
950        let size = 1024 * 1024;
951        let memory = vec![0u8; size];
952        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
953
954        let config = SlabConfig::default();
955        let mut allocator = SlabAllocator::new(ptr, size, config);
956
957        let alloc1 = allocator.allocate(64);
958        assert!(alloc1.is_ok());
959
960        let alloc2 = allocator.allocate(128);
961        assert!(alloc2.is_ok());
962
963        let stats = allocator.get_stats();
964        // Note: Depending on the slab configuration, sizes 64 and 128 might map to the same cache class
965        assert!(stats.total_caches >= 1); // At least one cache should be created
966    }
967
968    #[test]
969    fn test_thread_safe_allocator() {
970        let size = 1024 * 1024;
971        let memory = vec![0u8; size];
972        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
973
974        let config = SlabConfig::default();
975        let allocator = ThreadSafeSlabAllocator::new(ptr, size, config);
976
977        let alloc_result = allocator.allocate(64);
978        assert!(alloc_result.is_ok());
979
980        let stats = allocator.get_stats();
981        assert!(stats.allocated_objects > 0);
982    }
983}