1use std::collections::HashMap;
36use std::ffi::c_void;
37use std::sync::{Arc, Mutex};
38use std::time::{Duration, Instant};
39
40const SIM_ALLOC_ALIGN: usize = 256;
42
43fn 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 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 unsafe { (base as *mut usize).write(size) };
84 Ok(unsafe { base.add(SIM_ALLOC_ALIGN) } as *mut c_void)
88}
89
90fn sim_dealloc(ptr: *mut c_void) {
100 if ptr.is_null() || (ptr as usize) == SIM_ALLOC_ALIGN {
101 return;
102 }
103 let base = unsafe { (ptr as *mut u8).sub(SIM_ALLOC_ALIGN) };
107 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 unsafe { std::alloc::dealloc(base, layout) };
114 }
115}
116
117pub struct CudaMemoryBackend {
119 config: CudaConfig,
121 device_properties: CudaDeviceProperties,
123 contexts: HashMap<u32, CudaContext>,
125 memory_pools: HashMap<CudaMemoryType, CudaMemoryPool>,
127 stats: CudaStats,
129 stream_manager: CudaStreamManager,
131}
132
133#[derive(Debug, Clone)]
135pub struct CudaConfig {
136 pub device_id: u32,
138 pub enable_unified_memory: bool,
140 pub enable_memory_pools: bool,
142 pub enable_async_ops: bool,
144 pub pool_growth_size: usize,
146 pub enable_mapped_memory: bool,
148 pub enable_cuda_graphs: bool,
150 pub enable_cooperative_groups: bool,
152 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, enable_mapped_memory: true,
165 enable_cuda_graphs: false, enable_cooperative_groups: false,
167 max_streams: 16,
168 }
169 }
170}
171
172#[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195pub enum CudaMemoryType {
196 Device,
197 Host,
198 Unified,
199 Mapped,
200 Array,
201 Texture,
202}
203
204pub struct CudaContext {
206 pub handle: *mut c_void,
208 pub device_id: u32,
210 pub flags: CudaContextFlags,
212 pub created_at: Instant,
214 pub streams: Vec<CudaStream>,
216}
217
218#[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
242pub struct CudaStream {
244 pub handle: *mut c_void,
246 pub id: u32,
248 pub priority: i32,
250 pub flags: CudaStreamFlags,
252 pub created_at: Instant,
254 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#[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#[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#[derive(Debug, Clone)]
301pub enum CudaOperationType {
302 MemcpyHostToDevice,
303 MemcpyDeviceToHost,
304 MemcpyDeviceToDevice,
305 MemcpyAsync,
306 MemsetAsync,
307 KernelLaunch,
308 EventRecord,
309 EventSynchronize,
310}
311
312pub struct CudaMemoryPool {
314 memory_type: CudaMemoryType,
316 current_size: usize,
318 max_size: usize,
320 used_size: usize,
322 free_blocks: std::collections::VecDeque<CudaMemoryBlock>,
324 allocated_blocks: HashMap<*mut c_void, CudaMemoryBlock>,
326}
327
328#[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 pub fn allocate(&mut self, size: usize) -> Result<*mut c_void, CudaError> {
353 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 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 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 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 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 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 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 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 match self.memory_type {
472 CudaMemoryType::Device => sim_alloc(size), CudaMemoryType::Host => sim_alloc(size), CudaMemoryType::Unified => sim_alloc(size), CudaMemoryType::Mapped => sim_alloc(size), _ => Err(CudaError::UnsupportedOperation(
477 "Unsupported memory type for allocation".to_string(),
478 )),
479 }
480 }
481}
482
483pub struct CudaStreamManager {
485 streams: Vec<CudaStream>,
487 stream_pool: std::collections::VecDeque<CudaStream>,
489 next_stream_id: u32,
491 config: CudaStreamConfig,
493}
494
495#[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 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(), 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 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 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 pub fn synchronize_stream(&mut self, stream_id: u32) -> Result<(), CudaError> {
585 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 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 Ok(())
612 }
613}
614
615#[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 pub fn new(config: CudaConfig) -> Result<Self, CudaError> {
635 let device_properties = Self::query_device_properties(config.device_id)?;
637
638 let mut memory_pools = HashMap::new();
640 if config.enable_memory_pools {
641 let pool_size = device_properties.total_global_memory / 4; 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 fn query_device_properties(device_id: u32) -> Result<CudaDeviceProperties, CudaError> {
673 Ok(CudaDeviceProperties {
675 device_id,
676 name: format!("CUDA Device {}", device_id),
677 compute_capability: (7, 5), total_global_memory: 8 * 1024 * 1024 * 1024, shared_memory_per_block: 48 * 1024, warp_size: 32,
681 max_threads_per_block: 1024,
682 max_blocks_per_multiprocessor: 16,
683 multiprocessor_count: 68,
684 memory_clock_rate: 7001000, memory_bus_width: 256,
686 l2_cache_size: 4 * 1024 * 1024, unified_addressing: true,
688 managed_memory: true,
689 concurrent_kernels: true,
690 async_engine_count: 2,
691 })
692 }
693
694 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 self.direct_allocate(size, memory_type.clone())?
713 };
714
715 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 match memory_type {
750 CudaMemoryType::Device => sim_alloc(size), CudaMemoryType::Host => sim_alloc(size), CudaMemoryType::Unified => {
753 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 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 sim_dealloc(ptr);
783 }
784
785 self.stats.total_deallocations += 1;
786 Ok(())
787 }
788
789 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 self.stream_manager.execute_operation(operation)?;
812 self.stats.memory_transfers += 1;
813
814 Ok(())
815 }
816
817 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 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 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(), 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 pub fn get_device_properties(&self) -> &CudaDeviceProperties {
864 &self.device_properties
865 }
866
867 pub fn get_stats(&self) -> &CudaStats {
869 &self.stats
870 }
871
872 pub fn device_synchronize(&mut self) -> Result<(), CudaError> {
874 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 pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, CudaError> {
884 self.stream_manager.create_stream(priority)
885 }
886
887 pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), CudaError> {
889 self.stream_manager.destroy_stream(stream_id)
890 }
891}
892
893unsafe impl Send for CudaMemoryBackend {}
900unsafe impl Sync for CudaMemoryBackend {}
901
902#[derive(Debug, Clone)]
904pub enum CudaMemcpyKind {
905 HostToDevice,
906 DeviceToHost,
907 DeviceToDevice,
908 HostToHost,
909}
910
911#[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
947pub 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 #[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 sim_dealloc(ptr);
994 }
995
996 #[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 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(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}