Skip to main content

optirs_gpu/memory/allocation/
arena_allocator.rs

1// Arena allocator for GPU memory management
2//
3// This module implements arena (linear) allocators that allocate objects
4// sequentially from a contiguous block of memory. Arena allocators are
5// extremely fast for allocation and are ideal for temporary allocations
6// that can be freed all at once.
7
8use std::ptr::NonNull;
9use std::sync::{Arc, Mutex};
10use std::time::Instant;
11
12/// Main arena allocator implementation
13pub struct ArenaAllocator {
14    /// Base pointer of the arena
15    base_ptr: NonNull<u8>,
16    /// Total size of the arena
17    total_size: usize,
18    /// Current allocation offset
19    current_offset: usize,
20    /// High water mark (maximum offset reached)
21    high_water_mark: usize,
22    /// Memory alignment requirement
23    alignment: usize,
24    /// Arena configuration
25    config: ArenaConfig,
26    /// Allocation tracking (if enabled)
27    allocations: Vec<AllocationRecord>,
28    /// Statistics
29    stats: ArenaStats,
30    /// Checkpoints for nested scopes
31    checkpoints: Vec<ArenaCheckpoint>,
32}
33
34/// Arena allocation record
35#[derive(Debug, Clone)]
36pub struct AllocationRecord {
37    /// Pointer to the allocation
38    pub ptr: NonNull<u8>,
39    /// Size of the allocation
40    pub size: usize,
41    /// Offset from base
42    pub offset: usize,
43    /// Timestamp of allocation
44    pub allocated_at: Instant,
45    /// Allocation ID for debugging
46    pub id: u64,
47    /// Optional debug tag
48    pub tag: Option<String>,
49}
50
51/// Arena checkpoint for nested scopes
52#[derive(Debug, Clone)]
53pub struct ArenaCheckpoint {
54    /// Offset at checkpoint creation
55    pub offset: usize,
56    /// Number of allocations at checkpoint
57    pub allocation_count: usize,
58    /// Timestamp of checkpoint creation
59    pub created_at: Instant,
60    /// Optional checkpoint name
61    pub name: Option<String>,
62}
63
64/// Arena allocator configuration
65#[derive(Debug, Clone)]
66pub struct ArenaConfig {
67    /// Memory alignment (must be power of 2)
68    pub alignment: usize,
69    /// Enable allocation tracking
70    pub enable_tracking: bool,
71    /// Enable debug mode with extra checks
72    pub enable_debug: bool,
73    /// Enable checkpoint support
74    pub enable_checkpoints: bool,
75    /// Enable statistics collection
76    pub enable_stats: bool,
77    /// Growth strategy for resizable arenas
78    pub growth_strategy: GrowthStrategy,
79    /// Initial capacity for allocation tracking
80    pub initial_tracking_capacity: usize,
81}
82
83impl Default for ArenaConfig {
84    fn default() -> Self {
85        Self {
86            alignment: 8,
87            enable_tracking: false,
88            enable_debug: false,
89            enable_checkpoints: true,
90            enable_stats: true,
91            growth_strategy: GrowthStrategy::Fixed,
92            initial_tracking_capacity: 1024,
93        }
94    }
95}
96
97/// Growth strategy for resizable arenas
98///
99/// Deliberately does not derive `PartialEq`: `Custom` carries a raw fn
100/// pointer, and comparing fn pointers for equality is unreliable (their
101/// addresses are not guaranteed unique across codegen units and identical
102/// functions can be merged). Nothing in this crate compares two
103/// `GrowthStrategy` values, so the honest fix is to not offer a comparison
104/// that cannot mean what it appears to mean, rather than derive one anyway.
105#[derive(Debug, Clone)]
106pub enum GrowthStrategy {
107    /// Fixed size arena (no growth)
108    Fixed,
109    /// Double the size when full
110    Double,
111    /// Linear growth by fixed amount
112    Linear(usize),
113    /// Custom growth function
114    Custom(fn(usize) -> usize),
115}
116
117/// Arena allocator statistics
118#[derive(Debug, Clone, Default)]
119pub struct ArenaStats {
120    /// Total number of allocations
121    pub total_allocations: u64,
122    /// Total bytes allocated
123    pub total_bytes_allocated: u64,
124    /// Current bytes allocated
125    pub current_bytes_allocated: usize,
126    /// Peak bytes allocated
127    pub peak_bytes_allocated: usize,
128    /// Number of resets
129    pub reset_count: u64,
130    /// Number of checkpoint operations
131    pub checkpoint_count: u64,
132    /// Number of rollback operations
133    pub rollback_count: u64,
134    /// Average allocation size
135    pub average_allocation_size: f64,
136    /// Allocation rate (allocations per second)
137    pub allocation_rate: f64,
138    /// Memory utilization ratio
139    pub utilization_ratio: f64,
140    /// Time of first allocation
141    pub first_allocation_time: Option<Instant>,
142    /// Time of last allocation
143    pub last_allocation_time: Option<Instant>,
144    /// Bytes skipped to satisfy a caller-requested alignment stricter than
145    /// the arena's own (see [`ArenaAllocator::allocate_aligned`]) — real
146    /// wasted space, not counted in `total_bytes_allocated`.
147    pub bytes_wasted_to_alignment: u64,
148}
149
150impl ArenaStats {
151    pub fn record_allocation(&mut self, size: usize) {
152        let now = Instant::now();
153
154        self.total_allocations += 1;
155        self.total_bytes_allocated += size as u64;
156        self.current_bytes_allocated += size;
157
158        if self.current_bytes_allocated > self.peak_bytes_allocated {
159            self.peak_bytes_allocated = self.current_bytes_allocated;
160        }
161
162        // Update average allocation size
163        self.average_allocation_size =
164            self.total_bytes_allocated as f64 / self.total_allocations as f64;
165
166        // Update allocation rate
167        if let Some(first_time) = self.first_allocation_time {
168            let elapsed = now.duration_since(first_time).as_secs_f64();
169            if elapsed > 0.0 {
170                self.allocation_rate = self.total_allocations as f64 / elapsed;
171            }
172        } else {
173            self.first_allocation_time = Some(now);
174        }
175
176        self.last_allocation_time = Some(now);
177    }
178
179    pub fn record_reset(&mut self) {
180        self.reset_count += 1;
181        self.current_bytes_allocated = 0;
182    }
183
184    pub fn record_checkpoint(&mut self) {
185        self.checkpoint_count += 1;
186    }
187
188    pub fn record_rollback(&mut self, bytes_freed: usize) {
189        self.rollback_count += 1;
190        self.current_bytes_allocated = self.current_bytes_allocated.saturating_sub(bytes_freed);
191    }
192
193    pub fn update_utilization(&mut self, total_size: usize) {
194        if total_size > 0 {
195            self.utilization_ratio = self.current_bytes_allocated as f64 / total_size as f64;
196        }
197    }
198
199    /// Record bytes skipped purely to satisfy an alignment requirement.
200    pub fn record_padding(&mut self, padding: usize) {
201        self.bytes_wasted_to_alignment += padding as u64;
202    }
203}
204
205impl ArenaAllocator {
206    /// Create a new arena allocator
207    pub fn new(
208        base_ptr: NonNull<u8>,
209        size: usize,
210        config: ArenaConfig,
211    ) -> Result<Self, ArenaError> {
212        if size == 0 {
213            return Err(ArenaError::InvalidSize(
214                "Arena size cannot be zero".to_string(),
215            ));
216        }
217
218        if !config.alignment.is_power_of_two() {
219            return Err(ArenaError::InvalidAlignment(format!(
220                "Alignment {} is not a power of two",
221                config.alignment
222            )));
223        }
224
225        let allocations = if config.enable_tracking {
226            Vec::with_capacity(config.initial_tracking_capacity)
227        } else {
228            Vec::new()
229        };
230
231        Ok(Self {
232            base_ptr,
233            total_size: size,
234            current_offset: 0,
235            high_water_mark: 0,
236            alignment: config.alignment,
237            allocations,
238            stats: ArenaStats::default(),
239            checkpoints: Vec::new(),
240            config,
241        })
242    }
243
244    /// Allocate memory from the arena
245    pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError> {
246        if size == 0 {
247            return Err(ArenaError::InvalidSize(
248                "Cannot allocate zero bytes".to_string(),
249            ));
250        }
251
252        // Align the size
253        let aligned_size = (size + self.alignment - 1) & !(self.alignment - 1);
254
255        // Check if we have enough space
256        if self.current_offset + aligned_size > self.total_size {
257            return Err(ArenaError::OutOfMemory(format!(
258                "Not enough space: need {}, have {}",
259                aligned_size,
260                self.total_size - self.current_offset
261            )));
262        }
263
264        // Calculate the pointer
265        let ptr =
266            unsafe { NonNull::new_unchecked(self.base_ptr.as_ptr().add(self.current_offset)) };
267
268        // Update state
269        self.current_offset += aligned_size;
270        if self.current_offset > self.high_water_mark {
271            self.high_water_mark = self.current_offset;
272        }
273
274        // Record allocation
275        if self.config.enable_tracking {
276            let record = AllocationRecord {
277                ptr,
278                size: aligned_size,
279                offset: self.current_offset - aligned_size,
280                allocated_at: Instant::now(),
281                id: self.stats.total_allocations,
282                tag: None,
283            };
284            self.allocations.push(record);
285        }
286
287        // Update statistics
288        if self.config.enable_stats {
289            self.stats.record_allocation(aligned_size);
290            self.stats.update_utilization(self.total_size);
291        }
292
293        Ok(ptr)
294    }
295
296    /// Allocate memory with a debug tag
297    pub fn allocate_tagged(&mut self, size: usize, tag: String) -> Result<NonNull<u8>, ArenaError> {
298        let ptr = self.allocate(size)?;
299
300        if self.config.enable_tracking && !self.allocations.is_empty() {
301            let last_idx = self.allocations.len() - 1;
302            self.allocations[last_idx].tag = Some(tag);
303        }
304
305        Ok(ptr)
306    }
307
308    /// Allocate aligned memory
309    pub fn allocate_aligned(
310        &mut self,
311        size: usize,
312        alignment: usize,
313    ) -> Result<NonNull<u8>, ArenaError> {
314        if !alignment.is_power_of_two() {
315            return Err(ArenaError::InvalidAlignment(format!(
316                "Alignment {} is not a power of two",
317                alignment
318            )));
319        }
320
321        // Calculate aligned offset
322        let aligned_offset = (self.current_offset + alignment - 1) & !(alignment - 1);
323        let padding = aligned_offset - self.current_offset;
324
325        // Check if we have enough space including padding
326        if aligned_offset + size > self.total_size {
327            return Err(ArenaError::OutOfMemory(format!(
328                "Not enough space for aligned allocation: need {}, have {}",
329                aligned_offset + size - self.current_offset,
330                self.total_size - self.current_offset
331            )));
332        }
333
334        // Update offset to aligned position
335        self.current_offset = aligned_offset;
336        if self.config.enable_stats && padding > 0 {
337            self.stats.record_padding(padding);
338        }
339
340        // Now allocate normally
341        self.allocate(size)
342    }
343
344    /// Reset the arena to empty state
345    pub fn reset(&mut self) {
346        self.current_offset = 0;
347
348        if self.config.enable_tracking {
349            self.allocations.clear();
350        }
351
352        if self.config.enable_stats {
353            self.stats.record_reset();
354            self.stats.update_utilization(self.total_size);
355        }
356
357        self.checkpoints.clear();
358    }
359
360    /// Create a checkpoint for later rollback
361    pub fn checkpoint(&mut self) -> Result<CheckpointHandle, ArenaError> {
362        if !self.config.enable_checkpoints {
363            return Err(ArenaError::CheckpointsDisabled);
364        }
365
366        let checkpoint = ArenaCheckpoint {
367            offset: self.current_offset,
368            allocation_count: self.allocations.len(),
369            created_at: Instant::now(),
370            name: None,
371        };
372
373        self.checkpoints.push(checkpoint);
374
375        if self.config.enable_stats {
376            self.stats.record_checkpoint();
377        }
378
379        Ok(CheckpointHandle {
380            index: self.checkpoints.len() - 1,
381            offset: self.current_offset,
382        })
383    }
384
385    /// Create a named checkpoint
386    pub fn checkpoint_named(&mut self, name: String) -> Result<CheckpointHandle, ArenaError> {
387        if !self.config.enable_checkpoints {
388            return Err(ArenaError::CheckpointsDisabled);
389        }
390
391        let checkpoint = ArenaCheckpoint {
392            offset: self.current_offset,
393            allocation_count: self.allocations.len(),
394            created_at: Instant::now(),
395            name: Some(name),
396        };
397
398        self.checkpoints.push(checkpoint);
399
400        if self.config.enable_stats {
401            self.stats.record_checkpoint();
402        }
403
404        Ok(CheckpointHandle {
405            index: self.checkpoints.len() - 1,
406            offset: self.current_offset,
407        })
408    }
409
410    /// Rollback to a checkpoint
411    pub fn rollback(&mut self, handle: CheckpointHandle) -> Result<(), ArenaError> {
412        if !self.config.enable_checkpoints {
413            return Err(ArenaError::CheckpointsDisabled);
414        }
415
416        if handle.index >= self.checkpoints.len() {
417            return Err(ArenaError::InvalidCheckpoint(
418                "Checkpoint index out of range".to_string(),
419            ));
420        }
421
422        let checkpoint = &self.checkpoints[handle.index];
423
424        // A checkpoint's index can be reused by an unrelated, later checkpoint
425        // once this same `rollback` truncates `self.checkpoints` past the
426        // position it was originally recorded at (e.g. checkpoint A at index 0
427        // is rolled back -- which truncates the vec to length 0 -- and then a
428        // new checkpoint B is created and also lands at index 0). Trusting
429        // `handle.index` alone would silently roll back to B's offset using a
430        // handle the caller believes still refers to A, or underflow
431        // `current_offset - checkpoint.offset` if B's offset happens to be
432        // larger than the arena's current offset. Cross-check the offset
433        // recorded in the handle at creation time against the checkpoint
434        // currently stored at that index to detect and reject a stale handle.
435        if checkpoint.offset != handle.offset {
436            return Err(ArenaError::InvalidCheckpoint(
437                "stale checkpoint handle: the checkpoint at this index was replaced since the handle was created".to_string(),
438            ));
439        }
440
441        let bytes_freed = self.current_offset - checkpoint.offset;
442
443        // Rollback state
444        self.current_offset = checkpoint.offset;
445
446        if self.config.enable_tracking {
447            self.allocations.truncate(checkpoint.allocation_count);
448        }
449
450        // Remove checkpoints created after this one
451        self.checkpoints.truncate(handle.index);
452
453        if self.config.enable_stats {
454            self.stats.record_rollback(bytes_freed);
455            self.stats.update_utilization(self.total_size);
456        }
457
458        Ok(())
459    }
460
461    /// Get current usage information
462    pub fn get_usage(&self) -> ArenaUsage {
463        ArenaUsage {
464            total_size: self.total_size,
465            used_size: self.current_offset,
466            free_size: self.total_size - self.current_offset,
467            high_water_mark: self.high_water_mark,
468            allocation_count: self.allocations.len(),
469            checkpoint_count: self.checkpoints.len(),
470            utilization_ratio: self.current_offset as f64 / self.total_size as f64,
471        }
472    }
473
474    /// Get statistics
475    pub fn get_stats(&self) -> &ArenaStats {
476        &self.stats
477    }
478
479    /// Get allocation records (if tracking enabled)
480    pub fn get_allocations(&self) -> &[AllocationRecord] {
481        &self.allocations
482    }
483
484    /// Get checkpoints
485    pub fn get_checkpoints(&self) -> &[ArenaCheckpoint] {
486        &self.checkpoints
487    }
488
489    /// Check if a pointer belongs to this arena
490    pub fn contains_pointer(&self, ptr: NonNull<u8>) -> bool {
491        let ptr_addr = ptr.as_ptr() as usize;
492        let base_addr = self.base_ptr.as_ptr() as usize;
493
494        ptr_addr >= base_addr && ptr_addr < base_addr + self.current_offset
495    }
496
497    /// Get allocation info for a pointer (if tracking enabled)
498    pub fn get_allocation_info(&self, ptr: NonNull<u8>) -> Option<&AllocationRecord> {
499        if !self.config.enable_tracking {
500            return None;
501        }
502
503        self.allocations.iter().find(|record| record.ptr == ptr)
504    }
505
506    /// Validate arena consistency
507    pub fn validate(&self) -> Result<(), ArenaError> {
508        if self.current_offset > self.total_size {
509            return Err(ArenaError::CorruptedArena(format!(
510                "Current offset {} exceeds total size {}",
511                self.current_offset, self.total_size
512            )));
513        }
514
515        if self.high_water_mark > self.total_size {
516            return Err(ArenaError::CorruptedArena(format!(
517                "High water mark {} exceeds total size {}",
518                self.high_water_mark, self.total_size
519            )));
520        }
521
522        if self.high_water_mark < self.current_offset {
523            return Err(ArenaError::CorruptedArena(format!(
524                "High water mark {} is less than current offset {}",
525                self.high_water_mark, self.current_offset
526            )));
527        }
528
529        // Validate tracking records if enabled
530        if self.config.enable_tracking {
531            let mut total_tracked_size = 0;
532
533            for (i, record) in self.allocations.iter().enumerate() {
534                // Check pointer is within arena bounds
535                if !self.contains_pointer(record.ptr) {
536                    return Err(ArenaError::CorruptedArena(format!(
537                        "Allocation {} has pointer outside arena bounds",
538                        i
539                    )));
540                }
541
542                total_tracked_size += record.size;
543            }
544
545            // Note: total_tracked_size might be less than current_offset due to alignment padding
546            if total_tracked_size > self.current_offset {
547                return Err(ArenaError::CorruptedArena(format!(
548                    "Tracked size {} exceeds current offset {}",
549                    total_tracked_size, self.current_offset
550                )));
551            }
552        }
553
554        Ok(())
555    }
556
557    /// Get memory layout information
558    pub fn get_memory_layout(&self) -> MemoryLayout {
559        let mut layout = MemoryLayout {
560            base_address: self.base_ptr.as_ptr() as usize,
561            total_size: self.total_size,
562            used_size: self.current_offset,
563            regions: Vec::new(),
564        };
565
566        if self.config.enable_tracking {
567            for record in &self.allocations {
568                layout.regions.push(MemoryRegion {
569                    offset: record.offset,
570                    size: record.size,
571                    allocated_at: record.allocated_at,
572                    tag: record.tag.clone(),
573                });
574            }
575        }
576
577        layout
578    }
579}
580
581// Safety: ArenaAllocator manages GPU memory pointers. While NonNull<u8> is not Send/Sync by default,
582// it's safe to share ArenaAllocator across threads when protected by Arc<Mutex<>> because:
583// 1. The pointers point to GPU memory managed by the GPU driver
584// 2. The Mutex provides exclusive access for all mutable operations
585// 3. No thread-local state is maintained
586unsafe impl Send for ArenaAllocator {}
587unsafe impl Sync for ArenaAllocator {}
588
589/// Checkpoint handle for rollback operations
590#[derive(Debug, Clone)]
591pub struct CheckpointHandle {
592    index: usize,
593    offset: usize,
594}
595
596/// Arena usage information
597#[derive(Debug, Clone)]
598pub struct ArenaUsage {
599    pub total_size: usize,
600    pub used_size: usize,
601    pub free_size: usize,
602    pub high_water_mark: usize,
603    pub allocation_count: usize,
604    pub checkpoint_count: usize,
605    pub utilization_ratio: f64,
606}
607
608/// Memory layout information
609#[derive(Debug, Clone)]
610pub struct MemoryLayout {
611    pub base_address: usize,
612    pub total_size: usize,
613    pub used_size: usize,
614    pub regions: Vec<MemoryRegion>,
615}
616
617/// Memory region within arena
618#[derive(Debug, Clone)]
619pub struct MemoryRegion {
620    pub offset: usize,
621    pub size: usize,
622    pub allocated_at: Instant,
623    pub tag: Option<String>,
624}
625
626/// Ring buffer arena allocator for circular allocation patterns
627pub struct RingArena {
628    arena: ArenaAllocator,
629    /// Read pointer for ring buffer
630    read_offset: usize,
631    /// Number of live allocations
632    live_allocations: usize,
633    /// Ring configuration
634    ring_config: RingConfig,
635}
636
637/// Ring arena configuration
638#[derive(Debug, Clone)]
639pub struct RingConfig {
640    /// Enable overwrite protection
641    pub overwrite_protection: bool,
642    /// Callback when data is overwritten
643    pub overwrite_callback: Option<fn(*mut u8, usize)>,
644    /// Enable statistics
645    pub enable_stats: bool,
646}
647
648impl Default for RingConfig {
649    fn default() -> Self {
650        Self {
651            overwrite_protection: true,
652            overwrite_callback: None,
653            enable_stats: true,
654        }
655    }
656}
657
658impl RingArena {
659    pub fn new(
660        base_ptr: NonNull<u8>,
661        size: usize,
662        ring_config: RingConfig,
663    ) -> Result<Self, ArenaError> {
664        let arena_config = ArenaConfig {
665            enable_tracking: ring_config.enable_stats,
666            enable_checkpoints: false,
667            ..ArenaConfig::default()
668        };
669
670        let arena = ArenaAllocator::new(base_ptr, size, arena_config)?;
671
672        Ok(Self {
673            arena,
674            read_offset: 0,
675            live_allocations: 0,
676            ring_config,
677        })
678    }
679
680    /// Allocate from ring buffer
681    pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError> {
682        // Check if allocation would wrap around and collide with live data
683        if self.ring_config.overwrite_protection {
684            let aligned_size = (size + self.arena.alignment - 1) & !(self.arena.alignment - 1);
685
686            if self.arena.current_offset + aligned_size > self.arena.total_size {
687                // Would wrap around
688                if self.read_offset > 0 && aligned_size > self.read_offset {
689                    return Err(ArenaError::RingBufferFull(
690                        "Ring buffer full, would overwrite live data".to_string(),
691                    ));
692                }
693
694                // Safe to wrap
695                self.arena.current_offset = 0;
696            } else if self.read_offset > self.arena.current_offset {
697                // Normal case, check collision
698                if self.arena.current_offset + aligned_size > self.read_offset {
699                    return Err(ArenaError::RingBufferFull(
700                        "Ring buffer full, would overwrite live data".to_string(),
701                    ));
702                }
703            }
704        }
705
706        let ptr = self.arena.allocate(size)?;
707        self.live_allocations += 1;
708
709        Ok(ptr)
710    }
711
712    /// Mark data as consumed (advance read pointer)
713    pub fn consume(&mut self, size: usize) -> Result<(), ArenaError> {
714        let aligned_size = (size + self.arena.alignment - 1) & !(self.arena.alignment - 1);
715
716        if self.read_offset + aligned_size > self.arena.total_size {
717            // Wrap around
718            self.read_offset = aligned_size - (self.arena.total_size - self.read_offset);
719        } else {
720            self.read_offset += aligned_size;
721        }
722
723        self.live_allocations = self.live_allocations.saturating_sub(1);
724
725        Ok(())
726    }
727
728    /// Reset ring buffer
729    pub fn reset(&mut self) {
730        self.arena.reset();
731        self.read_offset = 0;
732        self.live_allocations = 0;
733    }
734
735    /// Get ring buffer usage
736    pub fn get_ring_usage(&self) -> RingUsage {
737        let total_size = self.arena.total_size;
738        let write_offset = self.arena.current_offset;
739
740        let used_size = if write_offset >= self.read_offset {
741            write_offset - self.read_offset
742        } else {
743            total_size - self.read_offset + write_offset
744        };
745
746        RingUsage {
747            total_size,
748            used_size,
749            free_size: total_size - used_size,
750            read_offset: self.read_offset,
751            write_offset,
752            live_allocations: self.live_allocations,
753        }
754    }
755}
756
757/// Ring buffer usage information
758#[derive(Debug, Clone)]
759pub struct RingUsage {
760    pub total_size: usize,
761    pub used_size: usize,
762    pub free_size: usize,
763    pub read_offset: usize,
764    pub write_offset: usize,
765    pub live_allocations: usize,
766}
767
768/// Growing arena that can expand its capacity
769pub struct GrowingArena {
770    /// Current arena
771    current_arena: ArenaAllocator,
772    /// Previous arenas (for lookups)
773    previous_arenas: Vec<ArenaAllocator>,
774    /// Growth strategy
775    growth_strategy: GrowthStrategy,
776    /// External memory allocator for growth
777    external_allocator: Option<Box<dyn ExternalAllocator>>,
778}
779
780/// External allocator trait for growing arenas
781pub trait ExternalAllocator {
782    fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError>;
783    fn deallocate(&mut self, ptr: NonNull<u8>, size: usize);
784}
785
786impl GrowingArena {
787    pub fn new(
788        base_ptr: NonNull<u8>,
789        initial_size: usize,
790        growth_strategy: GrowthStrategy,
791    ) -> Result<Self, ArenaError> {
792        let config = ArenaConfig::default();
793        let arena = ArenaAllocator::new(base_ptr, initial_size, config)?;
794
795        Ok(Self {
796            current_arena: arena,
797            previous_arenas: Vec::new(),
798            growth_strategy,
799            external_allocator: None,
800        })
801    }
802
803    pub fn with_external_allocator(mut self, allocator: Box<dyn ExternalAllocator>) -> Self {
804        self.external_allocator = Some(allocator);
805        self
806    }
807
808    /// Allocate with automatic growth
809    pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError> {
810        // Try current arena first
811        match self.current_arena.allocate(size) {
812            Ok(ptr) => Ok(ptr),
813            Err(ArenaError::OutOfMemory(_)) => {
814                // Need to grow
815                self.grow(size)?;
816                self.current_arena.allocate(size)
817            }
818            Err(e) => Err(e),
819        }
820    }
821
822    fn grow(&mut self, min_additional_size: usize) -> Result<(), ArenaError> {
823        if self.external_allocator.is_none() {
824            return Err(ArenaError::CannotGrow(
825                "No external allocator configured".to_string(),
826            ));
827        }
828
829        let current_size = self.current_arena.total_size;
830        let new_size = match &self.growth_strategy {
831            GrowthStrategy::Fixed => {
832                return Err(ArenaError::CannotGrow("Fixed size arena".to_string()))
833            }
834            GrowthStrategy::Double => current_size * 2,
835            GrowthStrategy::Linear(increment) => current_size + increment,
836            GrowthStrategy::Custom(func) => func(current_size),
837        };
838
839        let actual_new_size = new_size.max(min_additional_size);
840
841        let new_ptr = self
842            .external_allocator
843            .as_mut()
844            .ok_or_else(|| ArenaError::CannotGrow("No external allocator configured".to_string()))?
845            .allocate(actual_new_size)?;
846
847        // Move current arena to previous arenas
848        let old_arena = std::mem::replace(
849            &mut self.current_arena,
850            ArenaAllocator::new(new_ptr, actual_new_size, ArenaConfig::default())?,
851        );
852
853        self.previous_arenas.push(old_arena);
854
855        Ok(())
856    }
857
858    /// Check if pointer belongs to any arena
859    pub fn contains_pointer(&self, ptr: NonNull<u8>) -> bool {
860        if self.current_arena.contains_pointer(ptr) {
861            return true;
862        }
863
864        self.previous_arenas
865            .iter()
866            .any(|arena| arena.contains_pointer(ptr))
867    }
868
869    /// Get total usage across all arenas
870    pub fn get_total_usage(&self) -> GrowingArenaUsage {
871        let mut total_size = self.current_arena.total_size;
872        let mut used_size = self.current_arena.current_offset;
873        let mut allocation_count = self.current_arena.allocations.len();
874
875        for arena in &self.previous_arenas {
876            total_size += arena.total_size;
877            used_size += arena.current_offset;
878            allocation_count += arena.allocations.len();
879        }
880
881        GrowingArenaUsage {
882            total_size,
883            used_size,
884            free_size: total_size - used_size,
885            arena_count: 1 + self.previous_arenas.len(),
886            allocation_count,
887            current_arena_size: self.current_arena.total_size,
888            utilization_ratio: used_size as f64 / total_size as f64,
889        }
890    }
891}
892
893/// Growing arena usage information
894#[derive(Debug, Clone)]
895pub struct GrowingArenaUsage {
896    pub total_size: usize,
897    pub used_size: usize,
898    pub free_size: usize,
899    pub arena_count: usize,
900    pub allocation_count: usize,
901    pub current_arena_size: usize,
902    pub utilization_ratio: f64,
903}
904
905/// Arena allocator errors
906#[derive(Debug, Clone)]
907pub enum ArenaError {
908    InvalidSize(String),
909    InvalidAlignment(String),
910    OutOfMemory(String),
911    CheckpointsDisabled,
912    InvalidCheckpoint(String),
913    CorruptedArena(String),
914    RingBufferFull(String),
915    CannotGrow(String),
916}
917
918impl std::fmt::Display for ArenaError {
919    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
920        match self {
921            ArenaError::InvalidSize(msg) => write!(f, "Invalid size: {}", msg),
922            ArenaError::InvalidAlignment(msg) => write!(f, "Invalid alignment: {}", msg),
923            ArenaError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
924            ArenaError::CheckpointsDisabled => write!(f, "Checkpoints are disabled"),
925            ArenaError::InvalidCheckpoint(msg) => write!(f, "Invalid checkpoint: {}", msg),
926            ArenaError::CorruptedArena(msg) => write!(f, "Corrupted arena: {}", msg),
927            ArenaError::RingBufferFull(msg) => write!(f, "Ring buffer full: {}", msg),
928            ArenaError::CannotGrow(msg) => write!(f, "Cannot grow: {}", msg),
929        }
930    }
931}
932
933impl std::error::Error for ArenaError {}
934
935/// Thread-safe arena allocator wrapper
936pub struct ThreadSafeArena {
937    arena: Arc<Mutex<ArenaAllocator>>,
938}
939
940impl ThreadSafeArena {
941    pub fn new(
942        base_ptr: NonNull<u8>,
943        size: usize,
944        config: ArenaConfig,
945    ) -> Result<Self, ArenaError> {
946        let arena = ArenaAllocator::new(base_ptr, size, config)?;
947        Ok(Self {
948            arena: Arc::new(Mutex::new(arena)),
949        })
950    }
951
952    pub fn allocate(&self, size: usize) -> Result<NonNull<u8>, ArenaError> {
953        let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
954        arena.allocate(size)
955    }
956
957    pub fn reset(&self) {
958        let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
959        arena.reset();
960    }
961
962    pub fn checkpoint(&self) -> Result<CheckpointHandle, ArenaError> {
963        let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
964        arena.checkpoint()
965    }
966
967    pub fn rollback(&self, handle: CheckpointHandle) -> Result<(), ArenaError> {
968        let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
969        arena.rollback(handle)
970    }
971
972    pub fn get_usage(&self) -> ArenaUsage {
973        let arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
974        arena.get_usage()
975    }
976
977    pub fn get_stats(&self) -> ArenaStats {
978        let arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
979        arena.get_stats().clone()
980    }
981}
982
983#[cfg(test)]
984mod tests {
985    use super::*;
986
987    #[test]
988    fn test_arena_creation() {
989        let size = 4096;
990        let memory = vec![0u8; size];
991        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
992
993        let config = ArenaConfig::default();
994        let arena = ArenaAllocator::new(ptr, size, config);
995        assert!(arena.is_ok());
996    }
997
998    #[test]
999    fn test_basic_allocation() {
1000        let size = 4096;
1001        let memory = vec![0u8; size];
1002        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1003
1004        let config = ArenaConfig::default();
1005        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1006
1007        let alloc1 = arena.allocate(100);
1008        assert!(alloc1.is_ok());
1009
1010        let alloc2 = arena.allocate(200);
1011        assert!(alloc2.is_ok());
1012
1013        let usage = arena.get_usage();
1014        assert!(usage.used_size > 0);
1015        assert!(usage.allocation_count == 2 || !arena.config.enable_tracking);
1016    }
1017
1018    #[test]
1019    fn test_alignment() {
1020        let size = 4096;
1021        let memory = vec![0u8; size];
1022        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1023
1024        let config = ArenaConfig {
1025            alignment: 16,
1026            ..ArenaConfig::default()
1027        };
1028        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1029
1030        let alloc_ptr = arena.allocate(10).expect("unwrap failed");
1031        assert_eq!(alloc_ptr.as_ptr() as usize % 16, 0);
1032    }
1033
1034    #[test]
1035    fn test_allocate_aligned_records_padding_in_stats() {
1036        let size = 4096;
1037        let memory = vec![0u8; size];
1038        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1039
1040        let config = ArenaConfig {
1041            alignment: 1,
1042            ..ArenaConfig::default()
1043        };
1044        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1045        assert_eq!(arena.get_stats().bytes_wasted_to_alignment, 0);
1046
1047        // With `alignment: 1` this lands the offset at exactly 3 (no
1048        // rounding), off of any larger power-of-two boundary.
1049        arena.allocate(3).expect("unwrap failed");
1050
1051        // Aligning to 64 from offset 3 must skip 61 real bytes (3 -> 64).
1052        arena
1053            .allocate_aligned(10, 64)
1054            .expect("aligned allocation should succeed");
1055        assert_eq!(arena.get_stats().bytes_wasted_to_alignment, 61);
1056
1057        // A second aligned allocation that needs no padding (offset is
1058        // already 64-aligned after the first one landed exactly on 64+10's
1059        // own alignment) must not inflate the counter further than reality.
1060        let before = arena.get_stats().bytes_wasted_to_alignment;
1061        arena
1062            .allocate_aligned(4, 1)
1063            .expect("aligned allocation should succeed");
1064        assert_eq!(arena.get_stats().bytes_wasted_to_alignment, before);
1065    }
1066
1067    #[test]
1068    fn test_checkpoints() {
1069        let size = 4096;
1070        let memory = vec![0u8; size];
1071        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1072
1073        let config = ArenaConfig {
1074            enable_checkpoints: true,
1075            enable_tracking: true,
1076            ..ArenaConfig::default()
1077        };
1078        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1079
1080        arena.allocate(100).expect("unwrap failed");
1081        let checkpoint = arena.checkpoint().expect("unwrap failed");
1082        arena.allocate(200).expect("unwrap failed");
1083
1084        let usage_before = arena.get_usage();
1085        arena.rollback(checkpoint).expect("unwrap failed");
1086        let usage_after = arena.get_usage();
1087
1088        assert!(usage_after.used_size < usage_before.used_size);
1089    }
1090
1091    #[test]
1092    fn test_rollback_rejects_stale_handle_after_index_reuse() {
1093        let size = 4096;
1094        let memory = vec![0u8; size];
1095        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1096
1097        let config = ArenaConfig {
1098            enable_checkpoints: true,
1099            enable_tracking: true,
1100            ..ArenaConfig::default()
1101        };
1102        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1103
1104        arena.allocate(100).expect("unwrap failed");
1105        let handle_a = arena.checkpoint().expect("unwrap failed");
1106
1107        // Rolling back to A truncates `checkpoints` back to empty, freeing
1108        // index 0 for reuse by an unrelated, later checkpoint.
1109        arena.rollback(handle_a.clone()).expect("unwrap failed");
1110
1111        // A brand new checkpoint B now lands at the same index 0 that A used
1112        // to occupy, but at a different offset.
1113        arena.allocate(50).expect("unwrap failed");
1114        arena.checkpoint().expect("unwrap failed");
1115
1116        // Reusing the now-stale `handle_a` (same index, stale recorded
1117        // offset) must be rejected rather than silently rolling back to B's
1118        // position under A's name.
1119        let result = arena.rollback(handle_a);
1120        assert!(matches!(result, Err(ArenaError::InvalidCheckpoint(_))));
1121    }
1122
1123    #[test]
1124    fn test_reset() {
1125        let size = 4096;
1126        let memory = vec![0u8; size];
1127        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1128
1129        let config = ArenaConfig::default();
1130        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1131
1132        arena.allocate(100).expect("unwrap failed");
1133        arena.allocate(200).expect("unwrap failed");
1134
1135        let usage_before = arena.get_usage();
1136        assert!(usage_before.used_size > 0);
1137
1138        arena.reset();
1139        let usage_after = arena.get_usage();
1140        assert_eq!(usage_after.used_size, 0);
1141    }
1142
1143    #[test]
1144    fn test_ring_arena() {
1145        let size = 1024;
1146        let memory = vec![0u8; size];
1147        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1148
1149        let config = RingConfig::default();
1150        let mut ring = RingArena::new(ptr, size, config).expect("unwrap failed");
1151
1152        let alloc1 = ring.allocate(100);
1153        assert!(alloc1.is_ok());
1154
1155        ring.consume(100).expect("unwrap failed");
1156
1157        let alloc2 = ring.allocate(100);
1158        assert!(alloc2.is_ok());
1159    }
1160
1161    #[test]
1162    fn test_thread_safe_arena() {
1163        let size = 4096;
1164        let memory = vec![0u8; size];
1165        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1166
1167        let config = ArenaConfig::default();
1168        let arena = ThreadSafeArena::new(ptr, size, config).expect("unwrap failed");
1169
1170        let alloc_result = arena.allocate(100);
1171        assert!(alloc_result.is_ok());
1172
1173        let usage = arena.get_usage();
1174        assert!(usage.used_size > 0);
1175    }
1176
1177    #[test]
1178    fn test_arena_validation() {
1179        let size = 4096;
1180        let memory = vec![0u8; size];
1181        let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
1182
1183        let config = ArenaConfig {
1184            enable_tracking: true,
1185            ..ArenaConfig::default()
1186        };
1187        let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
1188
1189        arena.allocate(100).expect("unwrap failed");
1190        arena.allocate(200).expect("unwrap failed");
1191
1192        let validation_result = arena.validate();
1193        assert!(validation_result.is_ok());
1194    }
1195}