Skip to main content

optirs_gpu/memory/allocation/
buddy_allocator.rs

1// Buddy system allocator for GPU memory management
2//
3// This module implements a buddy system memory allocator that maintains
4// power-of-2 sized blocks in a binary tree structure for efficient
5// allocation and deallocation with minimal fragmentation.
6
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10
11/// Buddy system allocator implementation
12pub struct BuddyAllocator {
13    /// Base address of the memory pool
14    base_ptr: *mut u8,
15    /// Total size of the memory pool (must be power of 2)
16    total_size: usize,
17    /// Minimum block size (must be power of 2)
18    min_block_size: usize,
19    /// Maximum order (log2 of total_size / min_block_size)
20    max_order: usize,
21    /// Free lists for each order
22    free_lists: Vec<VecDeque<BuddyBlock>>,
23    /// Allocation tracking for debugging
24    allocated_blocks: HashMap<*mut u8, BuddyBlock>,
25    /// Statistics
26    stats: BuddyStats,
27    /// Configuration
28    config: BuddyConfig,
29}
30
31/// Buddy block representation
32#[derive(Debug, Clone)]
33pub struct BuddyBlock {
34    /// Block address
35    pub ptr: *mut u8,
36    /// Block size (always power of 2)
37    pub size: usize,
38    /// Block order (log2 of size / min_block_size)
39    pub order: usize,
40    /// Whether block is allocated
41    pub is_allocated: bool,
42    /// Allocation timestamp
43    pub allocated_at: Option<Instant>,
44    /// Last access timestamp
45    pub last_accessed: Option<Instant>,
46    /// Access count
47    pub access_count: u64,
48}
49
50impl BuddyBlock {
51    pub fn new(ptr: *mut u8, size: usize, order: usize) -> Self {
52        Self {
53            ptr,
54            size,
55            order,
56            is_allocated: false,
57            allocated_at: None,
58            last_accessed: None,
59            access_count: 0,
60        }
61    }
62
63    pub fn allocate(&mut self) {
64        self.is_allocated = true;
65        self.allocated_at = Some(Instant::now());
66        self.access_count += 1;
67    }
68
69    pub fn deallocate(&mut self) {
70        self.is_allocated = false;
71        self.allocated_at = None;
72    }
73
74    pub fn access(&mut self) {
75        self.last_accessed = Some(Instant::now());
76        self.access_count += 1;
77    }
78
79    /// Get buddy address for this block.
80    ///
81    /// The classic buddy-system XOR trick (`offset ^ size`) only identifies
82    /// the true buddy when `offset` is measured relative to a base every
83    /// block shares. `base_ptr` (the allocator's arena base, e.g.
84    /// `BuddyAllocator::base_ptr`) must be that shared base: XOR-ing the
85    /// raw absolute pointer would only coincidentally find the real buddy,
86    /// since a real heap allocation's address is not generally a multiple
87    /// of the arena's total size.
88    pub fn get_buddy_address(&self, base_ptr: *mut u8) -> *mut u8 {
89        let relative_offset = (self.ptr as usize).wrapping_sub(base_ptr as usize);
90        let buddy_relative = relative_offset ^ self.size;
91        (base_ptr as usize).wrapping_add(buddy_relative) as *mut u8
92    }
93
94    /// Check if two blocks are buddies, relative to the allocator's
95    /// `base_ptr` (see [`Self::get_buddy_address`]).
96    pub fn is_buddy_of(&self, other: &BuddyBlock, base_ptr: *mut u8) -> bool {
97        if self.size != other.size {
98            return false;
99        }
100
101        other.ptr == self.get_buddy_address(base_ptr)
102    }
103}
104
105/// Buddy allocator statistics
106#[derive(Debug, Clone, Default)]
107pub struct BuddyStats {
108    pub total_allocations: u64,
109    pub total_deallocations: u64,
110    pub successful_allocations: u64,
111    pub failed_allocations: u64,
112    pub split_operations: u64,
113    pub merge_operations: u64,
114    pub fragmentation_ratio: f64,
115    pub average_allocation_time_ns: f64,
116    pub peak_allocated_blocks: usize,
117    pub current_allocated_blocks: usize,
118    pub internal_fragmentation: f64,
119    pub external_fragmentation: f64,
120}
121
122impl BuddyStats {
123    pub fn record_allocation(
124        &mut self,
125        success: bool,
126        time_ns: u64,
127        size_requested: usize,
128        size_allocated: usize,
129    ) {
130        self.total_allocations += 1;
131
132        if success {
133            self.successful_allocations += 1;
134            self.current_allocated_blocks += 1;
135
136            if self.current_allocated_blocks > self.peak_allocated_blocks {
137                self.peak_allocated_blocks = self.current_allocated_blocks;
138            }
139
140            // Update average allocation time
141            let total_time = self.average_allocation_time_ns
142                * (self.successful_allocations - 1) as f64
143                + time_ns as f64;
144            self.average_allocation_time_ns = total_time / self.successful_allocations as f64;
145
146            // Update internal fragmentation
147            if size_allocated > 0 {
148                let waste = size_allocated - size_requested;
149                let new_frag = waste as f64 / size_allocated as f64;
150                self.internal_fragmentation = (self.internal_fragmentation
151                    * (self.successful_allocations - 1) as f64
152                    + new_frag)
153                    / self.successful_allocations as f64;
154            }
155        } else {
156            self.failed_allocations += 1;
157        }
158    }
159
160    pub fn record_deallocation(&mut self) {
161        self.total_deallocations += 1;
162        self.current_allocated_blocks = self.current_allocated_blocks.saturating_sub(1);
163    }
164
165    pub fn record_split(&mut self) {
166        self.split_operations += 1;
167    }
168
169    pub fn record_merge(&mut self) {
170        self.merge_operations += 1;
171    }
172
173    pub fn get_success_rate(&self) -> f64 {
174        if self.total_allocations == 0 {
175            0.0
176        } else {
177            self.successful_allocations as f64 / self.total_allocations as f64
178        }
179    }
180
181    pub fn get_fragmentation_ratio(&self) -> f64 {
182        self.fragmentation_ratio
183    }
184}
185
186/// Buddy allocator configuration
187#[derive(Debug, Clone)]
188pub struct BuddyConfig {
189    /// Enable coalescing of free blocks
190    pub enable_coalescing: bool,
191    /// Enable split optimization
192    pub enable_split_optimization: bool,
193    /// Minimum block size (must be power of 2)
194    pub min_block_size: usize,
195    /// Maximum allocation size
196    pub max_allocation_size: usize,
197    /// Enable allocation tracking
198    pub enable_tracking: bool,
199    /// Enable access pattern analysis
200    pub enable_access_analysis: bool,
201    /// Defragmentation threshold
202    pub defrag_threshold: f64,
203    /// Enable automatic defragmentation
204    pub auto_defrag: bool,
205}
206
207impl Default for BuddyConfig {
208    fn default() -> Self {
209        Self {
210            enable_coalescing: true,
211            enable_split_optimization: true,
212            min_block_size: 256,
213            max_allocation_size: 1024 * 1024 * 1024, // 1GB
214            enable_tracking: true,
215            enable_access_analysis: false,
216            defrag_threshold: 0.5,
217            auto_defrag: true,
218        }
219    }
220}
221
222impl BuddyAllocator {
223    /// Create a new buddy allocator
224    pub fn new(
225        base_ptr: *mut u8,
226        total_size: usize,
227        config: BuddyConfig,
228    ) -> Result<Self, BuddyError> {
229        // Validate that total_size is power of 2
230        if !total_size.is_power_of_two() {
231            return Err(BuddyError::InvalidSize(format!(
232                "Total size {} is not a power of 2",
233                total_size
234            )));
235        }
236
237        // Validate that min_block_size is power of 2
238        if !config.min_block_size.is_power_of_two() {
239            return Err(BuddyError::InvalidSize(format!(
240                "Minimum block size {} is not a power of 2",
241                config.min_block_size
242            )));
243        }
244
245        // Validate size relationships
246        if config.min_block_size > total_size {
247            return Err(BuddyError::InvalidSize(format!(
248                "Minimum block size {} exceeds total size {}",
249                config.min_block_size, total_size
250            )));
251        }
252
253        let max_order = (total_size / config.min_block_size).trailing_zeros() as usize;
254        let mut free_lists = vec![VecDeque::new(); max_order + 1];
255
256        // Initialize with one large free block
257        let initial_block = BuddyBlock::new(base_ptr, total_size, max_order);
258        free_lists[max_order].push_back(initial_block);
259
260        Ok(Self {
261            base_ptr,
262            total_size,
263            min_block_size: config.min_block_size,
264            max_order,
265            free_lists,
266            allocated_blocks: HashMap::new(),
267            stats: BuddyStats::default(),
268            config,
269        })
270    }
271
272    /// Allocate memory of specified size
273    pub fn allocate(&mut self, size: usize) -> Result<*mut u8, BuddyError> {
274        let start_time = Instant::now();
275
276        if size == 0 {
277            self.stats.record_allocation(false, 0, size, 0);
278            return Err(BuddyError::InvalidSize(
279                "Cannot allocate zero bytes".to_string(),
280            ));
281        }
282
283        if size > self.config.max_allocation_size {
284            self.stats.record_allocation(false, 0, size, 0);
285            return Err(BuddyError::InvalidSize(format!(
286                "Allocation size {} exceeds maximum {}",
287                size, self.config.max_allocation_size
288            )));
289        }
290
291        // Calculate required order (round up to next power of 2)
292        let required_size = size.max(self.min_block_size).next_power_of_two();
293        let required_order = (required_size / self.min_block_size).trailing_zeros() as usize;
294
295        if required_order > self.max_order {
296            let elapsed = start_time.elapsed().as_nanos() as u64;
297            self.stats.record_allocation(false, elapsed, size, 0);
298            return Err(BuddyError::OutOfMemory(format!(
299                "Required order {} exceeds maximum order {}",
300                required_order, self.max_order
301            )));
302        }
303
304        // Find available block
305        match self.find_free_block(required_order) {
306            Some(mut block) => {
307                block.allocate();
308                let ptr = block.ptr;
309
310                if self.config.enable_tracking {
311                    self.allocated_blocks.insert(ptr, block);
312                }
313
314                let elapsed = start_time.elapsed().as_nanos() as u64;
315                self.stats
316                    .record_allocation(true, elapsed, size, required_size);
317
318                Ok(ptr)
319            }
320            None => {
321                let elapsed = start_time.elapsed().as_nanos() as u64;
322                self.stats.record_allocation(false, elapsed, size, 0);
323                Err(BuddyError::OutOfMemory(
324                    "No suitable block available".to_string(),
325                ))
326            }
327        }
328    }
329
330    /// Deallocate memory at specified pointer
331    pub fn deallocate(&mut self, ptr: *mut u8) -> Result<(), BuddyError> {
332        if ptr.is_null() {
333            return Err(BuddyError::InvalidPointer(
334                "Cannot deallocate null pointer".to_string(),
335            ));
336        }
337
338        // Remove from allocated blocks
339        let block = if self.config.enable_tracking {
340            self.allocated_blocks.remove(&ptr).ok_or_else(|| {
341                BuddyError::InvalidPointer("Pointer not found in allocated blocks".to_string())
342            })?
343        } else {
344            // If tracking is disabled, we need to reconstruct block info
345            // This is less safe but more performance-oriented
346            return Err(BuddyError::InvalidPointer(
347                "Cannot deallocate without tracking enabled".to_string(),
348            ));
349        };
350
351        self.stats.record_deallocation();
352
353        // Add back to free list with coalescing
354        if self.config.enable_coalescing {
355            self.free_with_coalescing(block);
356        } else {
357            self.free_lists[block.order].push_back(block);
358        }
359
360        // Trigger automatic defragmentation if needed
361        if self.config.auto_defrag {
362            let fragmentation = self.calculate_fragmentation();
363            if fragmentation > self.config.defrag_threshold {
364                self.defragment();
365            }
366        }
367
368        Ok(())
369    }
370
371    /// Find a free block of at least the specified order
372    fn find_free_block(&mut self, min_order: usize) -> Option<BuddyBlock> {
373        // Look for exact fit first
374        if let Some(exact) = self.free_lists[min_order].pop_front() {
375            return Some(exact);
376        }
377
378        // Look for larger blocks and split them
379        for order in (min_order + 1)..=self.max_order {
380            if let Some(large_block) = self.free_lists[order].pop_front() {
381                return Some(self.split_block(large_block, min_order));
382            }
383        }
384
385        None
386    }
387
388    /// Split a block down to the target order
389    fn split_block(&mut self, mut block: BuddyBlock, target_order: usize) -> BuddyBlock {
390        while block.order > target_order {
391            self.stats.record_split();
392
393            // Create buddy block
394            let buddy_size = block.size / 2;
395            let buddy_order = block.order - 1;
396            let buddy_ptr = unsafe { block.ptr.add(buddy_size) };
397
398            let buddy_block = BuddyBlock::new(buddy_ptr, buddy_size, buddy_order);
399
400            // Update original block
401            block.size = buddy_size;
402            block.order = buddy_order;
403
404            // Add buddy to free list
405            self.free_lists[buddy_order].push_back(buddy_block);
406        }
407
408        block
409    }
410
411    /// Free a block with coalescing
412    fn free_with_coalescing(&mut self, block: BuddyBlock) {
413        let mut current_block = block;
414        current_block.deallocate();
415
416        // Try to coalesce with buddy blocks
417        while current_block.order < self.max_order {
418            let buddy_addr = current_block.get_buddy_address(self.base_ptr);
419
420            // Look for buddy in the same order free list.
421            let Some(pos) = self.free_lists[current_block.order]
422                .iter()
423                .position(|b| b.ptr == buddy_addr)
424            else {
425                // No buddy found, stop coalescing.
426                break;
427            };
428
429            // `pos` was just found in this exact list, so removal is
430            // guaranteed to succeed; the `else` bails out defensively
431            // (stopping coalescing for this block) rather than panicking
432            // if that invariant were ever violated.
433            let Some(buddy) = self.free_lists[current_block.order].remove(pos) else {
434                break;
435            };
436            self.stats.record_merge();
437
438            // Create coalesced block
439            let coalesced_ptr = if current_block.ptr < buddy.ptr {
440                current_block.ptr
441            } else {
442                buddy.ptr
443            };
444
445            current_block = BuddyBlock::new(
446                coalesced_ptr,
447                current_block.size * 2,
448                current_block.order + 1,
449            );
450        }
451
452        // Add final block to appropriate free list
453        self.free_lists[current_block.order].push_back(current_block);
454    }
455
456    /// Calculate current fragmentation level
457    pub fn calculate_fragmentation(&self) -> f64 {
458        let mut total_free_space = 0;
459        let mut largest_free_block = 0;
460
461        for (order, blocks) in self.free_lists.iter().enumerate() {
462            let block_size = self.min_block_size * (1 << order);
463            let free_space = blocks.len() * block_size;
464            total_free_space += free_space;
465
466            if !blocks.is_empty() && block_size > largest_free_block {
467                largest_free_block = block_size;
468            }
469        }
470
471        if total_free_space == 0 {
472            0.0
473        } else {
474            1.0 - (largest_free_block as f64 / total_free_space as f64)
475        }
476    }
477
478    /// Perform defragmentation by coalescing free blocks
479    pub fn defragment(&mut self) -> usize {
480        let mut coalesced_blocks = 0;
481
482        // Go through each order and try to coalesce adjacent blocks
483        for order in 0..self.max_order {
484            let mut blocks_to_process: Vec<BuddyBlock> = self.free_lists[order].drain(..).collect();
485            let mut processed = Vec::new();
486
487            while !blocks_to_process.is_empty() {
488                let current = blocks_to_process.remove(0);
489                let buddy_addr = current.get_buddy_address(self.base_ptr);
490
491                // Look for buddy in remaining blocks
492                if let Some(buddy_pos) = blocks_to_process.iter().position(|b| b.ptr == buddy_addr)
493                {
494                    let buddy = blocks_to_process.remove(buddy_pos);
495                    coalesced_blocks += 1;
496                    self.stats.record_merge();
497
498                    // Create coalesced block and add to next order
499                    let coalesced_ptr = if current.ptr < buddy.ptr {
500                        current.ptr
501                    } else {
502                        buddy.ptr
503                    };
504
505                    let coalesced_block =
506                        BuddyBlock::new(coalesced_ptr, current.size * 2, current.order + 1);
507
508                    self.free_lists[order + 1].push_back(coalesced_block);
509                } else {
510                    processed.push(current);
511                }
512            }
513
514            // Put back uncoalesced blocks
515            self.free_lists[order].extend(processed);
516        }
517
518        coalesced_blocks
519    }
520
521    /// Get allocator statistics
522    pub fn get_stats(&self) -> &BuddyStats {
523        &self.stats
524    }
525
526    /// Get current memory usage
527    pub fn get_memory_usage(&self) -> MemoryUsage {
528        let mut total_allocated = 0;
529        let mut total_free = 0;
530
531        // Calculate allocated memory
532        for block in self.allocated_blocks.values() {
533            total_allocated += block.size;
534        }
535
536        // Calculate free memory
537        for (order, blocks) in self.free_lists.iter().enumerate() {
538            let block_size = self.min_block_size * (1 << order);
539            total_free += blocks.len() * block_size;
540        }
541
542        MemoryUsage {
543            total_size: self.total_size,
544            allocated_size: total_allocated,
545            free_size: total_free,
546            fragmentation_ratio: self.calculate_fragmentation(),
547            allocated_blocks: self.allocated_blocks.len(),
548            free_blocks: self.free_lists.iter().map(|l| l.len()).sum(),
549        }
550    }
551
552    /// Get detailed statistics about free blocks
553    pub fn get_free_block_stats(&self) -> Vec<FreeBlockStats> {
554        self.free_lists
555            .iter()
556            .enumerate()
557            .map(|(order, blocks)| {
558                let block_size = self.min_block_size * (1 << order);
559                FreeBlockStats {
560                    order,
561                    block_size,
562                    block_count: blocks.len(),
563                    total_size: blocks.len() * block_size,
564                }
565            })
566            .collect()
567    }
568
569    /// Check allocator consistency
570    pub fn validate_consistency(&self) -> Result<(), BuddyError> {
571        let mut total_free = 0;
572        let mut total_allocated = 0;
573
574        // Check free blocks
575        for (order, blocks) in self.free_lists.iter().enumerate() {
576            let expected_size = self.min_block_size * (1 << order);
577
578            for block in blocks {
579                if block.size != expected_size {
580                    return Err(BuddyError::CorruptedState(format!(
581                        "Free block at order {} has incorrect size: expected {}, got {}",
582                        order, expected_size, block.size
583                    )));
584                }
585
586                if block.is_allocated {
587                    return Err(BuddyError::CorruptedState(
588                        "Free block marked as allocated".to_string(),
589                    ));
590                }
591
592                total_free += block.size;
593            }
594        }
595
596        // Check allocated blocks
597        for block in self.allocated_blocks.values() {
598            if !block.is_allocated {
599                return Err(BuddyError::CorruptedState(
600                    "Allocated block marked as free".to_string(),
601                ));
602            }
603
604            total_allocated += block.size;
605        }
606
607        // Check total memory accounting
608        if total_free + total_allocated != self.total_size {
609            return Err(BuddyError::CorruptedState(format!(
610                "Memory accounting error: total_free ({}) + total_allocated ({}) != total_size ({})",
611                total_free, total_allocated, self.total_size
612            )));
613        }
614
615        Ok(())
616    }
617
618    /// Reset allocator to initial state
619    pub fn reset(&mut self) {
620        self.free_lists = vec![VecDeque::new(); self.max_order + 1];
621        self.allocated_blocks.clear();
622        self.stats = BuddyStats::default();
623
624        // Add initial large block
625        let initial_block = BuddyBlock::new(self.base_ptr, self.total_size, self.max_order);
626        self.free_lists[self.max_order].push_back(initial_block);
627    }
628
629    /// Access a previously allocated block (for statistics)
630    pub fn access_block(&mut self, ptr: *mut u8) -> Result<(), BuddyError> {
631        if self.config.enable_access_analysis {
632            if let Some(block) = self.allocated_blocks.get_mut(&ptr) {
633                block.access();
634                Ok(())
635            } else {
636                Err(BuddyError::InvalidPointer("Block not found".to_string()))
637            }
638        } else {
639            Ok(()) // Silently succeed if access analysis is disabled
640        }
641    }
642
643    /// Get allocation info for a pointer
644    pub fn get_allocation_info(&self, ptr: *mut u8) -> Option<AllocationInfo> {
645        self.allocated_blocks.get(&ptr).map(|block| AllocationInfo {
646            ptr: block.ptr,
647            size: block.size,
648            order: block.order,
649            allocated_at: block.allocated_at,
650            last_accessed: block.last_accessed,
651            access_count: block.access_count,
652        })
653    }
654}
655
656// Safety: BuddyAllocator manages GPU memory pointers. While *mut u8 is not Send/Sync by default,
657// it's safe to share BuddyAllocator across threads when protected by Arc<Mutex<>> because:
658// 1. The pointers point to GPU memory managed by the GPU driver
659// 2. The Mutex provides exclusive access for all mutable operations
660// 3. No thread-local state is maintained
661unsafe impl Send for BuddyAllocator {}
662unsafe impl Sync for BuddyAllocator {}
663
664/// Memory usage information
665#[derive(Debug, Clone)]
666pub struct MemoryUsage {
667    pub total_size: usize,
668    pub allocated_size: usize,
669    pub free_size: usize,
670    pub fragmentation_ratio: f64,
671    pub allocated_blocks: usize,
672    pub free_blocks: usize,
673}
674
675/// Free block statistics
676#[derive(Debug, Clone)]
677pub struct FreeBlockStats {
678    pub order: usize,
679    pub block_size: usize,
680    pub block_count: usize,
681    pub total_size: usize,
682}
683
684/// Allocation information
685#[derive(Debug, Clone)]
686pub struct AllocationInfo {
687    pub ptr: *mut u8,
688    pub size: usize,
689    pub order: usize,
690    pub allocated_at: Option<Instant>,
691    pub last_accessed: Option<Instant>,
692    pub access_count: u64,
693}
694
695/// Buddy allocator errors
696#[derive(Debug, Clone)]
697pub enum BuddyError {
698    InvalidSize(String),
699    OutOfMemory(String),
700    InvalidPointer(String),
701    CorruptedState(String),
702}
703
704impl std::fmt::Display for BuddyError {
705    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
706        match self {
707            BuddyError::InvalidSize(msg) => write!(f, "Invalid size: {}", msg),
708            BuddyError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
709            BuddyError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
710            BuddyError::CorruptedState(msg) => write!(f, "Corrupted state: {}", msg),
711        }
712    }
713}
714
715impl std::error::Error for BuddyError {}
716
717/// Thread-safe buddy allocator wrapper
718pub struct ThreadSafeBuddyAllocator {
719    allocator: Arc<Mutex<BuddyAllocator>>,
720}
721
722impl ThreadSafeBuddyAllocator {
723    pub fn new(
724        base_ptr: *mut u8,
725        total_size: usize,
726        config: BuddyConfig,
727    ) -> Result<Self, BuddyError> {
728        let allocator = BuddyAllocator::new(base_ptr, total_size, config)?;
729        Ok(Self {
730            allocator: Arc::new(Mutex::new(allocator)),
731        })
732    }
733
734    pub fn allocate(&self, size: usize) -> Result<*mut u8, BuddyError> {
735        let mut allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
736        allocator.allocate(size)
737    }
738
739    pub fn deallocate(&self, ptr: *mut u8) -> Result<(), BuddyError> {
740        let mut allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
741        allocator.deallocate(ptr)
742    }
743
744    pub fn get_stats(&self) -> BuddyStats {
745        let allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
746        allocator.get_stats().clone()
747    }
748
749    pub fn get_memory_usage(&self) -> MemoryUsage {
750        let allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
751        allocator.get_memory_usage()
752    }
753
754    pub fn defragment(&self) -> usize {
755        let mut allocator = self.allocator.lock().unwrap_or_else(|e| e.into_inner());
756        allocator.defragment()
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763
764    #[test]
765    fn test_buddy_allocator_creation() {
766        let size = 1024 * 1024; // 1MB
767        let config = BuddyConfig::default();
768
769        // Simulate memory allocation
770        let memory = vec![0u8; size];
771        let ptr = memory.as_ptr() as *mut u8;
772
773        let allocator = BuddyAllocator::new(ptr, size, config);
774        assert!(allocator.is_ok());
775    }
776
777    #[test]
778    fn test_basic_allocation() {
779        let size = 1024 * 1024;
780        let config = BuddyConfig::default();
781        let memory = vec![0u8; size];
782        let ptr = memory.as_ptr() as *mut u8;
783
784        let mut allocator = BuddyAllocator::new(ptr, size, config).expect("unwrap failed");
785
786        // Allocate some memory
787        let alloc1 = allocator.allocate(1024);
788        assert!(alloc1.is_ok());
789
790        let alloc2 = allocator.allocate(2048);
791        assert!(alloc2.is_ok());
792
793        // Check stats
794        let stats = allocator.get_stats();
795        assert_eq!(stats.total_allocations, 2);
796        assert_eq!(stats.successful_allocations, 2);
797    }
798
799    #[test]
800    fn test_deallocation() {
801        let size = 1024 * 1024;
802        let config = BuddyConfig::default();
803        let memory = vec![0u8; size];
804        let ptr = memory.as_ptr() as *mut u8;
805
806        let mut allocator = BuddyAllocator::new(ptr, size, config).expect("unwrap failed");
807
808        let alloc_ptr = allocator.allocate(1024).expect("unwrap failed");
809        let dealloc_result = allocator.deallocate(alloc_ptr);
810        assert!(dealloc_result.is_ok());
811
812        let stats = allocator.get_stats();
813        assert_eq!(stats.total_deallocations, 1);
814    }
815
816    #[test]
817    fn test_coalescing() {
818        let size = 1024 * 1024;
819        let config = BuddyConfig::default();
820        let memory = vec![0u8; size];
821        let ptr = memory.as_ptr() as *mut u8;
822
823        let mut allocator = BuddyAllocator::new(ptr, size, config).expect("unwrap failed");
824
825        // Allocate a larger block that will be split
826        let large_ptr = allocator.allocate(4096).expect("unwrap failed");
827
828        // Free it - this will add it back to the free list
829        allocator.deallocate(large_ptr).expect("unwrap failed");
830
831        // Now allocate two smaller blocks that are buddies
832        // The allocator will split the 4096 block into two 2048 blocks
833        let ptr1 = allocator.allocate(2048).expect("unwrap failed");
834        let ptr2 = allocator.allocate(2048).expect("unwrap failed");
835
836        // Deallocate them - they should coalesce back into the 4096 block
837        allocator.deallocate(ptr1).expect("unwrap failed");
838        allocator.deallocate(ptr2).expect("unwrap failed");
839
840        let stats = allocator.get_stats();
841        // `memory` is a real heap allocation, so `ptr` is an arbitrary
842        // (non-zero, not power-of-two-aligned-to-`size`) address -- this is
843        // a regression test for the buddy-address computation being
844        // relative to the arena's `base_ptr` rather than the raw absolute
845        // pointer (see `BuddyBlock::get_buddy_address`): with the wrong
846        // (absolute) formula, this real address essentially never finds its
847        // true buddy and coalescing silently never happens.
848        assert!(
849            stats.merge_operations > 0,
850            "two adjacent same-size blocks with a real (non-zero) base pointer must coalesce"
851        );
852    }
853
854    #[test]
855    fn test_fragmentation_calculation() {
856        let size = 1024 * 1024;
857        let config = BuddyConfig::default();
858        let memory = vec![0u8; size];
859        let ptr = memory.as_ptr() as *mut u8;
860
861        let allocator = BuddyAllocator::new(ptr, size, config).expect("unwrap failed");
862        let fragmentation = allocator.calculate_fragmentation();
863
864        // With one large free block, fragmentation should be minimal
865        assert!(fragmentation < 0.1);
866    }
867
868    #[test]
869    fn test_memory_usage() {
870        let size = 1024 * 1024;
871        let config = BuddyConfig::default();
872        let memory = vec![0u8; size];
873        let ptr = memory.as_ptr() as *mut u8;
874
875        let mut allocator = BuddyAllocator::new(ptr, size, config).expect("unwrap failed");
876
877        let usage_before = allocator.get_memory_usage();
878        assert_eq!(usage_before.total_size, size);
879        assert_eq!(usage_before.allocated_size, 0);
880
881        allocator.allocate(1024).expect("unwrap failed");
882
883        let usage_after = allocator.get_memory_usage();
884        assert!(usage_after.allocated_size > 0);
885    }
886
887    #[test]
888    fn test_thread_safe_allocator() {
889        let size = 1024 * 1024;
890        let config = BuddyConfig::default();
891        let memory = vec![0u8; size];
892        let ptr = memory.as_ptr() as *mut u8;
893
894        let allocator = ThreadSafeBuddyAllocator::new(ptr, size, config).expect("unwrap failed");
895
896        let alloc_result = allocator.allocate(1024);
897        assert!(alloc_result.is_ok());
898
899        let stats = allocator.get_stats();
900        assert_eq!(stats.total_allocations, 1);
901    }
902}