1use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10
11pub struct BuddyAllocator {
13 base_ptr: *mut u8,
15 total_size: usize,
17 min_block_size: usize,
19 max_order: usize,
21 free_lists: Vec<VecDeque<BuddyBlock>>,
23 allocated_blocks: HashMap<*mut u8, BuddyBlock>,
25 stats: BuddyStats,
27 config: BuddyConfig,
29}
30
31#[derive(Debug, Clone)]
33pub struct BuddyBlock {
34 pub ptr: *mut u8,
36 pub size: usize,
38 pub order: usize,
40 pub is_allocated: bool,
42 pub allocated_at: Option<Instant>,
44 pub last_accessed: Option<Instant>,
46 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 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 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#[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 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 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#[derive(Debug, Clone)]
188pub struct BuddyConfig {
189 pub enable_coalescing: bool,
191 pub enable_split_optimization: bool,
193 pub min_block_size: usize,
195 pub max_allocation_size: usize,
197 pub enable_tracking: bool,
199 pub enable_access_analysis: bool,
201 pub defrag_threshold: f64,
203 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, enable_tracking: true,
215 enable_access_analysis: false,
216 defrag_threshold: 0.5,
217 auto_defrag: true,
218 }
219 }
220}
221
222impl BuddyAllocator {
223 pub fn new(
225 base_ptr: *mut u8,
226 total_size: usize,
227 config: BuddyConfig,
228 ) -> Result<Self, BuddyError> {
229 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 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 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 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 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 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 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 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 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 return Err(BuddyError::InvalidPointer(
347 "Cannot deallocate without tracking enabled".to_string(),
348 ));
349 };
350
351 self.stats.record_deallocation();
352
353 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 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 fn find_free_block(&mut self, min_order: usize) -> Option<BuddyBlock> {
373 if let Some(exact) = self.free_lists[min_order].pop_front() {
375 return Some(exact);
376 }
377
378 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 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 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 block.size = buddy_size;
402 block.order = buddy_order;
403
404 self.free_lists[buddy_order].push_back(buddy_block);
406 }
407
408 block
409 }
410
411 fn free_with_coalescing(&mut self, block: BuddyBlock) {
413 let mut current_block = block;
414 current_block.deallocate();
415
416 while current_block.order < self.max_order {
418 let buddy_addr = current_block.get_buddy_address(self.base_ptr);
419
420 let Some(pos) = self.free_lists[current_block.order]
422 .iter()
423 .position(|b| b.ptr == buddy_addr)
424 else {
425 break;
427 };
428
429 let Some(buddy) = self.free_lists[current_block.order].remove(pos) else {
434 break;
435 };
436 self.stats.record_merge();
437
438 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 self.free_lists[current_block.order].push_back(current_block);
454 }
455
456 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 pub fn defragment(&mut self) -> usize {
480 let mut coalesced_blocks = 0;
481
482 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 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 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 self.free_lists[order].extend(processed);
516 }
517
518 coalesced_blocks
519 }
520
521 pub fn get_stats(&self) -> &BuddyStats {
523 &self.stats
524 }
525
526 pub fn get_memory_usage(&self) -> MemoryUsage {
528 let mut total_allocated = 0;
529 let mut total_free = 0;
530
531 for block in self.allocated_blocks.values() {
533 total_allocated += block.size;
534 }
535
536 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 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 pub fn validate_consistency(&self) -> Result<(), BuddyError> {
571 let mut total_free = 0;
572 let mut total_allocated = 0;
573
574 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 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 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 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 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 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(()) }
641 }
642
643 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
656unsafe impl Send for BuddyAllocator {}
662unsafe impl Sync for BuddyAllocator {}
663
664#[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#[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#[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#[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
717pub 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; let config = BuddyConfig::default();
768
769 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 let alloc1 = allocator.allocate(1024);
788 assert!(alloc1.is_ok());
789
790 let alloc2 = allocator.allocate(2048);
791 assert!(alloc2.is_ok());
792
793 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 let large_ptr = allocator.allocate(4096).expect("unwrap failed");
827
828 allocator.deallocate(large_ptr).expect("unwrap failed");
830
831 let ptr1 = allocator.allocate(2048).expect("unwrap failed");
834 let ptr2 = allocator.allocate(2048).expect("unwrap failed");
835
836 allocator.deallocate(ptr1).expect("unwrap failed");
838 allocator.deallocate(ptr2).expect("unwrap failed");
839
840 let stats = allocator.get_stats();
841 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 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}