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                    && let Some(prev_block) = blocks.get(&prev_off)
490                    && prev_block.is_free
491                    && prev_block.offset + prev_block.size == *offset
492                {
493                    to_merge.push(*offset);
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                    && let Some(prev_block) = blocks.get_mut(&prev_off)
509                    && prev_block.is_free
510                {
511                    prev_block.size += block.size;
512                }
513            }
514        }
515    }
516
517    /// Create a defragmentation plan without executing it
518    ///
519    /// This analyzes the current memory layout and creates a plan for
520    /// compacting allocations. The plan can be inspected before executing.
521    pub fn plan_defragmentation(&self) -> DefragmentationPlan {
522        let config = self.defrag_config.read().clone();
523        let blocks = self.blocks.read();
524
525        let current_fragmentation = self.calculate_fragmentation_internal(&blocks);
526
527        // Collect all allocated blocks sorted by offset
528        let mut allocated_blocks: Vec<_> = blocks
529            .iter()
530            .filter(|(_, b)| !b.is_free && b.allocation_id.is_some())
531            .map(|(offset, b)| (*offset, b.clone()))
532            .collect();
533
534        allocated_blocks.sort_by_key(|(offset, _)| *offset);
535
536        // Calculate target positions (compacted from the start)
537        let mut moves = Vec::new();
538        let mut total_bytes = 0u64;
539        let mut next_offset = 0u64;
540
541        for (current_offset, block) in &allocated_blocks {
542            let aligned_offset = Self::align_up(next_offset, config.compaction_alignment);
543
544            // Only create a move if the block actually needs to move
545            if aligned_offset < *current_offset && block.movable {
546                if let Some(alloc_id) = block.allocation_id {
547                    moves.push(DefragMove {
548                        allocation_id: alloc_id,
549                        src_offset: *current_offset,
550                        dst_offset: aligned_offset,
551                        size: block.size,
552                    });
553                    total_bytes += block.size;
554                }
555                next_offset = aligned_offset + block.size;
556            } else {
557                // Block stays in place (either already optimal or unmovable)
558                next_offset = current_offset + block.size;
559            }
560        }
561
562        // Calculate expected fragmentation after defragmentation
563        let expected_fragmentation = if moves.is_empty() {
564            current_fragmentation
565        } else {
566            // After full compaction, fragmentation should be near zero
567            // unless there are unmovable blocks creating gaps
568            let unmovable_count = allocated_blocks.iter().filter(|(_, b)| !b.movable).count();
569            if unmovable_count == 0 {
570                0.0
571            } else {
572                // Estimate remaining fragmentation based on unmovable blocks
573                (unmovable_count as f64 / allocated_blocks.len().max(1) as f64) * 0.5
574            }
575        };
576
577        DefragmentationPlan {
578            moves,
579            total_bytes,
580            expected_fragmentation,
581            current_fragmentation,
582        }
583    }
584
585    /// Defragment the pool (logical defragmentation - metadata only)
586    ///
587    /// This method updates the block metadata without performing GPU memory copies.
588    /// It's useful for testing or when you plan to recreate the data anyway.
589    ///
590    /// For actual GPU memory defragmentation with data preservation, use
591    /// `defragment_with_queue()` instead.
592    pub fn defragment(&self) -> Result<DefragmentationResult> {
593        let start = Instant::now();
594        let plan = self.plan_defragmentation();
595
596        if plan.moves.is_empty() {
597            return Ok(DefragmentationResult {
598                performed: false,
599                fragmentation_before: plan.current_fragmentation,
600                fragmentation_after: plan.current_fragmentation,
601                duration: start.elapsed(),
602                ..Default::default()
603            });
604        }
605
606        let config = self.defrag_config.read().clone();
607
608        // Check if defragmentation is worthwhile
609        if !plan.is_worthwhile(config.min_improvement) {
610            return Ok(DefragmentationResult {
611                performed: false,
612                fragmentation_before: plan.current_fragmentation,
613                fragmentation_after: plan.current_fragmentation,
614                duration: start.elapsed(),
615                ..Default::default()
616            });
617        }
618
619        // Perform logical defragmentation (update metadata only)
620        let result = self.execute_defrag_plan_logical(&plan, &config)?;
621
622        // Update statistics
623        {
624            let mut count = self.defrag_count.lock();
625            *count = count.saturating_add(1);
626        }
627
628        self.total_bytes_defragged
629            .fetch_add(result.bytes_moved, Ordering::Relaxed);
630
631        *self.last_defrag_time.lock() = Some(Instant::now());
632
633        Ok(DefragmentationResult {
634            duration: start.elapsed(),
635            ..result
636        })
637    }
638
639    /// Defragment the pool with GPU memory copies
640    ///
641    /// This method performs actual GPU memory copies to compact allocations.
642    /// It requires a Queue for command submission.
643    ///
644    /// # Arguments
645    /// * `queue` - The GPU queue for submitting copy commands
646    ///
647    /// # Returns
648    /// A `DefragmentationResult` describing what was done
649    pub fn defragment_with_queue(&self, queue: &Queue) -> Result<DefragmentationResult> {
650        let start = Instant::now();
651        let plan = self.plan_defragmentation();
652
653        if plan.moves.is_empty() {
654            return Ok(DefragmentationResult {
655                performed: false,
656                fragmentation_before: plan.current_fragmentation,
657                fragmentation_after: plan.current_fragmentation,
658                duration: start.elapsed(),
659                ..Default::default()
660            });
661        }
662
663        let config = self.defrag_config.read().clone();
664
665        // Check if defragmentation is worthwhile
666        if plan.current_fragmentation < config.min_fragmentation_threshold {
667            return Ok(DefragmentationResult {
668                performed: false,
669                fragmentation_before: plan.current_fragmentation,
670                fragmentation_after: plan.current_fragmentation,
671                duration: start.elapsed(),
672                ..Default::default()
673            });
674        }
675
676        if !plan.is_worthwhile(config.min_improvement) {
677            return Ok(DefragmentationResult {
678                performed: false,
679                fragmentation_before: plan.current_fragmentation,
680                fragmentation_after: plan.current_fragmentation,
681                duration: start.elapsed(),
682                ..Default::default()
683            });
684        }
685
686        // Execute the defragmentation plan with GPU copies
687        let result = self.execute_defrag_plan_gpu(&plan, &config, queue)?;
688
689        // Update statistics
690        {
691            let mut count = self.defrag_count.lock();
692            *count = count.saturating_add(1);
693        }
694
695        self.total_bytes_defragged
696            .fetch_add(result.bytes_moved, Ordering::Relaxed);
697
698        *self.last_defrag_time.lock() = Some(Instant::now());
699
700        Ok(DefragmentationResult {
701            duration: start.elapsed(),
702            ..result
703        })
704    }
705
706    /// Execute defragmentation plan (logical only - no GPU copies)
707    fn execute_defrag_plan_logical(
708        &self,
709        plan: &DefragmentationPlan,
710        config: &DefragConfig,
711    ) -> Result<DefragmentationResult> {
712        let mut blocks = self.blocks.write();
713        let mut allocation_offsets = self.allocation_offsets.write();
714
715        let mut blocks_moved = 0usize;
716        let mut bytes_moved = 0u64;
717        let mut unmovable_blocks = 0usize;
718
719        let moves_to_execute: Vec<_> = plan
720            .moves
721            .iter()
722            .take(config.max_moves_per_pass)
723            .cloned()
724            .collect();
725
726        for defrag_move in &moves_to_execute {
727            // Remove the block from old position
728            let block = match blocks.remove(&defrag_move.src_offset) {
729                Some(b) => b,
730                None => {
731                    if config.skip_unmovable {
732                        unmovable_blocks += 1;
733                        continue;
734                    } else {
735                        return Err(GpuAdvancedError::memory_pool_error(
736                            "Block not found during defragmentation",
737                        ));
738                    }
739                }
740            };
741
742            if !block.movable {
743                // Put it back and skip
744                blocks.insert(defrag_move.src_offset, block);
745                unmovable_blocks += 1;
746                continue;
747            }
748
749            // Create block at new position
750            let new_block = MemoryBlock {
751                offset: defrag_move.dst_offset,
752                size: block.size,
753                is_free: false,
754                allocation_id: block.allocation_id,
755                movable: block.movable,
756                ref_count: block.ref_count,
757            };
758
759            blocks.insert(defrag_move.dst_offset, new_block);
760
761            // Update relocation table
762            if let Some(alloc_id) = block.allocation_id {
763                allocation_offsets.insert(alloc_id, defrag_move.dst_offset);
764            }
765
766            blocks_moved += 1;
767            bytes_moved += defrag_move.size;
768        }
769
770        // Rebuild free blocks after compaction
771        drop(blocks);
772        drop(allocation_offsets);
773        self.rebuild_free_blocks()?;
774
775        // Calculate final fragmentation
776        let blocks = self.blocks.read();
777        let fragmentation_after = self.calculate_fragmentation_internal(&blocks);
778
779        Ok(DefragmentationResult {
780            performed: blocks_moved > 0,
781            blocks_moved,
782            bytes_moved,
783            fragmentation_before: plan.current_fragmentation,
784            fragmentation_after,
785            duration: Duration::ZERO, // Will be filled by caller
786            unmovable_blocks,
787        })
788    }
789
790    /// Execute defragmentation plan with GPU memory copies
791    fn execute_defrag_plan_gpu(
792        &self,
793        plan: &DefragmentationPlan,
794        config: &DefragConfig,
795        queue: &Queue,
796    ) -> Result<DefragmentationResult> {
797        let buffer_guard = self.buffer.lock();
798        let buffer = buffer_guard
799            .as_ref()
800            .ok_or_else(|| GpuAdvancedError::memory_pool_error("Pool buffer not available"))?;
801
802        // Create a staging buffer for safe copying
803        // We use a staging buffer to avoid overlapping copies in the same buffer
804        let staging_buffer = self.device.create_buffer(&BufferDescriptor {
805            label: Some("Defrag Staging Buffer"),
806            size: plan.total_bytes,
807            usage: BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
808            mapped_at_creation: false,
809        });
810
811        let moves_to_execute: Vec<_> = plan
812            .moves
813            .iter()
814            .take(config.max_moves_per_pass)
815            .cloned()
816            .collect();
817
818        // First pass: Copy all blocks to staging buffer
819        let mut encoder = self
820            .device
821            .create_command_encoder(&CommandEncoderDescriptor {
822                label: Some("Defrag Copy to Staging"),
823            });
824
825        let mut staging_offset = 0u64;
826        let mut staging_map: Vec<(DefragMove, u64)> = Vec::new();
827
828        for defrag_move in &moves_to_execute {
829            encoder.copy_buffer_to_buffer(
830                buffer,
831                defrag_move.src_offset,
832                &staging_buffer,
833                staging_offset,
834                defrag_move.size,
835            );
836            staging_map.push((defrag_move.clone(), staging_offset));
837            staging_offset += defrag_move.size;
838        }
839
840        queue.submit(std::iter::once(encoder.finish()));
841
842        // Second pass: Copy from staging to final positions
843        let mut encoder = self
844            .device
845            .create_command_encoder(&CommandEncoderDescriptor {
846                label: Some("Defrag Copy from Staging"),
847            });
848
849        for (defrag_move, staging_off) in &staging_map {
850            encoder.copy_buffer_to_buffer(
851                &staging_buffer,
852                *staging_off,
853                buffer,
854                defrag_move.dst_offset,
855                defrag_move.size,
856            );
857        }
858
859        queue.submit(std::iter::once(encoder.finish()));
860
861        // In wgpu 28+, device polls automatically in the background
862        // No explicit poll needed - GPU operations complete asynchronously
863
864        drop(buffer_guard);
865
866        // Now update the metadata
867        let mut blocks = self.blocks.write();
868        let mut allocation_offsets = self.allocation_offsets.write();
869
870        let mut blocks_moved = 0usize;
871        let mut bytes_moved = 0u64;
872        let mut unmovable_blocks = 0usize;
873
874        for defrag_move in &moves_to_execute {
875            // Remove the block from old position
876            let block = match blocks.remove(&defrag_move.src_offset) {
877                Some(b) => b,
878                None => {
879                    unmovable_blocks += 1;
880                    continue;
881                }
882            };
883
884            // Create block at new position
885            let new_block = MemoryBlock {
886                offset: defrag_move.dst_offset,
887                size: block.size,
888                is_free: false,
889                allocation_id: block.allocation_id,
890                movable: block.movable,
891                ref_count: block.ref_count,
892            };
893
894            blocks.insert(defrag_move.dst_offset, new_block);
895
896            // Update relocation table
897            if let Some(alloc_id) = block.allocation_id {
898                allocation_offsets.insert(alloc_id, defrag_move.dst_offset);
899            }
900
901            blocks_moved += 1;
902            bytes_moved += defrag_move.size;
903        }
904
905        drop(blocks);
906        drop(allocation_offsets);
907
908        // Rebuild free blocks after compaction
909        self.rebuild_free_blocks()?;
910
911        // Calculate final fragmentation
912        let blocks = self.blocks.read();
913        let fragmentation_after = self.calculate_fragmentation_internal(&blocks);
914
915        Ok(DefragmentationResult {
916            performed: blocks_moved > 0,
917            blocks_moved,
918            bytes_moved,
919            fragmentation_before: plan.current_fragmentation,
920            fragmentation_after,
921            duration: Duration::ZERO, // Will be filled by caller
922            unmovable_blocks,
923        })
924    }
925
926    /// Rebuild free blocks after defragmentation
927    ///
928    /// This method scans the block map and creates free blocks for any gaps
929    fn rebuild_free_blocks(&self) -> Result<()> {
930        let mut blocks = self.blocks.write();
931
932        // Collect all allocated block ranges
933        let allocated_ranges: Vec<(u64, u64)> = blocks
934            .iter()
935            .filter(|(_, b)| !b.is_free)
936            .map(|(offset, b)| (*offset, b.size))
937            .collect();
938
939        // Remove all free blocks
940        let offsets_to_remove: Vec<u64> = blocks
941            .iter()
942            .filter(|(_, b)| b.is_free)
943            .map(|(offset, _)| *offset)
944            .collect();
945
946        for offset in offsets_to_remove {
947            blocks.remove(&offset);
948        }
949
950        // Find gaps and create free blocks
951        let mut last_end = 0u64;
952
953        for (offset, size) in &allocated_ranges {
954            if *offset > last_end {
955                // There's a gap - create a free block
956                blocks.insert(
957                    last_end,
958                    MemoryBlock {
959                        offset: last_end,
960                        size: offset - last_end,
961                        is_free: true,
962                        allocation_id: None,
963                        movable: true,
964                        ref_count: 0,
965                    },
966                );
967            }
968            last_end = offset + size;
969        }
970
971        // Create trailing free block if there's space at the end
972        if last_end < self.pool_size {
973            blocks.insert(
974                last_end,
975                MemoryBlock {
976                    offset: last_end,
977                    size: self.pool_size - last_end,
978                    is_free: true,
979                    allocation_id: None,
980                    movable: true,
981                    ref_count: 0,
982                },
983            );
984        }
985
986        // Coalesce any adjacent free blocks
987        self.coalesce_free_blocks(&mut blocks);
988
989        Ok(())
990    }
991
992    /// Calculate fragmentation from blocks (internal helper)
993    fn calculate_fragmentation_internal(&self, blocks: &BTreeMap<u64, MemoryBlock>) -> f64 {
994        let free_blocks: Vec<u64> = blocks
995            .values()
996            .filter(|b| b.is_free)
997            .map(|b| b.size)
998            .collect();
999
1000        self.calculate_fragmentation(&free_blocks)
1001    }
1002
1003    /// Check if defragmentation is needed based on current configuration
1004    pub fn needs_defragmentation(&self) -> bool {
1005        let config = self.defrag_config.read();
1006        let stats = self.get_stats();
1007
1008        stats.fragmentation >= config.min_fragmentation_threshold
1009    }
1010
1011    /// Get fragmentation level (0.0 - 1.0)
1012    pub fn get_fragmentation(&self) -> f64 {
1013        let blocks = self.blocks.read();
1014        self.calculate_fragmentation_internal(&blocks)
1015    }
1016
1017    /// Get total bytes defragmented across all defragmentation operations
1018    pub fn get_total_bytes_defragged(&self) -> u64 {
1019        self.total_bytes_defragged.load(Ordering::Relaxed)
1020    }
1021
1022    /// Get the time since the last defragmentation
1023    pub fn time_since_last_defrag(&self) -> Option<Duration> {
1024        self.last_defrag_time
1025            .lock()
1026            .map(|instant| instant.elapsed())
1027    }
1028
1029    /// Get pool buffer
1030    pub fn buffer(&self) -> Option<Buffer> {
1031        // Clone the buffer (increases reference count)
1032        self.buffer.lock().as_ref().map(|_b| {
1033            self.device.create_buffer(&BufferDescriptor {
1034                label: Some("Memory Pool Access"),
1035                size: self.pool_size,
1036                usage: self.usage,
1037                mapped_at_creation: false,
1038            })
1039        })
1040    }
1041
1042    /// Get available memory
1043    pub fn get_available_memory(&self) -> u64 {
1044        let blocks = self.blocks.read();
1045        blocks
1046            .values()
1047            .filter(|block| block.is_free)
1048            .map(|block| block.size)
1049            .sum()
1050    }
1051
1052    /// Get current memory usage
1053    pub fn get_current_usage(&self) -> u64 {
1054        *self.current_usage.lock()
1055    }
1056
1057    /// Get peak memory usage
1058    pub fn get_peak_usage(&self) -> u64 {
1059        *self.peak_usage.lock()
1060    }
1061
1062    /// Get memory statistics
1063    pub fn get_stats(&self) -> MemoryPoolStats {
1064        let blocks = self.blocks.read();
1065        let free_blocks: Vec<_> = blocks
1066            .values()
1067            .filter(|b| b.is_free)
1068            .map(|b| b.size)
1069            .collect();
1070
1071        let allocated_blocks: Vec<_> = blocks
1072            .values()
1073            .filter(|b| !b.is_free)
1074            .map(|b| b.size)
1075            .collect();
1076
1077        MemoryPoolStats {
1078            pool_size: self.pool_size,
1079            current_usage: *self.current_usage.lock(),
1080            peak_usage: *self.peak_usage.lock(),
1081            available: self.get_available_memory(),
1082            allocation_count: *self.allocation_count.lock(),
1083            deallocation_count: *self.deallocation_count.lock(),
1084            defrag_count: *self.defrag_count.lock(),
1085            free_block_count: free_blocks.len(),
1086            allocated_block_count: allocated_blocks.len(),
1087            largest_free_block: free_blocks.iter().max().copied().unwrap_or(0),
1088            fragmentation: self.calculate_fragmentation(&free_blocks),
1089        }
1090    }
1091
1092    /// Calculate fragmentation factor (0.0 = no fragmentation, 1.0 = highly fragmented)
1093    fn calculate_fragmentation(&self, free_blocks: &[u64]) -> f64 {
1094        if free_blocks.is_empty() {
1095            return 0.0;
1096        }
1097
1098        let total_free: u64 = free_blocks.iter().sum();
1099        let largest = free_blocks.iter().max().copied().unwrap_or(0);
1100
1101        if total_free == 0 {
1102            return 0.0;
1103        }
1104
1105        1.0 - (largest as f64 / total_free as f64)
1106    }
1107
1108    /// Align value up to alignment
1109    fn align_up(value: u64, alignment: u64) -> u64 {
1110        if alignment == 0 {
1111            return value;
1112        }
1113        value.div_ceil(alignment) * alignment
1114    }
1115
1116    /// Print pool statistics
1117    pub fn print_stats(&self) {
1118        let stats = self.get_stats();
1119        println!("\nMemory Pool Statistics:");
1120        println!("  Pool size: {} bytes", stats.pool_size);
1121        println!(
1122            "  Current usage: {} bytes ({:.1}%)",
1123            stats.current_usage,
1124            (stats.current_usage as f64 / stats.pool_size as f64) * 100.0
1125        );
1126        println!(
1127            "  Peak usage: {} bytes ({:.1}%)",
1128            stats.peak_usage,
1129            (stats.peak_usage as f64 / stats.pool_size as f64) * 100.0
1130        );
1131        println!("  Available: {} bytes", stats.available);
1132        println!("  Allocations: {}", stats.allocation_count);
1133        println!("  Deallocations: {}", stats.deallocation_count);
1134        println!("  Defragmentations: {}", stats.defrag_count);
1135        println!("  Free blocks: {}", stats.free_block_count);
1136        println!("  Allocated blocks: {}", stats.allocated_block_count);
1137        println!("  Largest free block: {} bytes", stats.largest_free_block);
1138        println!("  Fragmentation: {:.1}%", stats.fragmentation * 100.0);
1139    }
1140}
1141
1142/// Memory pool statistics
1143#[derive(Debug, Clone)]
1144pub struct MemoryPoolStats {
1145    /// Total pool size
1146    pub pool_size: u64,
1147    /// Current memory usage
1148    pub current_usage: u64,
1149    /// Peak memory usage
1150    pub peak_usage: u64,
1151    /// Available memory
1152    pub available: u64,
1153    /// Number of allocations
1154    pub allocation_count: u64,
1155    /// Number of deallocations
1156    pub deallocation_count: u64,
1157    /// Number of defragmentations
1158    pub defrag_count: u64,
1159    /// Number of free blocks
1160    pub free_block_count: usize,
1161    /// Number of allocated blocks
1162    pub allocated_block_count: usize,
1163    /// Size of largest free block
1164    pub largest_free_block: u64,
1165    /// Fragmentation factor (0.0 to 1.0)
1166    pub fragmentation: f64,
1167}
1168
1169impl MemoryAllocation {
1170    /// Get allocation offset
1171    ///
1172    /// This looks up the current offset from the pool's relocation table,
1173    /// which handles offset changes due to defragmentation.
1174    pub fn offset(&self) -> u64 {
1175        // Look up current offset from relocation table (handles defragmentation)
1176        // Fall back to original_offset if not found
1177        self.pool
1178            .get_allocation_offset(self.id)
1179            .unwrap_or(self.original_offset)
1180    }
1181
1182    /// Get allocation size
1183    pub fn size(&self) -> u64 {
1184        self.size
1185    }
1186
1187    /// Get allocation range
1188    pub fn range(&self) -> Range<u64> {
1189        let offset = self.offset();
1190        offset..(offset + self.size)
1191    }
1192
1193    /// Get allocation ID
1194    pub fn id(&self) -> u64 {
1195        self.id
1196    }
1197}
1198
1199impl Drop for MemoryAllocation {
1200    fn drop(&mut self) {
1201        // Automatically deallocate when dropped
1202        let _ = self.pool.deallocate(self);
1203    }
1204}
1205
1206#[cfg(test)]
1207mod tests {
1208    use super::*;
1209
1210    #[test]
1211    fn test_align_up() {
1212        assert_eq!(MemoryPool::align_up(0, 256), 0);
1213        assert_eq!(MemoryPool::align_up(1, 256), 256);
1214        assert_eq!(MemoryPool::align_up(256, 256), 256);
1215        assert_eq!(MemoryPool::align_up(257, 256), 512);
1216    }
1217
1218    #[test]
1219    fn test_memory_block() {
1220        let block = MemoryBlock {
1221            offset: 0,
1222            size: 1024,
1223            is_free: true,
1224            allocation_id: None,
1225            movable: true,
1226            ref_count: 0,
1227        };
1228
1229        assert!(block.is_free);
1230        assert_eq!(block.size, 1024);
1231        assert!(block.movable);
1232        assert_eq!(block.ref_count, 0);
1233    }
1234}