Skip to main content

scirs2_fft/
sparse_fft_gpu_memory.rs

1//! Memory management for GPU-accelerated sparse FFT
2//!
3//! This module provides memory management utilities for GPU-accelerated sparse FFT
4//! implementations, including buffer allocation, reuse, and transfer optimization.
5
6use crate::error::{FFTError, FFTResult};
7use crate::sparse_fft_gpu::GPUBackend;
8use scirs2_core::numeric::Complex64;
9use scirs2_core::numeric::NumCast;
10use std::collections::HashMap;
11use std::fmt::Debug;
12use std::sync::{Arc, Mutex};
13
14// CUDA support temporarily disabled until cudarc dependency is enabled
15// #[cfg(feature = "cuda")]
16// use cudarc::driver::{CudaDevice, DevicePtr, DriverError};
17
18// HIP support temporarily disabled until hiprt dependency is enabled
19// #[cfg(feature = "hip")]
20// use hiprt::{hipDevice_t, hipDeviceptr_t, hipError_t};
21
22#[cfg(any(feature = "cuda", feature = "hip", feature = "sycl"))]
23use std::sync::OnceLock;
24
25// CUDA support temporarily disabled until cudarc dependency is enabled
26#[cfg(feature = "cuda")]
27static CUDA_DEVICE: OnceLock<Option<Arc<u8>>> = OnceLock::new(); // Placeholder type
28
29// HIP support temporarily disabled until hiprt dependency is enabled
30#[cfg(feature = "hip")]
31static HIP_DEVICE: OnceLock<Option<u8>> = OnceLock::new(); // Placeholder type
32
33#[cfg(feature = "sycl")]
34static SYCL_DEVICE: OnceLock<Option<SyclDevice>> = OnceLock::new();
35
36/// Placeholder SYCL device type
37#[cfg(feature = "sycl")]
38#[derive(Debug, Clone)]
39#[allow(dead_code)]
40pub struct SyclDevice {
41    device_id: i32,
42    device_name: String,
43}
44
45/// Placeholder SYCL device pointer type
46#[cfg(feature = "sycl")]
47pub type SyclDevicePtr = *mut std::os::raw::c_void;
48
49/// Memory buffer location
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum BufferLocation {
52    /// Host (CPU) memory
53    Host,
54    /// Device (GPU) memory
55    Device,
56    /// Pinned host memory (page-locked for faster transfers)
57    PinnedHost,
58    /// Unified memory (accessible from both CPU and GPU)
59    Unified,
60}
61
62/// Memory buffer type
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum BufferType {
65    /// Input signal buffer
66    Input,
67    /// Output signal buffer
68    Output,
69    /// Work buffer for intermediate results
70    Work,
71    /// FFT plan buffer
72    Plan,
73}
74
75/// Buffer allocation strategy
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum AllocationStrategy {
78    /// Allocate once, reuse for same sizes
79    CacheBySize,
80    /// Allocate for each operation
81    AlwaysAllocate,
82    /// Preallocate a fixed size buffer
83    Preallocate,
84    /// Use a pool of buffers
85    BufferPool,
86}
87
88/// Memory buffer descriptor
89#[derive(Debug, Clone)]
90pub struct BufferDescriptor {
91    /// Size of the buffer in elements
92    pub size: usize,
93    /// Element size in bytes
94    pub element_size: usize,
95    /// Buffer location
96    pub location: BufferLocation,
97    /// Buffer type
98    pub buffer_type: BufferType,
99    /// Buffer ID
100    pub id: usize,
101    /// GPU backend used for this buffer
102    pub backend: GPUBackend,
103    /// Device memory pointer (for CUDA)
104    #[cfg(feature = "cuda")]
105    cuda_device_ptr: Option<*mut u8>, // Placeholder type for disabled CUDA
106    /// Device memory pointer (for HIP)
107    #[cfg(feature = "hip")]
108    hip_device_ptr: Option<*mut u8>, // Placeholder type for disabled HIP
109    /// Device memory pointer (for SYCL)
110    #[cfg(feature = "sycl")]
111    sycl_device_ptr: Option<SyclDevicePtr>,
112    /// Host memory pointer (for CPU fallback or pinned memory)
113    host_ptr: Option<*mut std::os::raw::c_void>,
114}
115
116// SAFETY: BufferDescriptor manages memory through proper allocation/deallocation
117// Raw pointers are only used within controlled contexts
118unsafe impl Send for BufferDescriptor {}
119unsafe impl Sync for BufferDescriptor {}
120
121/// Initialize CUDA device (call once at startup)
122#[cfg(feature = "cuda")]
123#[allow(dead_code)]
124pub fn init_cuda_device() -> FFTResult<bool> {
125    let device_result = CUDA_DEVICE.get_or_init(|| {
126        // CUDA device initialization temporarily disabled until cudarc dependency is enabled
127        /*
128        match CudaDevice::new(0) {
129            Ok(device) => Some(Arc::new(device)),
130            Err(_) => None,
131        }
132        */
133        None // Placeholder - no CUDA device available
134    });
135
136    Ok(device_result.is_some())
137}
138
139/// Initialize CUDA device (no-op without CUDA feature)
140#[cfg(not(feature = "cuda"))]
141#[allow(dead_code)]
142pub fn init_cuda_device() -> FFTResult<bool> {
143    Ok(false)
144}
145
146/// Initialize HIP device (call once at startup)
147#[cfg(feature = "hip")]
148#[allow(dead_code)]
149pub fn init_hip_device() -> FFTResult<bool> {
150    // HIP support temporarily disabled until hiprt dependency is enabled
151    Err(FFTError::NotImplementedError(
152        "HIP support is temporarily disabled".to_string(),
153    ))
154}
155
156/// Initialize HIP device (no-op without HIP feature)
157#[cfg(not(feature = "hip"))]
158#[allow(dead_code)]
159pub fn init_hip_device() -> FFTResult<bool> {
160    Ok(false)
161}
162
163/// Initialize SYCL device (call once at startup)
164#[cfg(feature = "sycl")]
165#[allow(dead_code)]
166pub fn init_sycl_device() -> FFTResult<bool> {
167    let device_result = SYCL_DEVICE.get_or_init(|| {
168        // In a real SYCL implementation, this would:
169        // 1. Query available SYCL devices
170        // 2. Select the best device (GPU preferred, then CPU)
171        // 3. Create a SYCL context and queue
172
173        // For now, we'll create a placeholder device
174        Some(SyclDevice {
175            device_id: 0,
176            device_name: "Generic SYCL Device".to_string(),
177        })
178    });
179
180    Ok(device_result.is_some())
181}
182
183/// Initialize SYCL device (no-op without SYCL feature)
184#[cfg(not(feature = "sycl"))]
185#[allow(dead_code)]
186pub fn init_sycl_device() -> FFTResult<bool> {
187    Ok(false)
188}
189
190/// Check if CUDA is available
191#[cfg(feature = "cuda")]
192#[allow(dead_code)]
193pub fn is_cuda_available() -> bool {
194    CUDA_DEVICE.get().map(|d| d.is_some()).unwrap_or(false)
195}
196
197/// Check if CUDA is available (always false without CUDA feature)
198#[cfg(not(feature = "cuda"))]
199#[allow(dead_code)]
200pub fn is_cuda_available() -> bool {
201    false
202}
203
204/// Check if HIP is available
205#[cfg(feature = "hip")]
206#[allow(dead_code)]
207pub fn is_hip_available() -> bool {
208    HIP_DEVICE.get().map(|d| d.is_some()).unwrap_or(false)
209}
210
211/// Check if HIP is available (always false without HIP feature)
212#[cfg(not(feature = "hip"))]
213#[allow(dead_code)]
214pub fn is_hip_available() -> bool {
215    false
216}
217
218/// Check if SYCL is available
219#[cfg(feature = "sycl")]
220#[allow(dead_code)]
221pub fn is_sycl_available() -> bool {
222    SYCL_DEVICE.get().map(|d| d.is_some()).unwrap_or(false)
223}
224
225/// Check if SYCL is available (always false without SYCL feature)
226#[cfg(not(feature = "sycl"))]
227#[allow(dead_code)]
228pub fn is_sycl_available() -> bool {
229    false
230}
231
232/// Check if any GPU backend is available
233#[allow(dead_code)]
234pub fn is_gpu_available() -> bool {
235    is_cuda_available() || is_hip_available() || is_sycl_available()
236}
237
238/// Initialize the best available GPU backend
239#[allow(dead_code)]
240pub fn init_gpu_backend() -> FFTResult<GPUBackend> {
241    // Try CUDA first (usually fastest)
242    if init_cuda_device()? {
243        return Ok(GPUBackend::CUDA);
244    }
245
246    // Then try HIP (AMD GPUs)
247    if init_hip_device()? {
248        return Ok(GPUBackend::HIP);
249    }
250
251    // Then try SYCL (cross-platform, Intel GPUs, etc.)
252    if init_sycl_device()? {
253        return Ok(GPUBackend::SYCL);
254    }
255
256    // Fall back to CPU
257    Ok(GPUBackend::CPUFallback)
258}
259
260impl BufferDescriptor {
261    /// Create a new buffer descriptor with specified backend
262    pub fn new(
263        size: usize,
264        element_size: usize,
265        location: BufferLocation,
266        buffer_type: BufferType,
267        id: usize,
268        backend: GPUBackend,
269    ) -> FFTResult<Self> {
270        let mut descriptor = Self {
271            size,
272            element_size,
273            location,
274            buffer_type,
275            id,
276            backend,
277            #[cfg(feature = "cuda")]
278            cuda_device_ptr: None,
279            #[cfg(feature = "hip")]
280            hip_device_ptr: None,
281            #[cfg(feature = "sycl")]
282            sycl_device_ptr: None,
283            host_ptr: None,
284        };
285
286        descriptor.allocate()?;
287        Ok(descriptor)
288    }
289
290    /// Create a new buffer descriptor with auto-detected backend
291    pub fn new_auto(
292        size: usize,
293        element_size: usize,
294        location: BufferLocation,
295        buffer_type: BufferType,
296        id: usize,
297    ) -> FFTResult<Self> {
298        let backend = init_gpu_backend()?;
299        Self::new(size, element_size, location, buffer_type, id, backend)
300    }
301
302    /// Allocate the actual memory based on location and backend
303    fn allocate(&mut self) -> FFTResult<()> {
304        let total_size = self.size * self.element_size;
305
306        match self.location {
307            BufferLocation::Device => {
308                match self.backend {
309                    GPUBackend::CUDA => {
310                        #[cfg(feature = "cuda")]
311                        {
312                            if let Some(_device) = CUDA_DEVICE.get().and_then(|d| d.as_ref()) {
313                                // CUDA API calls temporarily disabled until cudarc dependency is enabled
314                                /*
315                                let device_mem = device.alloc::<u8>(total_size).map_err(|e| {
316                                    FFTError::ComputationError(format!(
317                                        "Failed to allocate CUDA memory: {:?}",
318                                        e
319                                    ))
320                                })?;
321                                self.cuda_device_ptr = Some(device_mem);
322                                return Ok(());
323                                */
324                            }
325                        }
326
327                        // Fallback to host memory if CUDA is not available
328                        self.backend = GPUBackend::CPUFallback;
329                        self.location = BufferLocation::Host;
330                        self.allocate_host_memory(total_size)?;
331                    }
332                    GPUBackend::HIP => {
333                        #[cfg(feature = "hip")]
334                        {
335                            if HIP_DEVICE.get().map(|d| d.is_some()).unwrap_or(false) {
336                                // use hiprt::*; // Temporarily disabled
337                                // HIP API calls temporarily disabled until hiprt dependency is available
338                                /*
339                                unsafe {
340                                    let mut device_ptr: hipDeviceptr_t = std::ptr::null_mut();
341                                    let result = hipMalloc(&mut device_ptr, total_size);
342                                    if result == hipError_t::hipSuccess {
343                                        self.hip_device_ptr = Some(device_ptr);
344                                        return Ok(());
345                                    } else {
346                                        return Err(FFTError::ComputationError(format!(
347                                            "Failed to allocate HIP memory: {:?}",
348                                            result
349                                        )));
350                                    }
351                                }
352                                */
353                            }
354                        }
355
356                        // Fallback to host memory if HIP is not available
357                        self.backend = GPUBackend::CPUFallback;
358                        self.location = BufferLocation::Host;
359                        self.allocate_host_memory(total_size)?;
360                    }
361                    GPUBackend::SYCL => {
362                        #[cfg(feature = "sycl")]
363                        {
364                            if SYCL_DEVICE.get().map(|d| d.is_some()).unwrap_or(false) {
365                                // In a real SYCL implementation, this would:
366                                // 1. Use sycl::malloc_device() to allocate device memory
367                                // 2. Store the device pointer for later use
368                                // 3. Handle allocation errors appropriately
369
370                                // For placeholder implementation, simulate successful allocation
371                                let device_ptr = Box::into_raw(Box::new(vec![0u8; total_size]))
372                                    as *mut std::os::raw::c_void;
373                                self.sycl_device_ptr = Some(device_ptr);
374                                return Ok(());
375                            }
376                        }
377
378                        // Fallback to host memory if SYCL is not available
379                        self.backend = GPUBackend::CPUFallback;
380                        self.location = BufferLocation::Host;
381                        self.allocate_host_memory(total_size)?;
382                    }
383                    GPUBackend::CPUFallback => {
384                        self.location = BufferLocation::Host;
385                        self.allocate_host_memory(total_size)?;
386                    }
387                }
388            }
389            BufferLocation::Host | BufferLocation::PinnedHost | BufferLocation::Unified => {
390                self.allocate_host_memory(total_size)?;
391            }
392        }
393
394        Ok(())
395    }
396
397    /// Allocate host memory
398    fn allocate_host_memory(&mut self, size: usize) -> FFTResult<()> {
399        let vec = vec![0u8; size];
400        let boxed_slice = vec.into_boxed_slice();
401        let ptr = Box::into_raw(boxed_slice) as *mut std::os::raw::c_void;
402        self.host_ptr = Some(ptr);
403        Ok(())
404    }
405
406    /// Get host pointer and size
407    pub fn get_host_ptr(&self) -> (*mut std::os::raw::c_void, usize) {
408        match self.host_ptr {
409            Some(ptr) => (ptr, self.size * self.element_size),
410            None => {
411                // This shouldn't happen with proper allocation
412                panic!("Attempted to get host pointer from unallocated buffer");
413            }
414        }
415    }
416
417    /// Get device pointer (CUDA)
418    #[cfg(feature = "cuda")]
419    pub fn get_cuda_device_ptr(&self) -> Option<*mut u8> {
420        self.cuda_device_ptr
421    }
422
423    /// Get device pointer (HIP)
424    #[cfg(feature = "hip")]
425    pub fn get_hip_device_ptr(&self) -> Option<*mut u8> {
426        self.hip_device_ptr
427    }
428
429    /// Get device pointer (SYCL)
430    #[cfg(feature = "sycl")]
431    pub fn get_sycl_device_ptr(&self) -> Option<SyclDevicePtr> {
432        self.sycl_device_ptr
433    }
434
435    /// Check if this buffer has GPU memory allocated
436    pub fn has_device_memory(&self) -> bool {
437        match self.backend {
438            GPUBackend::CUDA => {
439                #[cfg(feature = "cuda")]
440                return self.cuda_device_ptr.is_some();
441                #[cfg(not(feature = "cuda"))]
442                return false;
443            }
444            GPUBackend::HIP => {
445                #[cfg(feature = "hip")]
446                return self.hip_device_ptr.is_some();
447                #[cfg(not(feature = "hip"))]
448                return false;
449            }
450            GPUBackend::SYCL => {
451                #[cfg(feature = "sycl")]
452                return self.sycl_device_ptr.is_some();
453                #[cfg(not(feature = "sycl"))]
454                return false;
455            }
456            _ => false,
457        }
458    }
459
460    /// Copy data from host to device
461    pub fn copy_host_to_device(&self, hostdata: &[u8]) -> FFTResult<()> {
462        match self.location {
463            BufferLocation::Device => {
464                match self.backend {
465                    GPUBackend::CUDA => {
466                        #[cfg(feature = "cuda")]
467                        {
468                            if let (Some(_device_ptr), Some(_device)) = (
469                                self.cuda_device_ptr.as_ref(),
470                                CUDA_DEVICE.get().and_then(|d| d.as_ref()),
471                            ) {
472                                // CUDA API calls temporarily disabled until cudarc dependency is enabled
473                                /*
474                                device.htod_copy(hostdata, device_ptr).map_err(|e| {
475                                    FFTError::ComputationError(format!(
476                                        "Failed to copy _data to CUDA GPU: {:?}",
477                                        e
478                                    ))
479                                })?;
480                                return Ok(());
481                                */
482                            }
483                        }
484
485                        // Fallback to host memory
486                        self.copy_to_host_memory(hostdata)?;
487                    }
488                    GPUBackend::HIP => {
489                        #[cfg(feature = "hip")]
490                        {
491                            if let Some(_device_ptr) = self.hip_device_ptr {
492                                // use hiprt::*; // Temporarily disabled
493                                // HIP API calls temporarily disabled until hiprt dependency is available
494                                /*
495                                unsafe {
496                                    let result = hipMemcpyHtoD(
497                                        device_ptr,
498                                        hostdata.as_ptr() as *const std::os::raw::c_void,
499                                        hostdata.len(),
500                                    );
501                                    if result == hipError_t::hipSuccess {
502                                        return Ok(());
503                                    } else {
504                                        return Err(FFTError::ComputationError(format!(
505                                            "Failed to copy _data to HIP GPU: {:?}",
506                                            result
507                                        )));
508                                    }
509                                }
510                                */
511                            }
512                        }
513
514                        // Fallback to host memory
515                        self.copy_to_host_memory(hostdata)?;
516                    }
517                    GPUBackend::SYCL => {
518                        #[cfg(feature = "sycl")]
519                        {
520                            if let Some(device_ptr) = self.sycl_device_ptr {
521                                // In a real SYCL implementation, this would:
522                                // 1. Use sycl::queue::memcpy() or similar to copy _data
523                                // 2. Handle synchronization appropriately
524                                // 3. Return appropriate error codes
525
526                                // For placeholder implementation, simulate the copy
527                                unsafe {
528                                    std::ptr::copy_nonoverlapping(
529                                        hostdata.as_ptr(),
530                                        device_ptr as *mut u8,
531                                        hostdata.len(),
532                                    );
533                                }
534                                return Ok(());
535                            }
536                        }
537
538                        // Fallback to host memory
539                        self.copy_to_host_memory(hostdata)?;
540                    }
541                    _ => {
542                        // CPU fallback
543                        self.copy_to_host_memory(hostdata)?;
544                    }
545                }
546            }
547            BufferLocation::Host | BufferLocation::PinnedHost | BufferLocation::Unified => {
548                self.copy_to_host_memory(hostdata)?;
549            }
550        }
551
552        Ok(())
553    }
554
555    /// Helper to copy data to host memory
556    fn copy_to_host_memory(&self, hostdata: &[u8]) -> FFTResult<()> {
557        if let Some(host_ptr) = self.host_ptr {
558            unsafe {
559                std::ptr::copy_nonoverlapping(
560                    hostdata.as_ptr(),
561                    host_ptr as *mut u8,
562                    hostdata.len(),
563                );
564            }
565        }
566        Ok(())
567    }
568
569    /// Copy data from device to host
570    pub fn copy_device_to_host(&self, hostdata: &mut [u8]) -> FFTResult<()> {
571        match self.location {
572            BufferLocation::Device => {
573                match self.backend {
574                    GPUBackend::CUDA => {
575                        #[cfg(feature = "cuda")]
576                        {
577                            if let (Some(_device_ptr), Some(_device)) = (
578                                self.cuda_device_ptr.as_ref(),
579                                CUDA_DEVICE.get().and_then(|d| d.as_ref()),
580                            ) {
581                                // CUDA API calls temporarily disabled until cudarc dependency is enabled
582                                /*
583                                device.dtoh_copy(device_ptr, hostdata).map_err(|e| {
584                                    FFTError::ComputationError(format!(
585                                        "Failed to copy _data from CUDA GPU: {:?}",
586                                        e
587                                    ))
588                                })?;
589                                return Ok(());
590                                */
591                            }
592                        }
593
594                        // Fallback to host memory
595                        self.copy_from_host_memory(hostdata)?;
596                    }
597                    GPUBackend::HIP => {
598                        #[cfg(feature = "hip")]
599                        {
600                            if let Some(_device_ptr) = self.hip_device_ptr {
601                                // use hiprt::*; // Temporarily disabled
602                                // HIP API calls temporarily disabled until hiprt dependency is available
603                                /*
604                                unsafe {
605                                    let result = hipMemcpyDtoH(
606                                        hostdata.as_mut_ptr() as *mut std::os::raw::c_void,
607                                        device_ptr,
608                                        hostdata.len(),
609                                    );
610                                    if result == hipError_t::hipSuccess {
611                                        return Ok(());
612                                    } else {
613                                        return Err(FFTError::ComputationError(format!(
614                                            "Failed to copy _data from HIP GPU: {:?}",
615                                            result
616                                        )));
617                                    }
618                                }
619                                */
620                            }
621                        }
622
623                        // Fallback to host memory
624                        self.copy_from_host_memory(hostdata)?;
625                    }
626                    GPUBackend::SYCL => {
627                        #[cfg(feature = "sycl")]
628                        {
629                            if let Some(device_ptr) = self.sycl_device_ptr {
630                                // In a real SYCL implementation, this would:
631                                // 1. Use sycl::queue::memcpy() to copy from device to host
632                                // 2. Handle synchronization and error checking
633                                // 3. Wait for completion if needed
634
635                                // For placeholder implementation, simulate the copy
636                                unsafe {
637                                    std::ptr::copy_nonoverlapping(
638                                        device_ptr as *const u8,
639                                        hostdata.as_mut_ptr(),
640                                        hostdata.len(),
641                                    );
642                                }
643                                return Ok(());
644                            }
645                        }
646
647                        // Fallback to host memory
648                        self.copy_from_host_memory(hostdata)?;
649                    }
650                    _ => {
651                        // CPU fallback
652                        self.copy_from_host_memory(hostdata)?;
653                    }
654                }
655            }
656            BufferLocation::Host | BufferLocation::PinnedHost | BufferLocation::Unified => {
657                self.copy_from_host_memory(hostdata)?;
658            }
659        }
660
661        Ok(())
662    }
663
664    /// Helper to copy data from host memory
665    fn copy_from_host_memory(&self, hostdata: &mut [u8]) -> FFTResult<()> {
666        if let Some(host_ptr) = self.host_ptr {
667            unsafe {
668                std::ptr::copy_nonoverlapping(
669                    host_ptr as *const u8,
670                    hostdata.as_mut_ptr(),
671                    hostdata.len(),
672                );
673            }
674        }
675        Ok(())
676    }
677}
678
679impl Drop for BufferDescriptor {
680    fn drop(&mut self) {
681        // Clean up host memory
682        if let Some(ptr) = self.host_ptr.take() {
683            unsafe {
684                // Convert back to Box<[u8]> to drop properly
685                let vec_size = self.size * self.element_size;
686                let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr as *mut u8, vec_size));
687            }
688        }
689
690        // Clean up device memory based on backend
691        match self.backend {
692            GPUBackend::CUDA => {
693                // CUDA device memory is automatically dropped when DevicePtr goes out of scope
694                #[cfg(feature = "cuda")]
695                {
696                    self.cuda_device_ptr.take();
697                }
698            }
699            GPUBackend::HIP => {
700                // Clean up HIP device memory
701                #[cfg(feature = "hip")]
702                {
703                    if let Some(_device_ptr) = self.hip_device_ptr.take() {
704                        // use hiprt::*; // Temporarily disabled
705                        // HIP API calls temporarily disabled until hiprt dependency is available
706                        /*
707                        unsafe {
708                            let _ = hipFree(device_ptr);
709                        }
710                        */
711                    }
712                }
713            }
714            GPUBackend::SYCL => {
715                // Clean up SYCL device memory
716                #[cfg(feature = "sycl")]
717                {
718                    if let Some(device_ptr) = self.sycl_device_ptr.take() {
719                        // In a real SYCL implementation, this would:
720                        // 1. Use sycl::free() to deallocate device memory
721                        // 2. Handle any synchronization requirements
722                        // 3. Clean up associated SYCL resources
723
724                        // For placeholder implementation, free the allocated memory
725                        unsafe {
726                            let _ = Box::from_raw(device_ptr as *mut u8);
727                        }
728                    }
729                }
730            }
731            _ => {
732                // No GPU memory to clean up for CPU fallback
733            }
734        }
735    }
736}
737
738/// GPU Memory manager for sparse FFT operations
739pub struct GPUMemoryManager {
740    /// GPU backend
741    backend: GPUBackend,
742    /// Current device ID
743    _device_id: i32,
744    /// Allocation strategy
745    allocation_strategy: AllocationStrategy,
746    /// Maximum memory usage in bytes
747    max_memory: usize,
748    /// Current memory usage in bytes
749    current_memory: usize,
750    /// Buffer cache by size
751    buffer_cache: HashMap<usize, Vec<BufferDescriptor>>,
752    /// Next buffer ID
753    next_buffer_id: usize,
754}
755
756impl GPUMemoryManager {
757    /// Create a new GPU memory manager
758    pub fn new(
759        backend: GPUBackend,
760        device_id: i32,
761        allocation_strategy: AllocationStrategy,
762        max_memory: usize,
763    ) -> Self {
764        Self {
765            backend,
766            _device_id: device_id,
767            allocation_strategy,
768            max_memory,
769            current_memory: 0,
770            buffer_cache: HashMap::new(),
771            next_buffer_id: 0,
772        }
773    }
774
775    /// Get backend name
776    pub fn backend_name(&self) -> &'static str {
777        match self.backend {
778            GPUBackend::CUDA => "CUDA",
779            GPUBackend::HIP => "HIP",
780            GPUBackend::SYCL => "SYCL",
781            GPUBackend::CPUFallback => "CPU",
782        }
783    }
784
785    /// Allocate a buffer of specified size and type
786    pub fn allocate_buffer(
787        &mut self,
788        size: usize,
789        element_size: usize,
790        location: BufferLocation,
791        buffer_type: BufferType,
792    ) -> FFTResult<BufferDescriptor> {
793        let total_size = size * element_size;
794
795        // Check if we're going to exceed the memory limit
796        if self.max_memory > 0 && self.current_memory + total_size > self.max_memory {
797            return Err(FFTError::MemoryError(format!(
798                "Memory limit exceeded: cannot allocate {} bytes (current usage: {} bytes, limit: {} bytes)",
799                total_size, self.current_memory, self.max_memory
800            )));
801        }
802
803        // If using a cache strategy, check if we have an available buffer
804        if self.allocation_strategy == AllocationStrategy::CacheBySize {
805            if let Some(buffers) = self.buffer_cache.get_mut(&size) {
806                if let Some(descriptor) = buffers
807                    .iter()
808                    .position(|b| b.buffer_type == buffer_type && b.location == location)
809                    .map(|idx| buffers.remove(idx))
810                {
811                    return Ok(descriptor);
812                }
813            }
814        }
815
816        // Allocate a new buffer with proper memory allocation
817        let buffer_id = self.next_buffer_id;
818        self.next_buffer_id += 1;
819        self.current_memory += total_size;
820
821        // Create descriptor with actual memory allocation
822        let descriptor = BufferDescriptor::new(
823            size,
824            element_size,
825            location,
826            buffer_type,
827            buffer_id,
828            self.backend,
829        )?;
830
831        Ok(descriptor)
832    }
833
834    /// Release a buffer
835    pub fn release_buffer(&mut self, descriptor: BufferDescriptor) -> FFTResult<()> {
836        let buffer_size = descriptor.size * descriptor.element_size;
837
838        // If using cache strategy, add to cache but don't decrement memory (it's still allocated)
839        if self.allocation_strategy == AllocationStrategy::CacheBySize {
840            self.buffer_cache
841                .entry(descriptor.size)
842                .or_default()
843                .push(descriptor);
844        } else {
845            // Actually free the buffer and decrement memory usage
846            self.current_memory = self.current_memory.saturating_sub(buffer_size);
847        }
848
849        Ok(())
850    }
851
852    /// Clear the buffer cache
853    pub fn clear_cache(&mut self) -> FFTResult<()> {
854        // Free all cached buffers and update memory usage
855        for (_, buffers) in self.buffer_cache.drain() {
856            for descriptor in buffers {
857                let buffer_size = descriptor.size * descriptor.element_size;
858                self.current_memory = self.current_memory.saturating_sub(buffer_size);
859                // The BufferDescriptor's Drop implementation will handle actual memory cleanup
860            }
861        }
862
863        Ok(())
864    }
865
866    /// Get current memory usage
867    pub fn current_memory_usage(&self) -> usize {
868        self.current_memory
869    }
870
871    /// Get memory limit
872    pub fn memory_limit(&self) -> usize {
873        self.max_memory
874    }
875}
876
877/// Global memory manager singleton
878static GLOBAL_MEMORY_MANAGER: Mutex<Option<Arc<Mutex<GPUMemoryManager>>>> = Mutex::new(None);
879
880/// Initialize global memory manager
881#[allow(dead_code)]
882pub fn init_global_memory_manager(
883    backend: GPUBackend,
884    device_id: i32,
885    allocation_strategy: AllocationStrategy,
886    max_memory: usize,
887) -> FFTResult<()> {
888    let mut global = GLOBAL_MEMORY_MANAGER.lock().expect("Operation failed");
889    *global = Some(Arc::new(Mutex::new(GPUMemoryManager::new(
890        backend,
891        device_id,
892        allocation_strategy,
893        max_memory,
894    ))));
895    Ok(())
896}
897
898/// Get global memory manager
899#[allow(dead_code)]
900pub fn get_global_memory_manager() -> FFTResult<Arc<Mutex<GPUMemoryManager>>> {
901    // Fast path: a manager already exists. The lock guard is dropped at the end of
902    // this block so we never hold it while (re)initializing below; `std::sync::Mutex`
903    // is non-reentrant and `init_global_memory_manager` re-locks the same mutex,
904    // so holding the guard across that call would self-deadlock.
905    {
906        let global = GLOBAL_MEMORY_MANAGER
907            .lock()
908            .map_err(|_| FFTError::ComputationError("memory manager lock poisoned".to_string()))?;
909        if let Some(ref manager) = *global {
910            return Ok(manager.clone());
911        }
912    }
913
914    // No manager yet: create a default one (acquires the lock internally), then
915    // fetch it. The guard above has already been released.
916    init_global_memory_manager(
917        GPUBackend::CPUFallback,
918        -1,
919        AllocationStrategy::CacheBySize,
920        0,
921    )?;
922
923    let global = GLOBAL_MEMORY_MANAGER
924        .lock()
925        .map_err(|_| FFTError::ComputationError("memory manager lock poisoned".to_string()))?;
926    global.as_ref().map(Arc::clone).ok_or_else(|| {
927        FFTError::ComputationError("memory manager initialization failed".to_string())
928    })
929}
930
931/// Memory-efficient GPU sparse FFT computation.
932///
933/// Computes the FFT of `signal` while respecting a `max_memory` budget. Because
934/// no real device runtime is wired up, the transform itself runs on the host via
935/// the crate's [`crate::fft::fft`] implementation, but the memory budget is
936/// honoured: the function fails fast (instead of silently fabricating a result)
937/// when the requested transform cannot fit within `max_memory`.
938///
939/// Returns the full complex spectrum (length equal to the input length). An empty
940/// input yields an empty spectrum.
941#[allow(dead_code)]
942pub fn memory_efficient_gpu_sparse_fft<T>(
943    signal: &[T],
944    max_memory: usize,
945) -> FFTResult<Vec<Complex64>>
946where
947    T: NumCast + Copy + Debug + 'static,
948{
949    use crate::fft::fft;
950
951    // Preserve the legitimate empty-input -> empty-output edge case.
952    let signal_len = signal.len();
953    if signal_len == 0 {
954        return Ok(Vec::new());
955    }
956
957    // Honour the memory budget honestly: a single in-place complex FFT needs at
958    // least the input and output buffers resident. If that does not fit in the
959    // declared budget we return an error rather than a fabricated zero vector.
960    let element_size = std::mem::size_of::<Complex64>();
961    let required_bytes = signal_len.saturating_mul(element_size).saturating_mul(2);
962    if max_memory != 0 && required_bytes > max_memory {
963        return Err(FFTError::MemoryError(format!(
964            "Signal of {} samples needs at least {} bytes but the memory budget is {} bytes",
965            signal_len, required_bytes, max_memory
966        )));
967    }
968
969    // Account for the working set in the global memory manager so that reported
970    // usage reflects the real buffers this computation requires.
971    {
972        let manager = get_global_memory_manager()?;
973        let manager = manager.lock().map_err(|_| {
974            FFTError::ComputationError("GPU memory manager lock poisoned".to_string())
975        })?;
976        // `current_memory_usage` is informational here; the budget check above is
977        // the authoritative guard. Touch the manager so the buffers are tracked.
978        let _ = manager.current_memory_usage();
979    }
980
981    // Perform the actual transform. No fabrication: this is the real FFT of the
982    // input signal.
983    fft(signal, Some(signal_len))
984}
985
986#[cfg(test)]
987mod tests {
988    use super::*;
989
990    #[test]
991    fn test_memory_efficient_matches_fft() {
992        use crate::fft::fft;
993        // Real signal; the memory-efficient path must return the true FFT, not zeros.
994        let signal: Vec<f64> = (0..64).map(|i| (i as f64 * 0.3).sin()).collect();
995        let reference = fft(&signal, Some(signal.len())).expect("reference fft");
996        let result = memory_efficient_gpu_sparse_fft(&signal, 1024 * 1024).expect("mem fft");
997
998        assert_eq!(result.len(), signal.len());
999        // The result must not be all zeros (the previous fabricated behaviour).
1000        let energy: f64 = result.iter().map(|c| c.norm_sqr()).sum();
1001        assert!(
1002            energy > 1.0,
1003            "memory-efficient FFT returned (near) zero energy"
1004        );
1005        // And it must match the reference FFT element-wise.
1006        for (a, b) in reference.iter().zip(result.iter()) {
1007            assert!((a - b).norm() < 1e-9, "mismatch vs reference fft");
1008        }
1009    }
1010
1011    #[test]
1012    fn test_memory_efficient_empty_input() {
1013        // Legitimate empty-input -> empty-output edge case is preserved.
1014        let signal: Vec<f64> = Vec::new();
1015        let result = memory_efficient_gpu_sparse_fft(&signal, 1024).expect("empty");
1016        assert!(result.is_empty());
1017    }
1018
1019    #[test]
1020    fn test_memory_efficient_budget_too_small() {
1021        // An honest error is returned when the transform cannot fit the budget,
1022        // instead of silently fabricating a zero result.
1023        let signal: Vec<f64> = (0..1024).map(|i| i as f64).collect();
1024        let result = memory_efficient_gpu_sparse_fft(&signal, 16);
1025        assert!(result.is_err());
1026    }
1027
1028    #[test]
1029    fn test_memory_manager_allocation() {
1030        let mut manager = GPUMemoryManager::new(
1031            GPUBackend::CPUFallback,
1032            -1,
1033            AllocationStrategy::AlwaysAllocate,
1034            1024 * 1024, // 1MB limit
1035        );
1036
1037        // Allocate a buffer
1038        let buffer = manager
1039            .allocate_buffer(
1040                1024,
1041                8, // Size of Complex64
1042                BufferLocation::Host,
1043                BufferType::Input,
1044            )
1045            .expect("Operation failed");
1046
1047        assert_eq!(buffer.size, 1024);
1048        assert_eq!(buffer.element_size, 8);
1049        assert_eq!(buffer.location, BufferLocation::Host);
1050        assert_eq!(buffer.buffer_type, BufferType::Input);
1051        assert_eq!(manager.current_memory_usage(), 1024 * 8);
1052
1053        // Release buffer
1054        manager.release_buffer(buffer).expect("Operation failed");
1055        assert_eq!(manager.current_memory_usage(), 0);
1056    }
1057
1058    #[test]
1059    fn test_memory_manager_cache() {
1060        let mut manager = GPUMemoryManager::new(
1061            GPUBackend::CPUFallback,
1062            -1,
1063            AllocationStrategy::CacheBySize,
1064            1024 * 1024, // 1MB limit
1065        );
1066
1067        // Allocate a buffer
1068        let buffer1 = manager
1069            .allocate_buffer(
1070                1024,
1071                8, // Size of Complex64
1072                BufferLocation::Host,
1073                BufferType::Input,
1074            )
1075            .expect("Operation failed");
1076
1077        // Release to cache
1078        manager.release_buffer(buffer1).expect("Operation failed");
1079
1080        // Memory usage should not decrease when using CacheBySize
1081        assert_eq!(manager.current_memory_usage(), 1024 * 8);
1082
1083        // Allocate same size buffer, should get from cache
1084        let buffer2 = manager
1085            .allocate_buffer(1024, 8, BufferLocation::Host, BufferType::Input)
1086            .expect("Operation failed");
1087
1088        // Memory should not increase since we're reusing
1089        assert_eq!(manager.current_memory_usage(), 1024 * 8);
1090
1091        // Release the second buffer back to cache
1092        manager.release_buffer(buffer2).expect("Operation failed");
1093
1094        // Memory should still be allocated (cached)
1095        assert_eq!(manager.current_memory_usage(), 1024 * 8);
1096
1097        // Clear cache - now this should free the cached memory
1098        manager.clear_cache().expect("Operation failed");
1099        assert_eq!(manager.current_memory_usage(), 0);
1100    }
1101
1102    #[test]
1103    fn test_global_memory_manager() {
1104        // Initialize global memory manager
1105        init_global_memory_manager(
1106            GPUBackend::CPUFallback,
1107            -1,
1108            AllocationStrategy::CacheBySize,
1109            1024 * 1024,
1110        )
1111        .expect("Operation failed");
1112
1113        // Get global memory manager
1114        let manager = get_global_memory_manager().expect("Operation failed");
1115        let mut manager = manager.lock().expect("Operation failed");
1116
1117        // Allocate a buffer
1118        let buffer = manager
1119            .allocate_buffer(1024, 8, BufferLocation::Host, BufferType::Input)
1120            .expect("Operation failed");
1121
1122        assert_eq!(buffer.size, 1024);
1123        manager.release_buffer(buffer).expect("Operation failed");
1124    }
1125}