Skip to main content

oxigdal_gpu_advanced/
memory_pool.rs

1//! Advanced GPU memory pool with sub-allocation and defragmentation.
2//!
3//! This module provides efficient GPU memory management through a pool-based
4//! allocator with support for:
5//! - Sub-allocation from a single large buffer
6//! - Alignment handling
7//! - Automatic coalescing of adjacent free blocks
8//! - Memory defragmentation to reduce fragmentation
9//!
10//! # Defragmentation
11//!
12//! The pool tracks fragmentation levels and provides methods to compact
13//! memory allocations:
14//! - `defragment()` - Logical defragmentation (updates metadata only)
15//! - `defragment_with_queue()` - Full defragmentation with GPU memory copies
16//!
17//! # Example
18//!
19//! ```ignore
20//! let pool = Arc::new(MemoryPool::new(device, 1024 * 1024, BufferUsages::STORAGE)?);
21//! let alloc = pool.allocate(1024, 256)?;
22//! // ... use allocation ...
23//! // Defragment when needed
24//! let result = pool.defragment_with_queue(&queue)?;
25//! ```
26
27use crate::error::{GpuAdvancedError, Result};
28use parking_lot::{Mutex, RwLock};
29use std::collections::{BTreeMap, HashMap};
30use std::ops::Range;
31use std::sync::Arc;
32use std::sync::atomic::{AtomicU64, Ordering};
33use std::time::{Duration, Instant};
34use wgpu::{Buffer, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Device, Queue};
35
36/// Memory block allocation
37#[derive(Debug, Clone)]
38struct MemoryBlock {
39    /// Offset in the pool
40    offset: u64,
41    /// Size of the block
42    size: u64,
43    /// Whether the block is free
44    is_free: bool,
45    /// Allocation ID
46    allocation_id: Option<u64>,
47    /// Whether this block can be moved during defragmentation
48    movable: bool,
49    /// Reference count for tracking active usage
50    ref_count: u32,
51}
52
53/// Defragmentation plan entry describing a memory move operation
54#[derive(Debug, Clone)]
55pub struct DefragMove {
56    /// Allocation ID being moved
57    pub allocation_id: u64,
58    /// Source offset in the pool
59    pub src_offset: u64,
60    /// Destination offset in the pool
61    pub dst_offset: u64,
62    /// Size of the block to move
63    pub size: u64,
64}
65
66/// Defragmentation plan containing all moves needed
67#[derive(Debug, Clone, Default)]
68pub struct DefragmentationPlan {
69    /// List of move operations to perform
70    pub moves: Vec<DefragMove>,
71    /// Total bytes to be moved
72    pub total_bytes: u64,
73    /// Expected fragmentation after defragmentation
74    pub expected_fragmentation: f64,
75    /// Current fragmentation level
76    pub current_fragmentation: f64,
77}
78
79impl DefragmentationPlan {
80    /// Check if defragmentation is worthwhile
81    pub fn is_worthwhile(&self, min_improvement: f64) -> bool {
82        if self.moves.is_empty() {
83            return false;
84        }
85        let improvement = self.current_fragmentation - self.expected_fragmentation;
86        improvement >= min_improvement
87    }
88
89    /// Get the number of moves
90    pub fn move_count(&self) -> usize {
91        self.moves.len()
92    }
93}
94
95/// Result of a defragmentation operation
96#[derive(Debug, Clone)]
97pub struct DefragmentationResult {
98    /// Whether defragmentation was performed
99    pub performed: bool,
100    /// Number of blocks moved
101    pub blocks_moved: usize,
102    /// Total bytes moved
103    pub bytes_moved: u64,
104    /// Fragmentation before defragmentation
105    pub fragmentation_before: f64,
106    /// Fragmentation after defragmentation
107    pub fragmentation_after: f64,
108    /// Time taken for defragmentation
109    pub duration: Duration,
110    /// Number of blocks that couldn't be moved (pinned/in-use)
111    pub unmovable_blocks: usize,
112}
113
114impl Default for DefragmentationResult {
115    fn default() -> Self {
116        Self {
117            performed: false,
118            blocks_moved: 0,
119            bytes_moved: 0,
120            fragmentation_before: 0.0,
121            fragmentation_after: 0.0,
122            duration: Duration::ZERO,
123            unmovable_blocks: 0,
124        }
125    }
126}
127
128/// Defragmentation configuration
129#[derive(Debug, Clone)]
130pub struct DefragConfig {
131    /// Minimum fragmentation level to trigger defragmentation (0.0 - 1.0)
132    pub min_fragmentation_threshold: f64,
133    /// Minimum improvement required to proceed with defragmentation
134    pub min_improvement: f64,
135    /// Maximum number of blocks to move in a single defragmentation pass
136    pub max_moves_per_pass: usize,
137    /// Whether to skip unmovable blocks and continue
138    pub skip_unmovable: bool,
139    /// Alignment for compacted blocks
140    pub compaction_alignment: u64,
141}
142
143impl Default for DefragConfig {
144    fn default() -> Self {
145        Self {
146            min_fragmentation_threshold: 0.2,
147            min_improvement: 0.1,
148            max_moves_per_pass: 100,
149            skip_unmovable: true,
150            compaction_alignment: 256,
151        }
152    }
153}
154
155/// Memory pool for efficient GPU memory management
156pub struct MemoryPool {
157    /// GPU device
158    device: Arc<Device>,
159    /// Pool buffer
160    buffer: Arc<Mutex<Option<Buffer>>>,
161    /// Total pool size
162    pool_size: u64,
163    /// Memory blocks (offset -> block)
164    blocks: Arc<RwLock<BTreeMap<u64, MemoryBlock>>>,
165    /// Next allocation ID
166    next_alloc_id: Arc<Mutex<u64>>,
167    /// Buffer usage flags
168    usage: BufferUsages,
169    /// Current memory usage
170    current_usage: Arc<Mutex<u64>>,
171    /// Peak memory usage
172    peak_usage: Arc<Mutex<u64>>,
173    /// Number of allocations
174    allocation_count: Arc<Mutex<u64>>,
175    /// Number of deallocations
176    deallocation_count: Arc<Mutex<u64>>,
177    /// Number of defragmentations
178    defrag_count: Arc<Mutex<u64>>,
179    /// Relocation table: maps allocation_id to current offset
180    /// This is updated during defragmentation to track moved blocks
181    allocation_offsets: Arc<RwLock<HashMap<u64, u64>>>,
182    /// Defragmentation configuration
183    defrag_config: RwLock<DefragConfig>,
184    /// Last defragmentation timestamp
185    last_defrag_time: Arc<Mutex<Option<Instant>>>,
186    /// Total bytes moved during all defragmentations
187    total_bytes_defragged: Arc<AtomicU64>,
188}
189
190/// Memory allocation handle
191///
192/// The allocation handle tracks its current position in the pool.
193/// During defragmentation, the offset may change, but this is handled
194/// transparently through the pool's relocation table.
195pub struct MemoryAllocation {
196    /// Allocation ID
197    id: u64,
198    /// Original offset in the pool (may be stale after defragmentation)
199    original_offset: u64,
200    /// Size of the allocation
201    size: u64,
202    /// Reference to the pool
203    pool: Arc<MemoryPool>,
204}
205
206impl MemoryPool {
207    /// Create a new memory pool
208    pub fn new(device: Arc<Device>, pool_size: u64, usage: BufferUsages) -> Result<Self> {
209        Self::with_config(device, pool_size, usage, DefragConfig::default())
210    }
211
212    /// Create a new memory pool with custom defragmentation configuration
213    pub fn with_config(
214        device: Arc<Device>,
215        pool_size: u64,
216        usage: BufferUsages,
217        defrag_config: DefragConfig,
218    ) -> Result<Self> {
219        // Ensure usage includes COPY_SRC and COPY_DST for defragmentation
220        let usage_with_copy = usage | BufferUsages::COPY_SRC | BufferUsages::COPY_DST;
221
222        // Create initial pool buffer
223        let buffer = device.create_buffer(&BufferDescriptor {
224            label: Some("Memory Pool"),
225            size: pool_size,
226            usage: usage_with_copy,
227            mapped_at_creation: false,
228        });
229
230        // Initialize with one large free block
231        let mut blocks = BTreeMap::new();
232        blocks.insert(
233            0,
234            MemoryBlock {
235                offset: 0,
236                size: pool_size,
237                is_free: true,
238                allocation_id: None,
239                movable: true,
240                ref_count: 0,
241            },
242        );
243
244        Ok(Self {
245            device,
246            buffer: Arc::new(Mutex::new(Some(buffer))),
247            pool_size,
248            blocks: Arc::new(RwLock::new(blocks)),
249            next_alloc_id: Arc::new(Mutex::new(0)),
250            usage: usage_with_copy,
251            current_usage: Arc::new(Mutex::new(0)),
252            peak_usage: Arc::new(Mutex::new(0)),
253            allocation_count: Arc::new(Mutex::new(0)),
254            deallocation_count: Arc::new(Mutex::new(0)),
255            defrag_count: Arc::new(Mutex::new(0)),
256            allocation_offsets: Arc::new(RwLock::new(HashMap::new())),
257            defrag_config: RwLock::new(defrag_config),
258            last_defrag_time: Arc::new(Mutex::new(None)),
259            total_bytes_defragged: Arc::new(AtomicU64::new(0)),
260        })
261    }
262
263    /// Allocate memory from the pool
264    pub fn allocate(self: &Arc<Self>, size: u64, alignment: u64) -> Result<MemoryAllocation> {
265        let aligned_size = Self::align_up(size, alignment);
266
267        let alloc_id = {
268            let mut next_id = self.next_alloc_id.lock();
269            let id = *next_id;
270            *next_id = next_id.wrapping_add(1);
271            id
272        };
273
274        // Find a free block using first-fit strategy
275        let (offset, block_offset) = {
276            let blocks = self.blocks.read();
277
278            let mut found: Option<(u64, u64)> = None;
279
280            for (blk_offset, block) in blocks.iter() {
281                if block.is_free && block.size >= aligned_size {
282                    let aligned_offset = Self::align_up(*blk_offset, alignment);
283                    let waste = aligned_offset - blk_offset;
284
285                    if block.size >= aligned_size + waste {
286                        found = Some((aligned_offset, *blk_offset));
287                        break;
288                    }
289                }
290            }
291
292            found.ok_or_else(|| GpuAdvancedError::AllocationFailed {
293                size: aligned_size,
294                available: self.get_available_memory(),
295            })?
296        };
297
298        // Split the block
299        {
300            let mut blocks = self.blocks.write();
301
302            let block = blocks
303                .remove(&block_offset)
304                .ok_or_else(|| GpuAdvancedError::memory_pool_error("Block not found"))?;
305
306            let waste = offset - block_offset;
307
308            // Create waste block if needed
309            if waste > 0 {
310                blocks.insert(
311                    block_offset,
312                    MemoryBlock {
313                        offset: block_offset,
314                        size: waste,
315                        is_free: true,
316                        allocation_id: None,
317                        movable: true,
318                        ref_count: 0,
319                    },
320                );
321            }
322
323            // Create allocated block
324            blocks.insert(
325                offset,
326                MemoryBlock {
327                    offset,
328                    size: aligned_size,
329                    is_free: false,
330                    allocation_id: Some(alloc_id),
331                    movable: true,
332                    ref_count: 1,
333                },
334            );
335
336            // Create remainder block if needed
337            let remainder = block.size - aligned_size - waste;
338            if remainder > 0 {
339                blocks.insert(
340                    offset + aligned_size,
341                    MemoryBlock {
342                        offset: offset + aligned_size,
343                        size: remainder,
344                        is_free: true,
345                        allocation_id: None,
346                        movable: true,
347                        ref_count: 0,
348                    },
349                );
350            }
351        }
352
353        // Register allocation offset in the relocation table
354        {
355            let mut offsets = self.allocation_offsets.write();
356            offsets.insert(alloc_id, offset);
357        }
358
359        // Update statistics
360        {
361            let mut usage = self.current_usage.lock();
362            *usage = usage.saturating_add(aligned_size);
363
364            let mut peak = self.peak_usage.lock();
365            *peak = (*peak).max(*usage);
366
367            let mut count = self.allocation_count.lock();
368            *count = count.saturating_add(1);
369        }
370
371        Ok(MemoryAllocation {
372            id: alloc_id,
373            original_offset: offset,
374            size: aligned_size,
375            pool: Arc::clone(self),
376        })
377    }
378
379    /// Get the current offset for an allocation ID
380    /// Returns None if the allocation is not found
381    pub fn get_allocation_offset(&self, alloc_id: u64) -> Option<u64> {
382        self.allocation_offsets.read().get(&alloc_id).copied()
383    }
384
385    /// Pin an allocation to prevent it from being moved during defragmentation
386    pub fn pin_allocation(&self, alloc_id: u64) -> Result<()> {
387        let offsets = self.allocation_offsets.read();
388        let offset = offsets
389            .get(&alloc_id)
390            .copied()
391            .ok_or_else(|| GpuAdvancedError::memory_pool_error("Allocation not found"))?;
392        drop(offsets);
393
394        let mut blocks = self.blocks.write();
395        if let Some(block) = blocks.get_mut(&offset) {
396            block.movable = false;
397            Ok(())
398        } else {
399            Err(GpuAdvancedError::memory_pool_error(
400                "Block not found for allocation",
401            ))
402        }
403    }
404
405    /// Unpin an allocation to allow it to be moved during defragmentation
406    pub fn unpin_allocation(&self, alloc_id: u64) -> Result<()> {
407        let offsets = self.allocation_offsets.read();
408        let offset = offsets
409            .get(&alloc_id)
410            .copied()
411            .ok_or_else(|| GpuAdvancedError::memory_pool_error("Allocation not found"))?;
412        drop(offsets);
413
414        let mut blocks = self.blocks.write();
415        if let Some(block) = blocks.get_mut(&offset) {
416            block.movable = true;
417            Ok(())
418        } else {
419            Err(GpuAdvancedError::memory_pool_error(
420                "Block not found for allocation",
421            ))
422        }
423    }
424
425    /// Set the defragmentation configuration
426    pub fn set_defrag_config(&self, config: DefragConfig) {
427        *self.defrag_config.write() = config;
428    }
429
430    /// Get the current defragmentation configuration
431    pub fn get_defrag_config(&self) -> DefragConfig {
432        self.defrag_config.read().clone()
433    }
434
435    /// Deallocate memory
436    fn deallocate(&self, allocation: &MemoryAllocation) -> Result<()> {
437        // Get the current offset from the relocation table (handles defragmentation)
438        let current_offset = self
439            .allocation_offsets
440            .read()
441            .get(&allocation.id)
442            .copied()
443            .unwrap_or(allocation.original_offset);
444
445        let mut blocks = self.blocks.write();
446
447        // Find and free the block using current offset
448        if let Some(block) = blocks.get_mut(&current_offset) {
449            if block.allocation_id == Some(allocation.id) {
450                block.is_free = true;
451                block.allocation_id = None;
452                block.ref_count = 0;
453            } else {
454                return Err(GpuAdvancedError::memory_pool_error("Invalid allocation ID"));
455            }
456        } else {
457            return Err(GpuAdvancedError::memory_pool_error("Block not found"));
458        }
459
460        // Remove from relocation table
461        {
462            let mut offsets = self.allocation_offsets.write();
463            offsets.remove(&allocation.id);
464        }
465
466        // Update statistics
467        {
468            let mut usage = self.current_usage.lock();
469            *usage = usage.saturating_sub(allocation.size);
470
471            let mut count = self.deallocation_count.lock();
472            *count = count.saturating_add(1);
473        }
474
475        // Try to merge adjacent free blocks
476        self.coalesce_free_blocks(&mut blocks);
477
478        Ok(())
479    }
480
481    /// Coalesce adjacent free blocks
482    fn coalesce_free_blocks(&self, blocks: &mut BTreeMap<u64, MemoryBlock>) {
483        let mut to_merge: Vec<u64> = Vec::new();
484
485        let mut prev_offset: Option<u64> = None;
486        for (offset, block) in blocks.iter() {
487            if block.is_free {
488                if let Some(prev_off) = prev_offset {
489                    if let Some(prev_block) = blocks.get(&prev_off) {
490                        if prev_block.is_free && prev_block.offset + prev_block.size == *offset {
491                            to_merge.push(*offset);
492                        }
493                    }
494                }
495                prev_offset = Some(*offset);
496            } else {
497                prev_offset = None;
498            }
499        }
500
501        // Merge blocks
502        for offset in to_merge {
503            if let Some(block) = blocks.remove(&offset) {
504                // Find previous block
505                let prev_offset = blocks.range(..offset).next_back().map(|(k, _)| *k);
506
507                if let Some(prev_off) = prev_offset {
508                    if let Some(prev_block) = blocks.get_mut(&prev_off) {
509                        if prev_block.is_free {
510                            prev_block.size += block.size;
511                        }
512                    }
513                }
514            }
515        }
516    }
517
518    /// Create a defragmentation plan without executing it
519    ///
520    /// This analyzes the current memory layout and creates a plan for
521    /// compacting allocations. The plan can be inspected before executing.
522    pub fn plan_defragmentation(&self) -> DefragmentationPlan {
523        let config = self.defrag_config.read().clone();
524        let blocks = self.blocks.read();
525
526        let current_fragmentation = self.calculate_fragmentation_internal(&blocks);
527
528        // Collect all allocated blocks sorted by offset
529        let mut allocated_blocks: Vec<_> = blocks
530            .iter()
531            .filter(|(_, b)| !b.is_free && b.allocation_id.is_some())
532            .map(|(offset, b)| (*offset, b.clone()))
533            .collect();
534
535        allocated_blocks.sort_by_key(|(offset, _)| *offset);
536
537        // Calculate target positions (compacted from the start)
538        let mut moves = Vec::new();
539        let mut total_bytes = 0u64;
540        let mut next_offset = 0u64;
541
542        for (current_offset, block) in &allocated_blocks {
543            let aligned_offset = Self::align_up(next_offset, config.compaction_alignment);
544
545            // Only create a move if the block actually needs to move
546            if aligned_offset < *current_offset && block.movable {
547                if let Some(alloc_id) = block.allocation_id {
548                    moves.push(DefragMove {
549                        allocation_id: alloc_id,
550                        src_offset: *current_offset,
551                        dst_offset: aligned_offset,
552                        size: block.size,
553                    });
554                    total_bytes += block.size;
555                }
556                next_offset = aligned_offset + block.size;
557            } else {
558                // Block stays in place (either already optimal or unmovable)
559                next_offset = current_offset + block.size;
560            }
561        }
562
563        // Calculate expected fragmentation after defragmentation
564        let expected_fragmentation = if moves.is_empty() {
565            current_fragmentation
566        } else {
567            // After full compaction, fragmentation should be near zero
568            // unless there are unmovable blocks creating gaps
569            let unmovable_count = allocated_blocks.iter().filter(|(_, b)| !b.movable).count();
570            if unmovable_count == 0 {
571                0.0
572            } else {
573                // Estimate remaining fragmentation based on unmovable blocks
574                (unmovable_count as f64 / allocated_blocks.len().max(1) as f64) * 0.5
575            }
576        };
577
578        DefragmentationPlan {
579            moves,
580            total_bytes,
581            expected_fragmentation,
582            current_fragmentation,
583        }
584    }
585
586    /// Defragment the pool (logical defragmentation - metadata only)
587    ///
588    /// This method updates the block metadata without performing GPU memory copies.
589    /// It's useful for testing or when you plan to recreate the data anyway.
590    ///
591    /// For actual GPU memory defragmentation with data preservation, use
592    /// `defragment_with_queue()` instead.
593    pub fn defragment(&self) -> Result<DefragmentationResult> {
594        let start = Instant::now();
595        let plan = self.plan_defragmentation();
596
597        if plan.moves.is_empty() {
598            return Ok(DefragmentationResult {
599                performed: false,
600                fragmentation_before: plan.current_fragmentation,
601                fragmentation_after: plan.current_fragmentation,
602                duration: start.elapsed(),
603                ..Default::default()
604            });
605        }
606
607        let config = self.defrag_config.read().clone();
608
609        // Check if defragmentation is worthwhile
610        if !plan.is_worthwhile(config.min_improvement) {
611            return Ok(DefragmentationResult {
612                performed: false,
613                fragmentation_before: plan.current_fragmentation,
614                fragmentation_after: plan.current_fragmentation,
615                duration: start.elapsed(),
616                ..Default::default()
617            });
618        }
619
620        // Perform logical defragmentation (update metadata only)
621        let result = self.execute_defrag_plan_logical(&plan, &config)?;
622
623        // Update statistics
624        {
625            let mut count = self.defrag_count.lock();
626            *count = count.saturating_add(1);
627        }
628
629        self.total_bytes_defragged
630            .fetch_add(result.bytes_moved, Ordering::Relaxed);
631
632        *self.last_defrag_time.lock() = Some(Instant::now());
633
634        Ok(DefragmentationResult {
635            duration: start.elapsed(),
636            ..result
637        })
638    }
639
640    /// Defragment the pool with GPU memory copies
641    ///
642    /// This method performs actual GPU memory copies to compact allocations.
643    /// It requires a Queue for command submission.
644    ///
645    /// # Arguments
646    /// * `queue` - The GPU queue for submitting copy commands
647    ///
648    /// # Returns
649    /// A `DefragmentationResult` describing what was done
650    pub fn defragment_with_queue(&self, queue: &Queue) -> Result<DefragmentationResult> {
651        let start = Instant::now();
652        let plan = self.plan_defragmentation();
653
654        if plan.moves.is_empty() {
655            return Ok(DefragmentationResult {
656                performed: false,
657                fragmentation_before: plan.current_fragmentation,
658                fragmentation_after: plan.current_fragmentation,
659                duration: start.elapsed(),
660                ..Default::default()
661            });
662        }
663
664        let config = self.defrag_config.read().clone();
665
666        // Check if defragmentation is worthwhile
667        if plan.current_fragmentation < config.min_fragmentation_threshold {
668            return Ok(DefragmentationResult {
669                performed: false,
670                fragmentation_before: plan.current_fragmentation,
671                fragmentation_after: plan.current_fragmentation,
672                duration: start.elapsed(),
673                ..Default::default()
674            });
675        }
676
677        if !plan.is_worthwhile(config.min_improvement) {
678            return Ok(DefragmentationResult {
679                performed: false,
680                fragmentation_before: plan.current_fragmentation,
681                fragmentation_after: plan.current_fragmentation,
682                duration: start.elapsed(),
683                ..Default::default()
684            });
685        }
686
687        // Execute the defragmentation plan with GPU copies
688        let result = self.execute_defrag_plan_gpu(&plan, &config, queue)?;
689
690        // Update statistics
691        {
692            let mut count = self.defrag_count.lock();
693            *count = count.saturating_add(1);
694        }
695
696        self.total_bytes_defragged
697            .fetch_add(result.bytes_moved, Ordering::Relaxed);
698
699        *self.last_defrag_time.lock() = Some(Instant::now());
700
701        Ok(DefragmentationResult {
702            duration: start.elapsed(),
703            ..result
704        })
705    }
706
707    /// Execute defragmentation plan (logical only - no GPU copies)
708    fn execute_defrag_plan_logical(
709        &self,
710        plan: &DefragmentationPlan,
711        config: &DefragConfig,
712    ) -> Result<DefragmentationResult> {
713        let mut blocks = self.blocks.write();
714        let mut allocation_offsets = self.allocation_offsets.write();
715
716        let mut blocks_moved = 0usize;
717        let mut bytes_moved = 0u64;
718        let mut unmovable_blocks = 0usize;
719
720        let moves_to_execute: Vec<_> = plan
721            .moves
722            .iter()
723            .take(config.max_moves_per_pass)
724            .cloned()
725            .collect();
726
727        for defrag_move in &moves_to_execute {
728            // Remove the block from old position
729            let block = match blocks.remove(&defrag_move.src_offset) {
730                Some(b) => b,
731                None => {
732                    if config.skip_unmovable {
733                        unmovable_blocks += 1;
734                        continue;
735                    } else {
736                        return Err(GpuAdvancedError::memory_pool_error(
737                            "Block not found during defragmentation",
738                        ));
739                    }
740                }
741            };
742
743            if !block.movable {
744                // Put it back and skip
745                blocks.insert(defrag_move.src_offset, block);
746                unmovable_blocks += 1;
747                continue;
748            }
749
750            // Create block at new position
751            let new_block = MemoryBlock {
752                offset: defrag_move.dst_offset,
753                size: block.size,
754                is_free: false,
755                allocation_id: block.allocation_id,
756                movable: block.movable,
757                ref_count: block.ref_count,
758            };
759
760            blocks.insert(defrag_move.dst_offset, new_block);
761
762            // Update relocation table
763            if let Some(alloc_id) = block.allocation_id {
764                allocation_offsets.insert(alloc_id, defrag_move.dst_offset);
765            }
766
767            blocks_moved += 1;
768            bytes_moved += defrag_move.size;
769        }
770
771        // Rebuild free blocks after compaction
772        drop(blocks);
773        drop(allocation_offsets);
774        self.rebuild_free_blocks()?;
775
776        // Calculate final fragmentation
777        let blocks = self.blocks.read();
778        let fragmentation_after = self.calculate_fragmentation_internal(&blocks);
779
780        Ok(DefragmentationResult {
781            performed: blocks_moved > 0,
782            blocks_moved,
783            bytes_moved,
784            fragmentation_before: plan.current_fragmentation,
785            fragmentation_after,
786            duration: Duration::ZERO, // Will be filled by caller
787            unmovable_blocks,
788        })
789    }
790
791    /// Execute defragmentation plan with GPU memory copies
792    fn execute_defrag_plan_gpu(
793        &self,
794        plan: &DefragmentationPlan,
795        config: &DefragConfig,
796        queue: &Queue,
797    ) -> Result<DefragmentationResult> {
798        let buffer_guard = self.buffer.lock();
799        let buffer = buffer_guard
800            .as_ref()
801            .ok_or_else(|| GpuAdvancedError::memory_pool_error("Pool buffer not available"))?;
802
803        // Create a staging buffer for safe copying
804        // We use a staging buffer to avoid overlapping copies in the same buffer
805        let staging_buffer = self.device.create_buffer(&BufferDescriptor {
806            label: Some("Defrag Staging Buffer"),
807            size: plan.total_bytes,
808            usage: BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
809            mapped_at_creation: false,
810        });
811
812        let moves_to_execute: Vec<_> = plan
813            .moves
814            .iter()
815            .take(config.max_moves_per_pass)
816            .cloned()
817            .collect();
818
819        // First pass: Copy all blocks to staging buffer
820        let mut encoder = self
821            .device
822            .create_command_encoder(&CommandEncoderDescriptor {
823                label: Some("Defrag Copy to Staging"),
824            });
825
826        let mut staging_offset = 0u64;
827        let mut staging_map: Vec<(DefragMove, u64)> = Vec::new();
828
829        for defrag_move in &moves_to_execute {
830            encoder.copy_buffer_to_buffer(
831                buffer,
832                defrag_move.src_offset,
833                &staging_buffer,
834                staging_offset,
835                defrag_move.size,
836            );
837            staging_map.push((defrag_move.clone(), staging_offset));
838            staging_offset += defrag_move.size;
839        }
840
841        queue.submit(std::iter::once(encoder.finish()));
842
843        // Second pass: Copy from staging to final positions
844        let mut encoder = self
845            .device
846            .create_command_encoder(&CommandEncoderDescriptor {
847                label: Some("Defrag Copy from Staging"),
848            });
849
850        for (defrag_move, staging_off) in &staging_map {
851            encoder.copy_buffer_to_buffer(
852                &staging_buffer,
853                *staging_off,
854                buffer,
855                defrag_move.dst_offset,
856                defrag_move.size,
857            );
858        }
859
860        queue.submit(std::iter::once(encoder.finish()));
861
862        // In wgpu 28+, device polls automatically in the background
863        // No explicit poll needed - GPU operations complete asynchronously
864
865        drop(buffer_guard);
866
867        // Now update the metadata
868        let mut blocks = self.blocks.write();
869        let mut allocation_offsets = self.allocation_offsets.write();
870
871        let mut blocks_moved = 0usize;
872        let mut bytes_moved = 0u64;
873        let mut unmovable_blocks = 0usize;
874
875        for defrag_move in &moves_to_execute {
876            // Remove the block from old position
877            let block = match blocks.remove(&defrag_move.src_offset) {
878                Some(b) => b,
879                None => {
880                    unmovable_blocks += 1;
881                    continue;
882                }
883            };
884
885            // Create block at new position
886            let new_block = MemoryBlock {
887                offset: defrag_move.dst_offset,
888                size: block.size,
889                is_free: false,
890                allocation_id: block.allocation_id,
891                movable: block.movable,
892                ref_count: block.ref_count,
893            };
894
895            blocks.insert(defrag_move.dst_offset, new_block);
896
897            // Update relocation table
898            if let Some(alloc_id) = block.allocation_id {
899                allocation_offsets.insert(alloc_id, defrag_move.dst_offset);
900            }
901
902            blocks_moved += 1;
903            bytes_moved += defrag_move.size;
904        }
905
906        drop(blocks);
907        drop(allocation_offsets);
908
909        // Rebuild free blocks after compaction
910        self.rebuild_free_blocks()?;
911
912        // Calculate final fragmentation
913        let blocks = self.blocks.read();
914        let fragmentation_after = self.calculate_fragmentation_internal(&blocks);
915
916        Ok(DefragmentationResult {
917            performed: blocks_moved > 0,
918            blocks_moved,
919            bytes_moved,
920            fragmentation_before: plan.current_fragmentation,
921            fragmentation_after,
922            duration: Duration::ZERO, // Will be filled by caller
923            unmovable_blocks,
924        })
925    }
926
927    /// Rebuild free blocks after defragmentation
928    ///
929    /// This method scans the block map and creates free blocks for any gaps
930    fn rebuild_free_blocks(&self) -> Result<()> {
931        let mut blocks = self.blocks.write();
932
933        // Collect all allocated block ranges
934        let allocated_ranges: Vec<(u64, u64)> = blocks
935            .iter()
936            .filter(|(_, b)| !b.is_free)
937            .map(|(offset, b)| (*offset, b.size))
938            .collect();
939
940        // Remove all free blocks
941        let offsets_to_remove: Vec<u64> = blocks
942            .iter()
943            .filter(|(_, b)| b.is_free)
944            .map(|(offset, _)| *offset)
945            .collect();
946
947        for offset in offsets_to_remove {
948            blocks.remove(&offset);
949        }
950
951        // Find gaps and create free blocks
952        let mut last_end = 0u64;
953
954        for (offset, size) in &allocated_ranges {
955            if *offset > last_end {
956                // There's a gap - create a free block
957                blocks.insert(
958                    last_end,
959                    MemoryBlock {
960                        offset: last_end,
961                        size: offset - last_end,
962                        is_free: true,
963                        allocation_id: None,
964                        movable: true,
965                        ref_count: 0,
966                    },
967                );
968            }
969            last_end = offset + size;
970        }
971
972        // Create trailing free block if there's space at the end
973        if last_end < self.pool_size {
974            blocks.insert(
975                last_end,
976                MemoryBlock {
977                    offset: last_end,
978                    size: self.pool_size - last_end,
979                    is_free: true,
980                    allocation_id: None,
981                    movable: true,
982                    ref_count: 0,
983                },
984            );
985        }
986
987        // Coalesce any adjacent free blocks
988        self.coalesce_free_blocks(&mut blocks);
989
990        Ok(())
991    }
992
993    /// Calculate fragmentation from blocks (internal helper)
994    fn calculate_fragmentation_internal(&self, blocks: &BTreeMap<u64, MemoryBlock>) -> f64 {
995        let free_blocks: Vec<u64> = blocks
996            .values()
997            .filter(|b| b.is_free)
998            .map(|b| b.size)
999            .collect();
1000
1001        self.calculate_fragmentation(&free_blocks)
1002    }
1003
1004    /// Check if defragmentation is needed based on current configuration
1005    pub fn needs_defragmentation(&self) -> bool {
1006        let config = self.defrag_config.read();
1007        let stats = self.get_stats();
1008
1009        stats.fragmentation >= config.min_fragmentation_threshold
1010    }
1011
1012    /// Get fragmentation level (0.0 - 1.0)
1013    pub fn get_fragmentation(&self) -> f64 {
1014        let blocks = self.blocks.read();
1015        self.calculate_fragmentation_internal(&blocks)
1016    }
1017
1018    /// Get total bytes defragmented across all defragmentation operations
1019    pub fn get_total_bytes_defragged(&self) -> u64 {
1020        self.total_bytes_defragged.load(Ordering::Relaxed)
1021    }
1022
1023    /// Get the time since the last defragmentation
1024    pub fn time_since_last_defrag(&self) -> Option<Duration> {
1025        self.last_defrag_time
1026            .lock()
1027            .map(|instant| instant.elapsed())
1028    }
1029
1030    /// Get pool buffer
1031    pub fn buffer(&self) -> Option<Buffer> {
1032        // Clone the buffer (increases reference count)
1033        self.buffer.lock().as_ref().map(|_b| {
1034            self.device.create_buffer(&BufferDescriptor {
1035                label: Some("Memory Pool Access"),
1036                size: self.pool_size,
1037                usage: self.usage,
1038                mapped_at_creation: false,
1039            })
1040        })
1041    }
1042
1043    /// Get available memory
1044    pub fn get_available_memory(&self) -> u64 {
1045        let blocks = self.blocks.read();
1046        blocks
1047            .values()
1048            .filter(|block| block.is_free)
1049            .map(|block| block.size)
1050            .sum()
1051    }
1052
1053    /// Get current memory usage
1054    pub fn get_current_usage(&self) -> u64 {
1055        *self.current_usage.lock()
1056    }
1057
1058    /// Get peak memory usage
1059    pub fn get_peak_usage(&self) -> u64 {
1060        *self.peak_usage.lock()
1061    }
1062
1063    /// Get memory statistics
1064    pub fn get_stats(&self) -> MemoryPoolStats {
1065        let blocks = self.blocks.read();
1066        let free_blocks: Vec<_> = blocks
1067            .values()
1068            .filter(|b| b.is_free)
1069            .map(|b| b.size)
1070            .collect();
1071
1072        let allocated_blocks: Vec<_> = blocks
1073            .values()
1074            .filter(|b| !b.is_free)
1075            .map(|b| b.size)
1076            .collect();
1077
1078        MemoryPoolStats {
1079            pool_size: self.pool_size,
1080            current_usage: *self.current_usage.lock(),
1081            peak_usage: *self.peak_usage.lock(),
1082            available: self.get_available_memory(),
1083            allocation_count: *self.allocation_count.lock(),
1084            deallocation_count: *self.deallocation_count.lock(),
1085            defrag_count: *self.defrag_count.lock(),
1086            free_block_count: free_blocks.len(),
1087            allocated_block_count: allocated_blocks.len(),
1088            largest_free_block: free_blocks.iter().max().copied().unwrap_or(0),
1089            fragmentation: self.calculate_fragmentation(&free_blocks),
1090        }
1091    }
1092
1093    /// Calculate fragmentation factor (0.0 = no fragmentation, 1.0 = highly fragmented)
1094    fn calculate_fragmentation(&self, free_blocks: &[u64]) -> f64 {
1095        if free_blocks.is_empty() {
1096            return 0.0;
1097        }
1098
1099        let total_free: u64 = free_blocks.iter().sum();
1100        let largest = free_blocks.iter().max().copied().unwrap_or(0);
1101
1102        if total_free == 0 {
1103            return 0.0;
1104        }
1105
1106        1.0 - (largest as f64 / total_free as f64)
1107    }
1108
1109    /// Align value up to alignment
1110    fn align_up(value: u64, alignment: u64) -> u64 {
1111        if alignment == 0 {
1112            return value;
1113        }
1114        value.div_ceil(alignment) * alignment
1115    }
1116
1117    /// Print pool statistics
1118    pub fn print_stats(&self) {
1119        let stats = self.get_stats();
1120        println!("\nMemory Pool Statistics:");
1121        println!("  Pool size: {} bytes", stats.pool_size);
1122        println!(
1123            "  Current usage: {} bytes ({:.1}%)",
1124            stats.current_usage,
1125            (stats.current_usage as f64 / stats.pool_size as f64) * 100.0
1126        );
1127        println!(
1128            "  Peak usage: {} bytes ({:.1}%)",
1129            stats.peak_usage,
1130            (stats.peak_usage as f64 / stats.pool_size as f64) * 100.0
1131        );
1132        println!("  Available: {} bytes", stats.available);
1133        println!("  Allocations: {}", stats.allocation_count);
1134        println!("  Deallocations: {}", stats.deallocation_count);
1135        println!("  Defragmentations: {}", stats.defrag_count);
1136        println!("  Free blocks: {}", stats.free_block_count);
1137        println!("  Allocated blocks: {}", stats.allocated_block_count);
1138        println!("  Largest free block: {} bytes", stats.largest_free_block);
1139        println!("  Fragmentation: {:.1}%", stats.fragmentation * 100.0);
1140    }
1141}
1142
1143/// Memory pool statistics
1144#[derive(Debug, Clone)]
1145pub struct MemoryPoolStats {
1146    /// Total pool size
1147    pub pool_size: u64,
1148    /// Current memory usage
1149    pub current_usage: u64,
1150    /// Peak memory usage
1151    pub peak_usage: u64,
1152    /// Available memory
1153    pub available: u64,
1154    /// Number of allocations
1155    pub allocation_count: u64,
1156    /// Number of deallocations
1157    pub deallocation_count: u64,
1158    /// Number of defragmentations
1159    pub defrag_count: u64,
1160    /// Number of free blocks
1161    pub free_block_count: usize,
1162    /// Number of allocated blocks
1163    pub allocated_block_count: usize,
1164    /// Size of largest free block
1165    pub largest_free_block: u64,
1166    /// Fragmentation factor (0.0 to 1.0)
1167    pub fragmentation: f64,
1168}
1169
1170impl MemoryAllocation {
1171    /// Get allocation offset
1172    ///
1173    /// This looks up the current offset from the pool's relocation table,
1174    /// which handles offset changes due to defragmentation.
1175    pub fn offset(&self) -> u64 {
1176        // Look up current offset from relocation table (handles defragmentation)
1177        // Fall back to original_offset if not found
1178        self.pool
1179            .get_allocation_offset(self.id)
1180            .unwrap_or(self.original_offset)
1181    }
1182
1183    /// Get allocation size
1184    pub fn size(&self) -> u64 {
1185        self.size
1186    }
1187
1188    /// Get allocation range
1189    pub fn range(&self) -> Range<u64> {
1190        let offset = self.offset();
1191        offset..(offset + self.size)
1192    }
1193
1194    /// Get allocation ID
1195    pub fn id(&self) -> u64 {
1196        self.id
1197    }
1198}
1199
1200impl Drop for MemoryAllocation {
1201    fn drop(&mut self) {
1202        // Automatically deallocate when dropped
1203        let _ = self.pool.deallocate(self);
1204    }
1205}
1206
1207#[cfg(test)]
1208mod tests {
1209    use super::*;
1210
1211    #[test]
1212    fn test_align_up() {
1213        assert_eq!(MemoryPool::align_up(0, 256), 0);
1214        assert_eq!(MemoryPool::align_up(1, 256), 256);
1215        assert_eq!(MemoryPool::align_up(256, 256), 256);
1216        assert_eq!(MemoryPool::align_up(257, 256), 512);
1217    }
1218
1219    #[test]
1220    fn test_memory_block() {
1221        let block = MemoryBlock {
1222            offset: 0,
1223            size: 1024,
1224            is_free: true,
1225            allocation_id: None,
1226            movable: true,
1227            ref_count: 0,
1228        };
1229
1230        assert!(block.is_free);
1231        assert_eq!(block.size, 1024);
1232        assert!(block.movable);
1233        assert_eq!(block.ref_count, 0);
1234    }
1235}