Skip to main content

optirs_gpu/memory/vendors/
cuda_backend.rs

1// CUDA backend for GPU memory management
2//
3// This module provides NVIDIA CUDA-specific memory management functionality,
4// including device memory allocation, unified memory, streams, and performance
5// optimization features specific to CUDA GPUs.
6//
7// # This is a host-memory simulation, not real CUDA
8//
9// `optirs-gpu` is Pure Rust with no FFI dependencies by default, and
10// `scirs2-core` 0.6.x removed its CUDA backend entirely (see
11// `crate::optimizers`'s module docs). There is therefore no real `cudaMalloc`
12// underneath this module: "device", "host", "unified" and "mapped" memory
13// are all the *same* system-heap allocation (see `sim_alloc`/`sim_dealloc`
14// below), and `CudaDeviceProperties`/`CudaStats` are example numbers, not a
15// query of real hardware. This module models the CUDA memory-management
16// *API shape* (pools, streams, statistics) for testing that shape in
17// isolation; treat every allocation as host memory and every device number
18// as illustrative. Real CUDA execution belongs in the `oxicuda-*` crates,
19// feature-gated off by default per COOLJAPAN policy.
20//
21// This extends to data movement: `memcpy` and `memcpy_async` copy **zero
22// bytes**. They build a `CudaOperation` record, hand it to
23// `CudaStreamManager::execute_operation` (which returns immediately and
24// never dereferences `src_ptr`/`dst_ptr`), and increment
25// `CudaStats::memory_transfers` — that counter says "this many `memcpy`
26// calls were made," not "this many bytes moved." An earlier revision of
27// this module also injected a `std::thread::sleep` here to imitate transfer
28// latency by operation kind; that fake timing has been removed, so the
29// distinction between `MemcpyHostToDevice`/`MemcpyDeviceToHost`/
30// `MemcpyDeviceToDevice`/`MemcpyAsync` no longer affects anything
31// observable. `CudaStats::stream_operations` and `CudaStats::kernel_launches`
32// are declared for API-shape completeness but nothing in this module ever
33// increments them — read a `0` there as "not tracked," not "none occurred."
34
35use std::collections::HashMap;
36use std::ffi::c_void;
37use std::sync::{Arc, Mutex};
38use std::time::{Duration, Instant};
39
40/// Byte alignment every simulated allocation below uses.
41const SIM_ALLOC_ALIGN: usize = 256;
42
43/// Allocate `size` bytes through the system allocator, 256-byte aligned,
44/// without the two ways the naive `std::alloc::alloc(Layout::from_size_align_unchecked(size,
45/// 256))` this module used to call was undefined behaviour:
46///
47/// * `GlobalAlloc::alloc`'s safety contract requires a *non-zero*-size
48///   layout; `size == 0` is handled here as the same "dangling, well-aligned,
49///   never-dereferenced sentinel" convention `Vec`/`Box` use for zero-sized
50///   allocations, without calling the allocator at all.
51/// * A real allocation failure returns a null pointer, which the caller must
52///   never treat as valid memory; this returns `Err` instead of a wrapped
53///   null.
54///
55/// The payload is prefixed with one `SIM_ALLOC_ALIGN`-byte header that
56/// records the requested size, so [`sim_dealloc`] can reconstruct the exact
57/// `Layout` this function used — deallocating with a *different* layout than
58/// the one used to allocate is itself undefined behaviour, which the
59/// `Layout::from_size_align_unchecked(1, 1)` this module used at free time
60/// was unconditionally invoking.
61fn sim_alloc(size: usize) -> Result<*mut c_void, CudaError> {
62    if size == 0 {
63        return Ok(SIM_ALLOC_ALIGN as *mut c_void);
64    }
65    let total = SIM_ALLOC_ALIGN.checked_add(size).ok_or_else(|| {
66        CudaError::OutOfMemory(format!(
67            "{size}-byte request overflows the allocator's size limit"
68        ))
69    })?;
70    let layout = std::alloc::Layout::from_size_align(total, SIM_ALLOC_ALIGN)
71        .map_err(|e| CudaError::OutOfMemory(format!("invalid allocation layout: {e}")))?;
72    // SAFETY: `layout` has non-zero size (checked above) and a valid
73    // (power-of-two) alignment constructed by `Layout::from_size_align`.
74    let base = unsafe { std::alloc::alloc(layout) };
75    if base.is_null() {
76        return Err(CudaError::OutOfMemory(format!(
77            "allocator returned null for a {size}-byte request"
78        )));
79    }
80    // SAFETY: `base` is non-null and `layout`'s size is at least
81    // `SIM_ALLOC_ALIGN + size >= SIM_ALLOC_ALIGN >= size_of::<usize>()`, so
82    // writing one `usize` at the start of the block is in-bounds.
83    unsafe { (base as *mut usize).write(size) };
84    // SAFETY: `base` was allocated with `total = SIM_ALLOC_ALIGN + size`
85    // bytes, so offsetting by `SIM_ALLOC_ALIGN` stays within (or one past)
86    // the allocation.
87    Ok(unsafe { base.add(SIM_ALLOC_ALIGN) } as *mut c_void)
88}
89
90/// Free a pointer returned by [`sim_alloc`]. A no-op for a null pointer or
91/// the zero-size sentinel — neither was ever allocated, so neither is passed
92/// to the system allocator.
93///
94/// Only ever called from this module (it is not `pub`) with a pointer
95/// `sim_alloc` returned that has not already been freed — the unsafety of
96/// the pointer arithmetic below is contained to that invariant, matching
97/// this module's existing style of confining `unsafe` to the raw
98/// `std::alloc` calls rather than marking `free()`'s public wrapper unsafe.
99fn sim_dealloc(ptr: *mut c_void) {
100    if ptr.is_null() || (ptr as usize) == SIM_ALLOC_ALIGN {
101        return;
102    }
103    // SAFETY: by this function's contract `ptr` came from `sim_alloc`, which
104    // always returns `base + SIM_ALLOC_ALIGN` for a real allocation, so
105    // stepping back `SIM_ALLOC_ALIGN` bytes recovers `base`.
106    let base = unsafe { (ptr as *mut u8).sub(SIM_ALLOC_ALIGN) };
107    // SAFETY: `sim_alloc` wrote a `usize` at `base` before returning.
108    let size = unsafe { (base as *const usize).read() };
109    if let Ok(layout) = std::alloc::Layout::from_size_align(SIM_ALLOC_ALIGN + size, SIM_ALLOC_ALIGN)
110    {
111        // SAFETY: `layout` is exactly the layout `sim_alloc` allocated
112        // `base` with.
113        unsafe { std::alloc::dealloc(base, layout) };
114    }
115}
116
117/// CUDA memory backend implementation
118pub struct CudaMemoryBackend {
119    /// Backend configuration
120    config: CudaConfig,
121    /// Device properties
122    device_properties: CudaDeviceProperties,
123    /// Active memory contexts
124    contexts: HashMap<u32, CudaContext>,
125    /// Memory pools
126    memory_pools: HashMap<CudaMemoryType, CudaMemoryPool>,
127    /// Statistics
128    stats: CudaStats,
129    /// Stream management
130    stream_manager: CudaStreamManager,
131}
132
133/// CUDA backend configuration
134#[derive(Debug, Clone)]
135pub struct CudaConfig {
136    /// Device ID to use
137    pub device_id: u32,
138    /// Enable unified memory
139    pub enable_unified_memory: bool,
140    /// Enable memory pools
141    pub enable_memory_pools: bool,
142    /// Enable async memory operations
143    pub enable_async_ops: bool,
144    /// Memory pool growth size
145    pub pool_growth_size: usize,
146    /// Enable memory mapped host memory
147    pub enable_mapped_memory: bool,
148    /// Enable CUDA graphs for memory ops
149    pub enable_cuda_graphs: bool,
150    /// Enable cooperative groups
151    pub enable_cooperative_groups: bool,
152    /// Maximum number of streams
153    pub max_streams: u32,
154}
155
156impl Default for CudaConfig {
157    fn default() -> Self {
158        Self {
159            device_id: 0,
160            enable_unified_memory: true,
161            enable_memory_pools: true,
162            enable_async_ops: true,
163            pool_growth_size: 64 * 1024 * 1024, // 64MB
164            enable_mapped_memory: true,
165            enable_cuda_graphs: false, // Experimental
166            enable_cooperative_groups: false,
167            max_streams: 16,
168        }
169    }
170}
171
172/// CUDA device properties
173#[derive(Debug, Clone)]
174pub struct CudaDeviceProperties {
175    pub device_id: u32,
176    pub name: String,
177    pub compute_capability: (u32, u32),
178    pub total_global_memory: usize,
179    pub shared_memory_per_block: usize,
180    pub warp_size: u32,
181    pub max_threads_per_block: u32,
182    pub max_blocks_per_multiprocessor: u32,
183    pub multiprocessor_count: u32,
184    pub memory_clock_rate: u32,
185    pub memory_bus_width: u32,
186    pub l2_cache_size: usize,
187    pub unified_addressing: bool,
188    pub managed_memory: bool,
189    pub concurrent_kernels: bool,
190    pub async_engine_count: u32,
191}
192
193/// CUDA memory types
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195pub enum CudaMemoryType {
196    Device,
197    Host,
198    Unified,
199    Mapped,
200    Array,
201    Texture,
202}
203
204/// CUDA context for managing device state
205pub struct CudaContext {
206    /// Context handle (simulated)
207    pub handle: *mut c_void,
208    /// Device ID
209    pub device_id: u32,
210    /// Context flags
211    pub flags: CudaContextFlags,
212    /// Creation time
213    pub created_at: Instant,
214    /// Active streams
215    pub streams: Vec<CudaStream>,
216}
217
218/// CUDA context creation flags
219#[derive(Debug, Clone)]
220pub struct CudaContextFlags {
221    pub sched_auto: bool,
222    pub sched_spin: bool,
223    pub sched_yield: bool,
224    pub sched_blocking_sync: bool,
225    pub map_host: bool,
226    pub lmem_resize_to_max: bool,
227}
228
229impl Default for CudaContextFlags {
230    fn default() -> Self {
231        Self {
232            sched_auto: true,
233            sched_spin: false,
234            sched_yield: false,
235            sched_blocking_sync: false,
236            map_host: false,
237            lmem_resize_to_max: false,
238        }
239    }
240}
241
242/// CUDA stream for asynchronous operations
243pub struct CudaStream {
244    /// Stream handle (simulated)
245    pub handle: *mut c_void,
246    /// Stream ID
247    pub id: u32,
248    /// Stream priority
249    pub priority: i32,
250    /// Stream flags
251    pub flags: CudaStreamFlags,
252    /// Creation time
253    pub created_at: Instant,
254    /// Operations queue
255    pub operations: std::collections::VecDeque<CudaOperation>,
256}
257
258impl std::fmt::Debug for CudaStream {
259    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260        f.debug_struct("CudaStream")
261            .field("handle", &format!("{:p}", self.handle))
262            .field("id", &self.id)
263            .field("priority", &self.priority)
264            .field("flags", &self.flags)
265            .field("created_at", &self.created_at)
266            .field("operations", &self.operations)
267            .finish()
268    }
269}
270
271/// CUDA stream flags
272#[derive(Debug, Clone)]
273pub struct CudaStreamFlags {
274    pub default: bool,
275    pub non_blocking: bool,
276    pub per_thread: bool,
277}
278
279impl Default for CudaStreamFlags {
280    fn default() -> Self {
281        Self {
282            default: true,
283            non_blocking: false,
284            per_thread: false,
285        }
286    }
287}
288
289/// CUDA asynchronous operation
290#[derive(Debug, Clone)]
291pub struct CudaOperation {
292    pub op_type: CudaOperationType,
293    pub src_ptr: Option<*mut c_void>,
294    pub dst_ptr: Option<*mut c_void>,
295    pub size: usize,
296    pub timestamp: Instant,
297}
298
299/// Types of CUDA operations
300#[derive(Debug, Clone)]
301pub enum CudaOperationType {
302    MemcpyHostToDevice,
303    MemcpyDeviceToHost,
304    MemcpyDeviceToDevice,
305    MemcpyAsync,
306    MemsetAsync,
307    KernelLaunch,
308    EventRecord,
309    EventSynchronize,
310}
311
312/// CUDA memory pool
313pub struct CudaMemoryPool {
314    /// Memory type
315    memory_type: CudaMemoryType,
316    /// Current size
317    current_size: usize,
318    /// Maximum size
319    max_size: usize,
320    /// Used size
321    used_size: usize,
322    /// Free blocks
323    free_blocks: std::collections::VecDeque<CudaMemoryBlock>,
324    /// Allocated blocks
325    allocated_blocks: HashMap<*mut c_void, CudaMemoryBlock>,
326}
327
328/// CUDA memory block
329#[derive(Debug, Clone)]
330pub struct CudaMemoryBlock {
331    pub ptr: *mut c_void,
332    pub size: usize,
333    pub memory_type: CudaMemoryType,
334    pub allocated_at: Instant,
335    pub last_access: Option<Instant>,
336    pub ref_count: u32,
337}
338
339impl CudaMemoryPool {
340    pub fn new(memory_type: CudaMemoryType, max_size: usize) -> Self {
341        Self {
342            memory_type,
343            current_size: 0,
344            max_size,
345            used_size: 0,
346            free_blocks: std::collections::VecDeque::new(),
347            allocated_blocks: HashMap::new(),
348        }
349    }
350
351    /// Allocate from pool
352    pub fn allocate(&mut self, size: usize) -> Result<*mut c_void, CudaError> {
353        // Try to find suitable free block
354        for i in 0..self.free_blocks.len() {
355            if self.free_blocks[i].size >= size {
356                let Some(mut block) = self.free_blocks.remove(i) else {
357                    continue;
358                };
359
360                // Split block if much larger
361                if block.size > size * 2 {
362                    let remaining_block = CudaMemoryBlock {
363                        ptr: unsafe { block.ptr.add(size) },
364                        size: block.size - size,
365                        memory_type: block.memory_type.clone(),
366                        allocated_at: block.allocated_at,
367                        last_access: None,
368                        ref_count: 0,
369                    };
370                    self.free_blocks.push_back(remaining_block);
371                    block.size = size;
372                }
373
374                block.last_access = Some(Instant::now());
375                block.ref_count = 1;
376
377                let ptr = block.ptr;
378                self.allocated_blocks.insert(ptr, block);
379                self.used_size += size;
380
381                return Ok(ptr);
382            }
383        }
384
385        // Need to allocate new memory
386        if self.current_size + size > self.max_size {
387            return Err(CudaError::OutOfMemory(
388                "Pool size limit exceeded".to_string(),
389            ));
390        }
391
392        let ptr = self.cuda_malloc(size)?;
393        let block = CudaMemoryBlock {
394            ptr,
395            size,
396            memory_type: self.memory_type.clone(),
397            allocated_at: Instant::now(),
398            last_access: Some(Instant::now()),
399            ref_count: 1,
400        };
401
402        self.allocated_blocks.insert(ptr, block);
403        self.current_size += size;
404        self.used_size += size;
405
406        Ok(ptr)
407    }
408
409    /// Free back to pool
410    pub fn free(&mut self, ptr: *mut c_void) -> Result<(), CudaError> {
411        if let Some(block) = self.allocated_blocks.remove(&ptr) {
412            self.used_size -= block.size;
413
414            // Add to free blocks
415            self.free_blocks.push_back(CudaMemoryBlock {
416                ptr: block.ptr,
417                size: block.size,
418                memory_type: block.memory_type,
419                allocated_at: block.allocated_at,
420                last_access: None,
421                ref_count: 0,
422            });
423
424            // Try to coalesce adjacent blocks
425            self.coalesce_free_blocks();
426
427            Ok(())
428        } else {
429            Err(CudaError::InvalidPointer(
430                "Pointer not found in pool".to_string(),
431            ))
432        }
433    }
434
435    fn coalesce_free_blocks(&mut self) {
436        // Sort free blocks by address
437        let mut blocks: Vec<CudaMemoryBlock> = self.free_blocks.drain(..).collect();
438        blocks.sort_by_key(|block| block.ptr as usize);
439
440        let mut coalesced = Vec::new();
441        let mut current_block: Option<CudaMemoryBlock> = None;
442
443        for block in blocks {
444            match current_block.take() {
445                None => current_block = Some(block),
446                Some(mut prev_block) => {
447                    let prev_end = prev_block.ptr as usize + prev_block.size;
448                    let block_start = block.ptr as usize;
449
450                    if prev_end == block_start {
451                        // Coalesce blocks
452                        prev_block.size += block.size;
453                        current_block = Some(prev_block);
454                    } else {
455                        coalesced.push(prev_block);
456                        current_block = Some(block);
457                    }
458                }
459            }
460        }
461
462        if let Some(block) = current_block {
463            coalesced.push(block);
464        }
465
466        self.free_blocks = coalesced.into();
467    }
468
469    fn cuda_malloc(&self, size: usize) -> Result<*mut c_void, CudaError> {
470        // Simulate CUDA memory allocation
471        match self.memory_type {
472            CudaMemoryType::Device => sim_alloc(size), // cudaMalloc equivalent
473            CudaMemoryType::Host => sim_alloc(size),   // cudaMallocHost equivalent
474            CudaMemoryType::Unified => sim_alloc(size), // cudaMallocManaged equivalent
475            CudaMemoryType::Mapped => sim_alloc(size), // cudaHostAlloc with mapping flags
476            _ => Err(CudaError::UnsupportedOperation(
477                "Unsupported memory type for allocation".to_string(),
478            )),
479        }
480    }
481}
482
483/// CUDA stream manager
484pub struct CudaStreamManager {
485    /// Available streams
486    streams: Vec<CudaStream>,
487    /// Stream pool for reuse
488    stream_pool: std::collections::VecDeque<CudaStream>,
489    /// Next stream ID
490    next_stream_id: u32,
491    /// Configuration
492    config: CudaStreamConfig,
493}
494
495/// Stream manager configuration
496#[derive(Debug, Clone)]
497pub struct CudaStreamConfig {
498    pub default_priority: i32,
499    pub enable_priorities: bool,
500    pub max_operations_per_stream: usize,
501}
502
503impl Default for CudaStreamConfig {
504    fn default() -> Self {
505        Self {
506            default_priority: 0,
507            enable_priorities: true,
508            max_operations_per_stream: 1000,
509        }
510    }
511}
512
513impl CudaStreamManager {
514    pub fn new(config: CudaStreamConfig) -> Self {
515        Self {
516            streams: Vec::new(),
517            stream_pool: std::collections::VecDeque::new(),
518            next_stream_id: 0,
519            config,
520        }
521    }
522
523    /// Create new stream
524    ///
525    /// Reuses a previously [`Self::destroy_stream`]d stream from
526    /// `stream_pool` when one is available (its operation queue is cleared
527    /// and it is given a fresh ID) instead of always allocating a new one.
528    pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, CudaError> {
529        let stream_id = self.next_stream_id;
530        self.next_stream_id += 1;
531
532        let mut stream = self.stream_pool.pop_front().unwrap_or_else(|| CudaStream {
533            handle: std::ptr::null_mut(), // Would be actual CUDA stream
534            id: stream_id,
535            priority: priority.unwrap_or(self.config.default_priority),
536            flags: CudaStreamFlags::default(),
537            created_at: Instant::now(),
538            operations: std::collections::VecDeque::new(),
539        });
540        stream.id = stream_id;
541        stream.priority = priority.unwrap_or(self.config.default_priority);
542        stream.created_at = Instant::now();
543        stream.operations.clear();
544
545        self.streams.push(stream);
546        Ok(stream_id)
547    }
548
549    /// Destroy stream
550    ///
551    /// Returns the stream to `stream_pool` for [`Self::create_stream`] to
552    /// reuse instead of dropping it outright.
553    pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), CudaError> {
554        if let Some(pos) = self.streams.iter().position(|s| s.id == stream_id) {
555            let stream = self.streams.remove(pos);
556            self.stream_pool.push_back(stream);
557            Ok(())
558        } else {
559            Err(CudaError::InvalidStream("Stream not found".to_string()))
560        }
561    }
562
563    /// Add operation to stream
564    pub fn add_operation(
565        &mut self,
566        stream_id: u32,
567        operation: CudaOperation,
568    ) -> Result<(), CudaError> {
569        if let Some(stream) = self.streams.iter_mut().find(|s| s.id == stream_id) {
570            if stream.operations.len() >= self.config.max_operations_per_stream {
571                return Err(CudaError::StreamFull(
572                    "Stream operation queue is full".to_string(),
573                ));
574            }
575
576            stream.operations.push_back(operation);
577            Ok(())
578        } else {
579            Err(CudaError::InvalidStream("Stream not found".to_string()))
580        }
581    }
582
583    /// Synchronize stream
584    pub fn synchronize_stream(&mut self, stream_id: u32) -> Result<(), CudaError> {
585        // First, collect all operations from the stream
586        let mut operations = Vec::new();
587        if let Some(stream) = self.streams.iter_mut().find(|s| s.id == stream_id) {
588            while let Some(operation) = stream.operations.pop_front() {
589                operations.push(operation);
590            }
591        } else {
592            return Err(CudaError::InvalidStream("Stream not found".to_string()));
593        }
594
595        // Now execute all operations
596        for operation in operations {
597            self.execute_operation(operation)?;
598        }
599
600        Ok(())
601    }
602
603    fn execute_operation(&self, _operation: CudaOperation) -> Result<(), CudaError> {
604        // Host-memory simulation (see module docs): there is no real CUDA
605        // device to transfer to or from, so no data movement happens here.
606        // This used to also inject an artificial `std::thread::sleep` per
607        // operation type to mimic device-transfer latency; that fake timing
608        // has been removed rather than left as an undisclosed simulated
609        // number, so callers now see the true (near-zero) cost of this
610        // simulation instead of a fabricated one.
611        Ok(())
612    }
613}
614
615/// CUDA statistics
616#[derive(Debug, Clone, Default)]
617pub struct CudaStats {
618    pub total_allocations: u64,
619    pub total_deallocations: u64,
620    pub bytes_allocated: u64,
621    pub bytes_deallocated: u64,
622    pub device_memory_used: usize,
623    pub host_memory_used: usize,
624    pub unified_memory_used: usize,
625    pub stream_operations: u64,
626    pub kernel_launches: u64,
627    pub memory_transfers: u64,
628    pub average_allocation_time: Duration,
629    pub peak_memory_usage: usize,
630}
631
632impl CudaMemoryBackend {
633    /// Create new CUDA backend
634    pub fn new(config: CudaConfig) -> Result<Self, CudaError> {
635        // Initialize CUDA device
636        let device_properties = Self::query_device_properties(config.device_id)?;
637
638        // Create memory pools
639        let mut memory_pools = HashMap::new();
640        if config.enable_memory_pools {
641            let pool_size = device_properties.total_global_memory / 4; // Use 1/4 of total memory
642            memory_pools.insert(
643                CudaMemoryType::Device,
644                CudaMemoryPool::new(CudaMemoryType::Device, pool_size),
645            );
646            memory_pools.insert(
647                CudaMemoryType::Host,
648                CudaMemoryPool::new(CudaMemoryType::Host, pool_size),
649            );
650
651            if config.enable_unified_memory && device_properties.managed_memory {
652                memory_pools.insert(
653                    CudaMemoryType::Unified,
654                    CudaMemoryPool::new(CudaMemoryType::Unified, pool_size),
655                );
656            }
657        }
658
659        let stream_manager = CudaStreamManager::new(CudaStreamConfig::default());
660
661        Ok(Self {
662            config,
663            device_properties,
664            contexts: HashMap::new(),
665            memory_pools,
666            stats: CudaStats::default(),
667            stream_manager,
668        })
669    }
670
671    /// Query device properties
672    fn query_device_properties(device_id: u32) -> Result<CudaDeviceProperties, CudaError> {
673        // Simulate querying CUDA device properties
674        Ok(CudaDeviceProperties {
675            device_id,
676            name: format!("CUDA Device {}", device_id),
677            compute_capability: (7, 5), // Simulate Turing architecture
678            total_global_memory: 8 * 1024 * 1024 * 1024, // 8GB
679            shared_memory_per_block: 48 * 1024, // 48KB
680            warp_size: 32,
681            max_threads_per_block: 1024,
682            max_blocks_per_multiprocessor: 16,
683            multiprocessor_count: 68,
684            memory_clock_rate: 7001000, // 7 GHz
685            memory_bus_width: 256,
686            l2_cache_size: 4 * 1024 * 1024, // 4MB
687            unified_addressing: true,
688            managed_memory: true,
689            concurrent_kernels: true,
690            async_engine_count: 2,
691        })
692    }
693
694    /// Allocate device memory
695    pub fn allocate(
696        &mut self,
697        size: usize,
698        memory_type: CudaMemoryType,
699    ) -> Result<*mut c_void, CudaError> {
700        let start_time = Instant::now();
701
702        let ptr = if self.config.enable_memory_pools {
703            if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
704                pool.allocate(size)?
705            } else {
706                return Err(CudaError::UnsupportedMemoryType(
707                    "Memory type not supported".to_string(),
708                ));
709            }
710        } else {
711            // Direct allocation
712            self.direct_allocate(size, memory_type.clone())?
713        };
714
715        // Update statistics
716        self.stats.total_allocations += 1;
717        self.stats.bytes_allocated += size as u64;
718
719        match memory_type {
720            CudaMemoryType::Device => self.stats.device_memory_used += size,
721            CudaMemoryType::Host => self.stats.host_memory_used += size,
722            CudaMemoryType::Unified => self.stats.unified_memory_used += size,
723            _ => {}
724        }
725
726        let allocation_time = start_time.elapsed();
727        let total_time = self.stats.average_allocation_time.as_nanos() as u64
728            * (self.stats.total_allocations - 1)
729            + allocation_time.as_nanos() as u64;
730        self.stats.average_allocation_time =
731            Duration::from_nanos(total_time / self.stats.total_allocations);
732
733        let current_usage = self.stats.device_memory_used
734            + self.stats.host_memory_used
735            + self.stats.unified_memory_used;
736        if current_usage > self.stats.peak_memory_usage {
737            self.stats.peak_memory_usage = current_usage;
738        }
739
740        Ok(ptr)
741    }
742
743    fn direct_allocate(
744        &self,
745        size: usize,
746        memory_type: CudaMemoryType,
747    ) -> Result<*mut c_void, CudaError> {
748        // Simulate direct CUDA allocation
749        match memory_type {
750            CudaMemoryType::Device => sim_alloc(size), // cudaMalloc
751            CudaMemoryType::Host => sim_alloc(size),   // cudaMallocHost
752            CudaMemoryType::Unified => {
753                // cudaMallocManaged
754                if !self.device_properties.managed_memory {
755                    return Err(CudaError::UnsupportedOperation(
756                        "Unified memory not supported".to_string(),
757                    ));
758                }
759                sim_alloc(size)
760            }
761            _ => Err(CudaError::UnsupportedMemoryType(
762                "Unsupported memory type".to_string(),
763            )),
764        }
765    }
766
767    /// Free device memory
768    pub fn free(&mut self, ptr: *mut c_void, memory_type: CudaMemoryType) -> Result<(), CudaError> {
769        if self.config.enable_memory_pools {
770            if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
771                pool.free(ptr)?;
772            } else {
773                return Err(CudaError::UnsupportedMemoryType(
774                    "Memory type not supported".to_string(),
775                ));
776            }
777        } else {
778            // Direct deallocation. `ptr` was returned by `sim_alloc` via
779            // `cuda_malloc`/`direct_allocate` above (the only producers of
780            // pointers this path frees), and this is the first time it is
781            // freed — `free` is not reentrant-called for the same pointer.
782            sim_dealloc(ptr);
783        }
784
785        self.stats.total_deallocations += 1;
786        Ok(())
787    }
788
789    /// Copy memory
790    pub fn memcpy(
791        &mut self,
792        dst: *mut c_void,
793        src: *const c_void,
794        size: usize,
795        kind: CudaMemcpyKind,
796    ) -> Result<(), CudaError> {
797        let operation = CudaOperation {
798            op_type: match kind {
799                CudaMemcpyKind::HostToDevice => CudaOperationType::MemcpyHostToDevice,
800                CudaMemcpyKind::DeviceToHost => CudaOperationType::MemcpyDeviceToHost,
801                CudaMemcpyKind::DeviceToDevice => CudaOperationType::MemcpyDeviceToDevice,
802                CudaMemcpyKind::HostToHost => CudaOperationType::MemcpyAsync,
803            },
804            src_ptr: Some(src as *mut c_void),
805            dst_ptr: Some(dst),
806            size,
807            timestamp: Instant::now(),
808        };
809
810        // Execute synchronously for now
811        self.stream_manager.execute_operation(operation)?;
812        self.stats.memory_transfers += 1;
813
814        Ok(())
815    }
816
817    /// Asynchronous memory copy
818    pub fn memcpy_async(
819        &mut self,
820        dst: *mut c_void,
821        src: *const c_void,
822        size: usize,
823        kind: CudaMemcpyKind,
824        stream_id: u32,
825    ) -> Result<(), CudaError> {
826        let operation = CudaOperation {
827            // Mirrors the synchronous `memcpy`'s mapping above so the queued
828            // operation's recorded direction matches what the caller asked
829            // for instead of always reporting a generic `MemcpyAsync`.
830            op_type: match kind {
831                CudaMemcpyKind::HostToDevice => CudaOperationType::MemcpyHostToDevice,
832                CudaMemcpyKind::DeviceToHost => CudaOperationType::MemcpyDeviceToHost,
833                CudaMemcpyKind::DeviceToDevice => CudaOperationType::MemcpyDeviceToDevice,
834                CudaMemcpyKind::HostToHost => CudaOperationType::MemcpyAsync,
835            },
836            src_ptr: Some(src as *mut c_void),
837            dst_ptr: Some(dst),
838            size,
839            timestamp: Instant::now(),
840        };
841
842        self.stream_manager.add_operation(stream_id, operation)?;
843        Ok(())
844    }
845
846    /// Create CUDA context
847    pub fn create_context(&mut self, flags: CudaContextFlags) -> Result<u32, CudaError> {
848        let context_id = self.contexts.len() as u32;
849
850        let context = CudaContext {
851            handle: std::ptr::null_mut(), // Would be actual CUDA context
852            device_id: self.config.device_id,
853            flags,
854            created_at: Instant::now(),
855            streams: Vec::new(),
856        };
857
858        self.contexts.insert(context_id, context);
859        Ok(context_id)
860    }
861
862    /// Get device properties
863    pub fn get_device_properties(&self) -> &CudaDeviceProperties {
864        &self.device_properties
865    }
866
867    /// Get statistics
868    pub fn get_stats(&self) -> &CudaStats {
869        &self.stats
870    }
871
872    /// Synchronize device
873    pub fn device_synchronize(&mut self) -> Result<(), CudaError> {
874        // Synchronize all streams
875        let stream_ids: Vec<u32> = self.stream_manager.streams.iter().map(|s| s.id).collect();
876        for stream_id in stream_ids {
877            self.stream_manager.synchronize_stream(stream_id)?;
878        }
879        Ok(())
880    }
881
882    /// Create stream
883    pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, CudaError> {
884        self.stream_manager.create_stream(priority)
885    }
886
887    /// Destroy stream
888    pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), CudaError> {
889        self.stream_manager.destroy_stream(stream_id)
890    }
891}
892
893// Safety: CudaMemoryBackend manages CUDA GPU memory pointers via *mut c_void.
894// While raw pointers are not Send/Sync by default, it's safe to share across threads
895// when protected by Arc<Mutex<>> because:
896// 1. All pointers point to CUDA GPU memory managed by the CUDA driver
897// 2. The Mutex provides exclusive access for all mutable operations
898// 3. No thread-local state is maintained
899unsafe impl Send for CudaMemoryBackend {}
900unsafe impl Sync for CudaMemoryBackend {}
901
902/// CUDA memory copy kinds
903#[derive(Debug, Clone)]
904pub enum CudaMemcpyKind {
905    HostToDevice,
906    DeviceToHost,
907    DeviceToDevice,
908    HostToHost,
909}
910
911/// CUDA errors
912#[derive(Debug, Clone)]
913pub enum CudaError {
914    DeviceNotFound(String),
915    OutOfMemory(String),
916    InvalidPointer(String),
917    InvalidStream(String),
918    StreamFull(String),
919    UnsupportedOperation(String),
920    UnsupportedMemoryType(String),
921    ContextCreationFailed(String),
922    KernelLaunchFailed(String),
923    SynchronizationFailed(String),
924    InternalError(String),
925}
926
927impl std::fmt::Display for CudaError {
928    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
929        match self {
930            CudaError::DeviceNotFound(msg) => write!(f, "Device not found: {}", msg),
931            CudaError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
932            CudaError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
933            CudaError::InvalidStream(msg) => write!(f, "Invalid stream: {}", msg),
934            CudaError::StreamFull(msg) => write!(f, "Stream full: {}", msg),
935            CudaError::UnsupportedOperation(msg) => write!(f, "Unsupported operation: {}", msg),
936            CudaError::UnsupportedMemoryType(msg) => write!(f, "Unsupported memory type: {}", msg),
937            CudaError::ContextCreationFailed(msg) => write!(f, "Context creation failed: {}", msg),
938            CudaError::KernelLaunchFailed(msg) => write!(f, "Kernel launch failed: {}", msg),
939            CudaError::SynchronizationFailed(msg) => write!(f, "Synchronization failed: {}", msg),
940            CudaError::InternalError(msg) => write!(f, "Internal error: {}", msg),
941        }
942    }
943}
944
945impl std::error::Error for CudaError {}
946
947/// Thread-safe CUDA backend wrapper
948pub struct ThreadSafeCudaBackend {
949    backend: Arc<Mutex<CudaMemoryBackend>>,
950}
951
952impl ThreadSafeCudaBackend {
953    pub fn new(config: CudaConfig) -> Result<Self, CudaError> {
954        let backend = CudaMemoryBackend::new(config)?;
955        Ok(Self {
956            backend: Arc::new(Mutex::new(backend)),
957        })
958    }
959
960    pub fn allocate(
961        &self,
962        size: usize,
963        memory_type: CudaMemoryType,
964    ) -> Result<*mut c_void, CudaError> {
965        let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
966        backend.allocate(size, memory_type)
967    }
968
969    pub fn free(&self, ptr: *mut c_void, memory_type: CudaMemoryType) -> Result<(), CudaError> {
970        let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
971        backend.free(ptr, memory_type)
972    }
973
974    pub fn get_stats(&self) -> CudaStats {
975        let backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
976        backend.get_stats().clone()
977    }
978}
979
980#[cfg(test)]
981mod tests {
982    use super::*;
983
984    /// Regression test for F26: a zero-size request must not reach
985    /// `std::alloc::alloc` (unsound for a zero-size layout) and must round
986    /// trip through `sim_dealloc` as a safe no-op.
987    #[test]
988    fn sim_alloc_zero_size_is_a_safe_sentinel_not_a_ub_call() {
989        let ptr = sim_alloc(0).expect("zero-size request must succeed");
990        assert!(!ptr.is_null());
991        // The sentinel returned for a zero-size request; freeing it must be
992        // a no-op, never a call into the system allocator.
993        sim_dealloc(ptr);
994    }
995
996    /// A real allocation must be readable/writable across its full
997    /// requested size (proves the header/offset bookkeeping did not corrupt
998    /// the returned pointer) and must free through the *same* layout it was
999    /// allocated with.
1000    #[test]
1001    fn sim_alloc_real_allocation_round_trips_and_frees_cleanly() {
1002        for size in [1usize, 7, 256, 4096, 1_000_003] {
1003            let ptr = sim_alloc(size).expect("allocation must succeed") as *mut u8;
1004            assert!(!ptr.is_null());
1005            // SAFETY: `ptr` was just allocated with `size` bytes available.
1006            unsafe {
1007                for i in 0..size {
1008                    ptr.add(i).write(0xAB);
1009                }
1010                for i in 0..size {
1011                    assert_eq!(ptr.add(i).read(), 0xAB);
1012                }
1013                sim_dealloc(ptr as *mut c_void);
1014            }
1015        }
1016    }
1017
1018    #[test]
1019    fn sim_dealloc_null_is_a_no_op() {
1020        // `sim_dealloc` documents a null pointer as a no-op, which is
1021        // exactly what this exercises.
1022        sim_dealloc(std::ptr::null_mut());
1023    }
1024
1025    #[test]
1026    fn test_cuda_backend_creation() {
1027        let config = CudaConfig::default();
1028        let backend = CudaMemoryBackend::new(config);
1029        assert!(backend.is_ok());
1030    }
1031
1032    #[test]
1033    fn test_memory_pool() {
1034        let mut pool = CudaMemoryPool::new(CudaMemoryType::Device, 1024 * 1024);
1035        let ptr = pool.allocate(1024);
1036        assert!(ptr.is_ok());
1037
1038        let ptr = ptr.expect("unwrap failed");
1039        let result = pool.free(ptr);
1040        assert!(result.is_ok());
1041    }
1042
1043    #[test]
1044    fn test_stream_manager() {
1045        let mut manager = CudaStreamManager::new(CudaStreamConfig::default());
1046        let stream_id = manager.create_stream(Some(1));
1047        assert!(stream_id.is_ok());
1048
1049        let stream_id = stream_id.expect("unwrap failed");
1050        let result = manager.destroy_stream(stream_id);
1051        assert!(result.is_ok());
1052    }
1053
1054    #[test]
1055    fn test_thread_safe_backend() {
1056        let config = CudaConfig::default();
1057        let backend = ThreadSafeCudaBackend::new(config);
1058        assert!(backend.is_ok());
1059
1060        let backend = backend.expect("unwrap failed");
1061        let stats = backend.get_stats();
1062        assert_eq!(stats.total_allocations, 0);
1063    }
1064}