1use std::collections::HashMap;
34use std::ffi::c_void;
35use std::sync::{Arc, Mutex};
36use std::time::{Duration, Instant};
37
38const SIM_ALLOC_ALIGN: usize = 256;
40
41fn sim_alloc(size: usize) -> Result<*mut c_void, RocmError> {
54 if size == 0 {
55 return Ok(SIM_ALLOC_ALIGN as *mut c_void);
56 }
57 let total = SIM_ALLOC_ALIGN.checked_add(size).ok_or_else(|| {
58 RocmError::OutOfMemory(format!(
59 "{size}-byte request overflows the allocator's size limit"
60 ))
61 })?;
62 let layout = std::alloc::Layout::from_size_align(total, SIM_ALLOC_ALIGN)
63 .map_err(|e| RocmError::OutOfMemory(format!("invalid allocation layout: {e}")))?;
64 let base = unsafe { std::alloc::alloc(layout) };
67 if base.is_null() {
68 return Err(RocmError::OutOfMemory(format!(
69 "allocator returned null for a {size}-byte request"
70 )));
71 }
72 unsafe { (base as *mut usize).write(size) };
76 Ok(unsafe { base.add(SIM_ALLOC_ALIGN) } as *mut c_void)
80}
81
82fn sim_dealloc(ptr: *mut c_void) {
91 if ptr.is_null() || (ptr as usize) == SIM_ALLOC_ALIGN {
92 return;
93 }
94 let base = unsafe { (ptr as *mut u8).sub(SIM_ALLOC_ALIGN) };
96 let size = unsafe { (base as *const usize).read() };
98 if let Ok(layout) = std::alloc::Layout::from_size_align(SIM_ALLOC_ALIGN + size, SIM_ALLOC_ALIGN)
99 {
100 unsafe { std::alloc::dealloc(base, layout) };
103 }
104}
105
106pub struct RocmMemoryBackend {
108 config: RocmConfig,
110 device_properties: RocmDeviceProperties,
112 contexts: HashMap<u32, HipContext>,
114 memory_pools: HashMap<RocmMemoryType, RocmMemoryPool>,
116 stats: RocmStats,
118 stream_manager: HipStreamManager,
120}
121
122#[derive(Debug, Clone)]
124pub struct RocmConfig {
125 pub device_id: u32,
127 pub enable_coarse_memory: bool,
129 pub enable_fine_memory: bool,
131 pub enable_memory_pools: bool,
133 pub enable_async_ops: bool,
135 pub pool_growth_size: usize,
137 pub enable_host_visible: bool,
139 pub enable_device_coherent: bool,
141 pub max_streams: u32,
143 pub enable_profiling: bool,
145}
146
147impl Default for RocmConfig {
148 fn default() -> Self {
149 Self {
150 device_id: 0,
151 enable_coarse_memory: true,
152 enable_fine_memory: true,
153 enable_memory_pools: true,
154 enable_async_ops: true,
155 pool_growth_size: 64 * 1024 * 1024, enable_host_visible: true,
157 enable_device_coherent: false,
158 max_streams: 16,
159 enable_profiling: false,
160 }
161 }
162}
163
164#[derive(Debug, Clone)]
166pub struct RocmDeviceProperties {
167 pub device_id: u32,
168 pub name: String,
169 pub arch: String,
170 pub gcn_arch_name: String,
171 pub total_global_memory: usize,
172 pub local_memory_size: usize,
173 pub max_work_group_size: u32,
174 pub max_work_item_dimensions: u32,
175 pub max_work_item_sizes: [u32; 3],
176 pub compute_units: u32,
177 pub wavefront_size: u32,
178 pub memory_clock_frequency: u32,
179 pub memory_bus_width: u32,
180 pub l2_cache_size: usize,
181 pub max_constant_buffer_size: usize,
182 pub pci_bus_id: u32,
183 pub pci_device_id: u32,
184 pub supports_cooperative_launch: bool,
185 pub supports_dynamic_parallelism: bool,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Hash)]
190pub enum RocmMemoryType {
191 Device,
192 Host,
193 HostVisible,
194 DeviceCoherent,
195 CoarseGrained,
196 FineGrained,
197}
198
199pub struct HipContext {
201 pub handle: *mut c_void,
203 pub device_id: u32,
205 pub flags: HipContextFlags,
207 pub created_at: Instant,
209 pub streams: Vec<HipStream>,
211 pub memory_info: HipMemoryInfo,
213}
214
215#[derive(Debug, Clone)]
217pub struct HipContextFlags {
218 pub sched_auto: bool,
219 pub sched_spin: bool,
220 pub sched_yield: bool,
221 pub sched_blocking_sync: bool,
222 pub map_host: bool,
223}
224
225impl Default for HipContextFlags {
226 fn default() -> Self {
227 Self {
228 sched_auto: true,
229 sched_spin: false,
230 sched_yield: false,
231 sched_blocking_sync: false,
232 map_host: false,
233 }
234 }
235}
236
237#[derive(Debug, Clone)]
239pub struct HipMemoryInfo {
240 pub total_memory: usize,
241 pub free_memory: usize,
242 pub used_memory: usize,
243 pub coarse_memory: usize,
244 pub fine_memory: usize,
245}
246
247pub struct HipStream {
249 pub handle: *mut c_void,
251 pub id: u32,
253 pub priority: i32,
255 pub flags: HipStreamFlags,
257 pub created_at: Instant,
259 pub operations: std::collections::VecDeque<HipOperation>,
261}
262
263#[derive(Debug, Clone)]
265pub struct HipStreamFlags {
266 pub default: bool,
267 pub non_blocking: bool,
268 pub per_thread: bool,
269}
270
271impl Default for HipStreamFlags {
272 fn default() -> Self {
273 Self {
274 default: true,
275 non_blocking: false,
276 per_thread: false,
277 }
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct HipOperation {
284 pub op_type: HipOperationType,
285 pub src_ptr: Option<*mut c_void>,
286 pub dst_ptr: Option<*mut c_void>,
287 pub size: usize,
288 pub timestamp: Instant,
289}
290
291#[derive(Debug, Clone)]
293pub enum HipOperationType {
294 MemcpyHostToDevice,
295 MemcpyDeviceToHost,
296 MemcpyDeviceToDevice,
297 MemcpyAsync,
298 MemsetAsync,
299 KernelLaunch,
300 EventRecord,
301 EventSynchronize,
302}
303
304pub struct RocmMemoryPool {
306 memory_type: RocmMemoryType,
308 current_size: usize,
310 max_size: usize,
312 used_size: usize,
314 free_blocks: std::collections::VecDeque<RocmMemoryBlock>,
316 allocated_blocks: HashMap<*mut c_void, RocmMemoryBlock>,
318 attributes: RocmMemoryAttributes,
320}
321
322#[derive(Debug, Clone)]
324pub struct RocmMemoryBlock {
325 pub ptr: *mut c_void,
326 pub size: usize,
327 pub memory_type: RocmMemoryType,
328 pub allocated_at: Instant,
329 pub last_access: Option<Instant>,
330 pub ref_count: u32,
331 pub agent_accessible: bool,
332}
333
334#[derive(Debug, Clone)]
336pub struct RocmMemoryAttributes {
337 pub is_coarse_grained: bool,
338 pub is_fine_grained: bool,
339 pub is_host_accessible: bool,
340 pub is_device_accessible: bool,
341 pub is_coherent: bool,
342 pub numa_node: Option<u32>,
343}
344
345impl Default for RocmMemoryAttributes {
346 fn default() -> Self {
347 Self {
348 is_coarse_grained: true,
349 is_fine_grained: false,
350 is_host_accessible: false,
351 is_device_accessible: true,
352 is_coherent: false,
353 numa_node: None,
354 }
355 }
356}
357
358impl RocmMemoryPool {
359 pub fn new(memory_type: RocmMemoryType, max_size: usize) -> Self {
360 let attributes = match memory_type {
361 RocmMemoryType::CoarseGrained => RocmMemoryAttributes {
362 is_coarse_grained: true,
363 is_fine_grained: false,
364 is_host_accessible: false,
365 is_device_accessible: true,
366 is_coherent: false,
367 numa_node: None,
368 },
369 RocmMemoryType::FineGrained => RocmMemoryAttributes {
370 is_coarse_grained: false,
371 is_fine_grained: true,
372 is_host_accessible: true,
373 is_device_accessible: true,
374 is_coherent: true,
375 numa_node: Some(0),
376 },
377 RocmMemoryType::HostVisible => RocmMemoryAttributes {
378 is_coarse_grained: false,
379 is_fine_grained: false,
380 is_host_accessible: true,
381 is_device_accessible: true,
382 is_coherent: false,
383 numa_node: None,
384 },
385 _ => RocmMemoryAttributes::default(),
386 };
387
388 Self {
389 memory_type,
390 current_size: 0,
391 max_size,
392 used_size: 0,
393 free_blocks: std::collections::VecDeque::new(),
394 allocated_blocks: HashMap::new(),
395 attributes,
396 }
397 }
398
399 pub fn allocate(&mut self, size: usize) -> Result<*mut c_void, RocmError> {
401 for i in 0..self.free_blocks.len() {
403 if self.free_blocks[i].size >= size {
404 let Some(mut block) = self.free_blocks.remove(i) else {
405 continue;
406 };
407
408 if block.size > size * 2 {
410 let remaining_block = RocmMemoryBlock {
411 ptr: unsafe { block.ptr.add(size) },
412 size: block.size - size,
413 memory_type: block.memory_type.clone(),
414 allocated_at: block.allocated_at,
415 last_access: None,
416 ref_count: 0,
417 agent_accessible: block.agent_accessible,
418 };
419 self.free_blocks.push_back(remaining_block);
420 block.size = size;
421 }
422
423 block.last_access = Some(Instant::now());
424 block.ref_count = 1;
425
426 let ptr = block.ptr;
427 self.allocated_blocks.insert(ptr, block);
428 self.used_size += size;
429
430 return Ok(ptr);
431 }
432 }
433
434 if self.current_size + size > self.max_size {
436 return Err(RocmError::OutOfMemory(
437 "Pool size limit exceeded".to_string(),
438 ));
439 }
440
441 let ptr = self.hip_malloc(size)?;
442 let block = RocmMemoryBlock {
443 ptr,
444 size,
445 memory_type: self.memory_type.clone(),
446 allocated_at: Instant::now(),
447 last_access: Some(Instant::now()),
448 ref_count: 1,
449 agent_accessible: self.attributes.is_device_accessible,
450 };
451
452 self.allocated_blocks.insert(ptr, block);
453 self.current_size += size;
454 self.used_size += size;
455
456 Ok(ptr)
457 }
458
459 pub fn free(&mut self, ptr: *mut c_void) -> Result<(), RocmError> {
461 if let Some(block) = self.allocated_blocks.remove(&ptr) {
462 self.used_size -= block.size;
463
464 self.free_blocks.push_back(RocmMemoryBlock {
466 ptr: block.ptr,
467 size: block.size,
468 memory_type: block.memory_type,
469 allocated_at: block.allocated_at,
470 last_access: None,
471 ref_count: 0,
472 agent_accessible: block.agent_accessible,
473 });
474
475 self.coalesce_free_blocks();
477
478 Ok(())
479 } else {
480 Err(RocmError::InvalidPointer(
481 "Pointer not found in pool".to_string(),
482 ))
483 }
484 }
485
486 fn coalesce_free_blocks(&mut self) {
487 let mut blocks: Vec<RocmMemoryBlock> = self.free_blocks.drain(..).collect();
489 blocks.sort_by_key(|block| block.ptr as usize);
490
491 let mut coalesced = Vec::new();
492 let mut current_block: Option<RocmMemoryBlock> = None;
493
494 for block in blocks {
495 match current_block.take() {
496 None => current_block = Some(block),
497 Some(mut prev_block) => {
498 let prev_end = prev_block.ptr as usize + prev_block.size;
499 let block_start = block.ptr as usize;
500
501 if prev_end == block_start && prev_block.memory_type == block.memory_type {
502 prev_block.size += block.size;
504 current_block = Some(prev_block);
505 } else {
506 coalesced.push(prev_block);
507 current_block = Some(block);
508 }
509 }
510 }
511 }
512
513 if let Some(block) = current_block {
514 coalesced.push(block);
515 }
516
517 self.free_blocks = coalesced.into();
518 }
519
520 fn hip_malloc(&self, size: usize) -> Result<*mut c_void, RocmError> {
521 match self.memory_type {
523 RocmMemoryType::Device => sim_alloc(size), RocmMemoryType::Host => sim_alloc(size), RocmMemoryType::CoarseGrained => sim_alloc(size), RocmMemoryType::FineGrained => sim_alloc(size), RocmMemoryType::HostVisible => sim_alloc(size), _ => Err(RocmError::UnsupportedOperation(
529 "Unsupported memory type for allocation".to_string(),
530 )),
531 }
532 }
533}
534
535pub struct HipStreamManager {
537 streams: Vec<HipStream>,
539 stream_pool: std::collections::VecDeque<HipStream>,
541 next_stream_id: u32,
543 config: HipStreamConfig,
545}
546
547#[derive(Debug, Clone)]
549pub struct HipStreamConfig {
550 pub default_priority: i32,
551 pub enable_priorities: bool,
552 pub max_operations_per_stream: usize,
553}
554
555impl Default for HipStreamConfig {
556 fn default() -> Self {
557 Self {
558 default_priority: 0,
559 enable_priorities: true,
560 max_operations_per_stream: 1000,
561 }
562 }
563}
564
565impl HipStreamManager {
566 pub fn new(config: HipStreamConfig) -> Self {
567 Self {
568 streams: Vec::new(),
569 stream_pool: std::collections::VecDeque::new(),
570 next_stream_id: 0,
571 config,
572 }
573 }
574
575 pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, RocmError> {
582 let stream_id = self.next_stream_id;
583 self.next_stream_id += 1;
584
585 let mut stream = self.stream_pool.pop_front().unwrap_or_else(|| HipStream {
586 handle: std::ptr::null_mut(), id: stream_id,
588 priority: priority.unwrap_or(self.config.default_priority),
589 flags: HipStreamFlags::default(),
590 created_at: Instant::now(),
591 operations: std::collections::VecDeque::new(),
592 });
593 stream.id = stream_id;
594 stream.priority = priority.unwrap_or(self.config.default_priority);
595 stream.created_at = Instant::now();
596 stream.operations.clear();
597
598 self.streams.push(stream);
599 Ok(stream_id)
600 }
601
602 pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), RocmError> {
607 if let Some(pos) = self.streams.iter().position(|s| s.id == stream_id) {
608 let stream = self.streams.remove(pos);
609 self.stream_pool.push_back(stream);
610 Ok(())
611 } else {
612 Err(RocmError::InvalidStream("Stream not found".to_string()))
613 }
614 }
615
616 pub fn add_operation(
618 &mut self,
619 stream_id: u32,
620 operation: HipOperation,
621 ) -> Result<(), RocmError> {
622 if let Some(stream) = self.streams.iter_mut().find(|s| s.id == stream_id) {
623 if stream.operations.len() >= self.config.max_operations_per_stream {
624 return Err(RocmError::StreamFull(
625 "Stream operation queue is full".to_string(),
626 ));
627 }
628
629 stream.operations.push_back(operation);
630 Ok(())
631 } else {
632 Err(RocmError::InvalidStream("Stream not found".to_string()))
633 }
634 }
635
636 pub fn synchronize_stream(&mut self, stream_id: u32) -> Result<(), RocmError> {
638 let mut operations = Vec::new();
640 if let Some(stream) = self.streams.iter_mut().find(|s| s.id == stream_id) {
641 while let Some(operation) = stream.operations.pop_front() {
642 operations.push(operation);
643 }
644 } else {
645 return Err(RocmError::InvalidStream("Stream not found".to_string()));
646 }
647
648 for operation in operations {
650 self.execute_operation(operation)?;
651 }
652
653 Ok(())
654 }
655
656 fn execute_operation(&self, _operation: HipOperation) -> Result<(), RocmError> {
657 Ok(())
665 }
666}
667
668#[derive(Debug, Clone, Default)]
670pub struct RocmStats {
671 pub total_allocations: u64,
672 pub total_deallocations: u64,
673 pub bytes_allocated: u64,
674 pub bytes_deallocated: u64,
675 pub device_memory_used: usize,
676 pub host_memory_used: usize,
677 pub coarse_grained_used: usize,
678 pub fine_grained_used: usize,
679 pub stream_operations: u64,
680 pub kernel_launches: u64,
681 pub memory_transfers: u64,
682 pub average_allocation_time: Duration,
683 pub peak_memory_usage: usize,
684}
685
686impl RocmMemoryBackend {
687 pub fn new(config: RocmConfig) -> Result<Self, RocmError> {
689 let device_properties = Self::query_device_properties(config.device_id)?;
691
692 let mut memory_pools = HashMap::new();
694 if config.enable_memory_pools {
695 let pool_size = device_properties.total_global_memory / 4; memory_pools.insert(
698 RocmMemoryType::Device,
699 RocmMemoryPool::new(RocmMemoryType::Device, pool_size),
700 );
701 memory_pools.insert(
702 RocmMemoryType::Host,
703 RocmMemoryPool::new(RocmMemoryType::Host, pool_size),
704 );
705
706 if config.enable_coarse_memory {
707 memory_pools.insert(
708 RocmMemoryType::CoarseGrained,
709 RocmMemoryPool::new(RocmMemoryType::CoarseGrained, pool_size),
710 );
711 }
712
713 if config.enable_fine_memory {
714 memory_pools.insert(
715 RocmMemoryType::FineGrained,
716 RocmMemoryPool::new(RocmMemoryType::FineGrained, pool_size / 2),
717 );
718 }
719
720 if config.enable_host_visible {
721 memory_pools.insert(
722 RocmMemoryType::HostVisible,
723 RocmMemoryPool::new(RocmMemoryType::HostVisible, pool_size / 4),
724 );
725 }
726 }
727
728 let stream_manager = HipStreamManager::new(HipStreamConfig::default());
729
730 Ok(Self {
731 config,
732 device_properties,
733 contexts: HashMap::new(),
734 memory_pools,
735 stats: RocmStats::default(),
736 stream_manager,
737 })
738 }
739
740 fn query_device_properties(device_id: u32) -> Result<RocmDeviceProperties, RocmError> {
742 Ok(RocmDeviceProperties {
744 device_id,
745 name: format!("AMD GPU {}", device_id),
746 arch: "gfx906".to_string(), gcn_arch_name: "Vega20".to_string(),
748 total_global_memory: 16 * 1024 * 1024 * 1024, local_memory_size: 64 * 1024, max_work_group_size: 1024,
751 max_work_item_dimensions: 3,
752 max_work_item_sizes: [1024, 1024, 1024],
753 compute_units: 64,
754 wavefront_size: 64,
755 memory_clock_frequency: 1000000, memory_bus_width: 4096,
757 l2_cache_size: 4 * 1024 * 1024, max_constant_buffer_size: 64 * 1024, pci_bus_id: 0x03,
760 pci_device_id: 0x66AF,
761 supports_cooperative_launch: true,
762 supports_dynamic_parallelism: false,
763 })
764 }
765
766 pub fn allocate(
768 &mut self,
769 size: usize,
770 memory_type: RocmMemoryType,
771 ) -> Result<*mut c_void, RocmError> {
772 let start_time = Instant::now();
773
774 let ptr = if self.config.enable_memory_pools {
775 if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
776 pool.allocate(size)?
777 } else {
778 return Err(RocmError::UnsupportedMemoryType(
779 "Memory type not supported".to_string(),
780 ));
781 }
782 } else {
783 self.direct_allocate(size, memory_type.clone())?
785 };
786
787 self.stats.total_allocations += 1;
789 self.stats.bytes_allocated += size as u64;
790
791 match memory_type {
792 RocmMemoryType::Device => self.stats.device_memory_used += size,
793 RocmMemoryType::Host => self.stats.host_memory_used += size,
794 RocmMemoryType::CoarseGrained => self.stats.coarse_grained_used += size,
795 RocmMemoryType::FineGrained => self.stats.fine_grained_used += size,
796 _ => {}
797 }
798
799 let allocation_time = start_time.elapsed();
800 let total_time = self.stats.average_allocation_time.as_nanos() as u64
801 * (self.stats.total_allocations - 1)
802 + allocation_time.as_nanos() as u64;
803 self.stats.average_allocation_time =
804 Duration::from_nanos(total_time / self.stats.total_allocations);
805
806 let current_usage = self.stats.device_memory_used
807 + self.stats.host_memory_used
808 + self.stats.coarse_grained_used
809 + self.stats.fine_grained_used;
810 if current_usage > self.stats.peak_memory_usage {
811 self.stats.peak_memory_usage = current_usage;
812 }
813
814 Ok(ptr)
815 }
816
817 fn direct_allocate(
818 &self,
819 size: usize,
820 memory_type: RocmMemoryType,
821 ) -> Result<*mut c_void, RocmError> {
822 match memory_type {
824 RocmMemoryType::Device => sim_alloc(size), RocmMemoryType::Host => sim_alloc(size), RocmMemoryType::CoarseGrained => sim_alloc(size), RocmMemoryType::FineGrained => sim_alloc(size), _ => Err(RocmError::UnsupportedMemoryType(
829 "Unsupported memory type".to_string(),
830 )),
831 }
832 }
833
834 pub fn free(&mut self, ptr: *mut c_void, memory_type: RocmMemoryType) -> Result<(), RocmError> {
836 if self.config.enable_memory_pools {
837 if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
838 pool.free(ptr)?;
839 } else {
840 return Err(RocmError::UnsupportedMemoryType(
841 "Memory type not supported".to_string(),
842 ));
843 }
844 } else {
845 sim_dealloc(ptr);
849 }
850
851 self.stats.total_deallocations += 1;
852 Ok(())
853 }
854
855 pub fn memcpy(
857 &mut self,
858 dst: *mut c_void,
859 src: *const c_void,
860 size: usize,
861 kind: RocmMemcpyKind,
862 ) -> Result<(), RocmError> {
863 let operation = HipOperation {
864 op_type: match kind {
865 RocmMemcpyKind::HostToDevice => HipOperationType::MemcpyHostToDevice,
866 RocmMemcpyKind::DeviceToHost => HipOperationType::MemcpyDeviceToHost,
867 RocmMemcpyKind::DeviceToDevice => HipOperationType::MemcpyDeviceToDevice,
868 RocmMemcpyKind::HostToHost => HipOperationType::MemcpyAsync,
869 },
870 src_ptr: Some(src as *mut c_void),
871 dst_ptr: Some(dst),
872 size,
873 timestamp: Instant::now(),
874 };
875
876 self.stream_manager.execute_operation(operation)?;
878 self.stats.memory_transfers += 1;
879
880 Ok(())
881 }
882
883 pub fn memcpy_async(
885 &mut self,
886 dst: *mut c_void,
887 src: *const c_void,
888 size: usize,
889 kind: RocmMemcpyKind,
890 stream_id: u32,
891 ) -> Result<(), RocmError> {
892 let operation = HipOperation {
893 op_type: match kind {
897 RocmMemcpyKind::HostToDevice => HipOperationType::MemcpyHostToDevice,
898 RocmMemcpyKind::DeviceToHost => HipOperationType::MemcpyDeviceToHost,
899 RocmMemcpyKind::DeviceToDevice => HipOperationType::MemcpyDeviceToDevice,
900 RocmMemcpyKind::HostToHost => HipOperationType::MemcpyAsync,
901 },
902 src_ptr: Some(src as *mut c_void),
903 dst_ptr: Some(dst),
904 size,
905 timestamp: Instant::now(),
906 };
907
908 self.stream_manager.add_operation(stream_id, operation)?;
909 Ok(())
910 }
911
912 pub fn create_context(&mut self, flags: HipContextFlags) -> Result<u32, RocmError> {
914 let context_id = self.contexts.len() as u32;
915
916 let memory_info = HipMemoryInfo {
917 total_memory: self.device_properties.total_global_memory,
918 free_memory: self.device_properties.total_global_memory - self.stats.device_memory_used,
919 used_memory: self.stats.device_memory_used,
920 coarse_memory: self.stats.coarse_grained_used,
921 fine_memory: self.stats.fine_grained_used,
922 };
923
924 let context = HipContext {
925 handle: std::ptr::null_mut(), device_id: self.config.device_id,
927 flags,
928 created_at: Instant::now(),
929 streams: Vec::new(),
930 memory_info,
931 };
932
933 self.contexts.insert(context_id, context);
934 Ok(context_id)
935 }
936
937 pub fn get_device_properties(&self) -> &RocmDeviceProperties {
939 &self.device_properties
940 }
941
942 pub fn get_stats(&self) -> &RocmStats {
944 &self.stats
945 }
946
947 pub fn device_synchronize(&mut self) -> Result<(), RocmError> {
949 let stream_ids: Vec<u32> = self.stream_manager.streams.iter().map(|s| s.id).collect();
951 for stream_id in stream_ids {
952 self.stream_manager.synchronize_stream(stream_id)?;
953 }
954 Ok(())
955 }
956
957 pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, RocmError> {
959 self.stream_manager.create_stream(priority)
960 }
961
962 pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), RocmError> {
964 self.stream_manager.destroy_stream(stream_id)
965 }
966
967 pub fn query_memory_attributes(
975 &self,
976 ptr: *mut c_void,
977 ) -> Result<RocmMemoryAttributes, RocmError> {
978 self.memory_pools
979 .values()
980 .find(|pool| pool.allocated_blocks.contains_key(&ptr))
981 .map(|pool| pool.attributes.clone())
982 .ok_or_else(|| {
983 RocmError::InvalidPointer("pointer was not allocated by this backend".to_string())
984 })
985 }
986}
987
988unsafe impl Send for RocmMemoryBackend {}
995unsafe impl Sync for RocmMemoryBackend {}
996
997#[derive(Debug, Clone)]
999pub enum RocmMemcpyKind {
1000 HostToDevice,
1001 DeviceToHost,
1002 DeviceToDevice,
1003 HostToHost,
1004}
1005
1006#[derive(Debug, Clone)]
1008pub enum RocmError {
1009 DeviceNotFound(String),
1010 OutOfMemory(String),
1011 InvalidPointer(String),
1012 InvalidStream(String),
1013 StreamFull(String),
1014 UnsupportedOperation(String),
1015 UnsupportedMemoryType(String),
1016 ContextCreationFailed(String),
1017 KernelLaunchFailed(String),
1018 SynchronizationFailed(String),
1019 InternalError(String),
1020}
1021
1022impl std::fmt::Display for RocmError {
1023 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1024 match self {
1025 RocmError::DeviceNotFound(msg) => write!(f, "Device not found: {}", msg),
1026 RocmError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
1027 RocmError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
1028 RocmError::InvalidStream(msg) => write!(f, "Invalid stream: {}", msg),
1029 RocmError::StreamFull(msg) => write!(f, "Stream full: {}", msg),
1030 RocmError::UnsupportedOperation(msg) => write!(f, "Unsupported operation: {}", msg),
1031 RocmError::UnsupportedMemoryType(msg) => write!(f, "Unsupported memory type: {}", msg),
1032 RocmError::ContextCreationFailed(msg) => write!(f, "Context creation failed: {}", msg),
1033 RocmError::KernelLaunchFailed(msg) => write!(f, "Kernel launch failed: {}", msg),
1034 RocmError::SynchronizationFailed(msg) => write!(f, "Synchronization failed: {}", msg),
1035 RocmError::InternalError(msg) => write!(f, "Internal error: {}", msg),
1036 }
1037 }
1038}
1039
1040impl std::error::Error for RocmError {}
1041
1042pub struct ThreadSafeRocmBackend {
1044 backend: Arc<Mutex<RocmMemoryBackend>>,
1045}
1046
1047impl ThreadSafeRocmBackend {
1048 pub fn new(config: RocmConfig) -> Result<Self, RocmError> {
1049 let backend = RocmMemoryBackend::new(config)?;
1050 Ok(Self {
1051 backend: Arc::new(Mutex::new(backend)),
1052 })
1053 }
1054
1055 pub fn allocate(
1056 &self,
1057 size: usize,
1058 memory_type: RocmMemoryType,
1059 ) -> Result<*mut c_void, RocmError> {
1060 let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1061 backend.allocate(size, memory_type)
1062 }
1063
1064 pub fn free(&self, ptr: *mut c_void, memory_type: RocmMemoryType) -> Result<(), RocmError> {
1065 let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1066 backend.free(ptr, memory_type)
1067 }
1068
1069 pub fn get_stats(&self) -> RocmStats {
1070 let backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1071 backend.get_stats().clone()
1072 }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077 use super::*;
1078
1079 #[test]
1082 fn sim_alloc_zero_size_is_a_safe_sentinel_not_a_ub_call() {
1083 let ptr = sim_alloc(0).expect("zero-size request must succeed");
1084 assert!(!ptr.is_null());
1085 sim_dealloc(ptr);
1086 }
1087
1088 #[test]
1091 fn sim_alloc_real_allocation_round_trips_and_frees_cleanly() {
1092 for size in [1usize, 7, 256, 4096, 1_000_003] {
1093 let ptr = sim_alloc(size).expect("allocation must succeed") as *mut u8;
1094 assert!(!ptr.is_null());
1095 unsafe {
1096 for i in 0..size {
1097 ptr.add(i).write(0xAB);
1098 }
1099 for i in 0..size {
1100 assert_eq!(ptr.add(i).read(), 0xAB);
1101 }
1102 sim_dealloc(ptr as *mut c_void);
1103 }
1104 }
1105 }
1106
1107 #[test]
1108 fn sim_dealloc_null_is_a_no_op() {
1109 sim_dealloc(std::ptr::null_mut());
1110 }
1111
1112 #[test]
1113 fn test_rocm_backend_creation() {
1114 let config = RocmConfig::default();
1115 let backend = RocmMemoryBackend::new(config);
1116 assert!(backend.is_ok());
1117 }
1118
1119 #[test]
1120 fn test_memory_pool() {
1121 let mut pool = RocmMemoryPool::new(RocmMemoryType::CoarseGrained, 1024 * 1024);
1122 let ptr = pool.allocate(1024);
1123 assert!(ptr.is_ok());
1124
1125 let ptr = ptr.expect("unwrap failed");
1126 let result = pool.free(ptr);
1127 assert!(result.is_ok());
1128 }
1129
1130 #[test]
1131 fn test_hip_stream_manager() {
1132 let mut manager = HipStreamManager::new(HipStreamConfig::default());
1133 let stream_id = manager.create_stream(Some(1));
1134 assert!(stream_id.is_ok());
1135
1136 let stream_id = stream_id.expect("unwrap failed");
1137 let result = manager.destroy_stream(stream_id);
1138 assert!(result.is_ok());
1139 }
1140
1141 #[test]
1142 fn test_thread_safe_backend() {
1143 let config = RocmConfig::default();
1144 let backend = ThreadSafeRocmBackend::new(config);
1145 assert!(backend.is_ok());
1146
1147 let backend = backend.expect("unwrap failed");
1148 let stats = backend.get_stats();
1149 assert_eq!(stats.total_allocations, 0);
1150 }
1151}