use std::collections::HashMap;
use std::ffi::c_void;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
const SIM_ALLOC_ALIGN: usize = 256;
fn sim_alloc(size: usize) -> Result<*mut c_void, RocmError> {
if size == 0 {
return Ok(SIM_ALLOC_ALIGN as *mut c_void);
}
let total = SIM_ALLOC_ALIGN.checked_add(size).ok_or_else(|| {
RocmError::OutOfMemory(format!(
"{size}-byte request overflows the allocator's size limit"
))
})?;
let layout = std::alloc::Layout::from_size_align(total, SIM_ALLOC_ALIGN)
.map_err(|e| RocmError::OutOfMemory(format!("invalid allocation layout: {e}")))?;
let base = unsafe { std::alloc::alloc(layout) };
if base.is_null() {
return Err(RocmError::OutOfMemory(format!(
"allocator returned null for a {size}-byte request"
)));
}
unsafe { (base as *mut usize).write(size) };
Ok(unsafe { base.add(SIM_ALLOC_ALIGN) } as *mut c_void)
}
fn sim_dealloc(ptr: *mut c_void) {
if ptr.is_null() || (ptr as usize) == SIM_ALLOC_ALIGN {
return;
}
let base = unsafe { (ptr as *mut u8).sub(SIM_ALLOC_ALIGN) };
let size = unsafe { (base as *const usize).read() };
if let Ok(layout) = std::alloc::Layout::from_size_align(SIM_ALLOC_ALIGN + size, SIM_ALLOC_ALIGN)
{
unsafe { std::alloc::dealloc(base, layout) };
}
}
pub struct RocmMemoryBackend {
config: RocmConfig,
device_properties: RocmDeviceProperties,
contexts: HashMap<u32, HipContext>,
memory_pools: HashMap<RocmMemoryType, RocmMemoryPool>,
stats: RocmStats,
stream_manager: HipStreamManager,
}
#[derive(Debug, Clone)]
pub struct RocmConfig {
pub device_id: u32,
pub enable_coarse_memory: bool,
pub enable_fine_memory: bool,
pub enable_memory_pools: bool,
pub enable_async_ops: bool,
pub pool_growth_size: usize,
pub enable_host_visible: bool,
pub enable_device_coherent: bool,
pub max_streams: u32,
pub enable_profiling: bool,
}
impl Default for RocmConfig {
fn default() -> Self {
Self {
device_id: 0,
enable_coarse_memory: true,
enable_fine_memory: true,
enable_memory_pools: true,
enable_async_ops: true,
pool_growth_size: 64 * 1024 * 1024, enable_host_visible: true,
enable_device_coherent: false,
max_streams: 16,
enable_profiling: false,
}
}
}
#[derive(Debug, Clone)]
pub struct RocmDeviceProperties {
pub device_id: u32,
pub name: String,
pub arch: String,
pub gcn_arch_name: String,
pub total_global_memory: usize,
pub local_memory_size: usize,
pub max_work_group_size: u32,
pub max_work_item_dimensions: u32,
pub max_work_item_sizes: [u32; 3],
pub compute_units: u32,
pub wavefront_size: u32,
pub memory_clock_frequency: u32,
pub memory_bus_width: u32,
pub l2_cache_size: usize,
pub max_constant_buffer_size: usize,
pub pci_bus_id: u32,
pub pci_device_id: u32,
pub supports_cooperative_launch: bool,
pub supports_dynamic_parallelism: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RocmMemoryType {
Device,
Host,
HostVisible,
DeviceCoherent,
CoarseGrained,
FineGrained,
}
pub struct HipContext {
pub handle: *mut c_void,
pub device_id: u32,
pub flags: HipContextFlags,
pub created_at: Instant,
pub streams: Vec<HipStream>,
pub memory_info: HipMemoryInfo,
}
#[derive(Debug, Clone)]
pub struct HipContextFlags {
pub sched_auto: bool,
pub sched_spin: bool,
pub sched_yield: bool,
pub sched_blocking_sync: bool,
pub map_host: bool,
}
impl Default for HipContextFlags {
fn default() -> Self {
Self {
sched_auto: true,
sched_spin: false,
sched_yield: false,
sched_blocking_sync: false,
map_host: false,
}
}
}
#[derive(Debug, Clone)]
pub struct HipMemoryInfo {
pub total_memory: usize,
pub free_memory: usize,
pub used_memory: usize,
pub coarse_memory: usize,
pub fine_memory: usize,
}
pub struct HipStream {
pub handle: *mut c_void,
pub id: u32,
pub priority: i32,
pub flags: HipStreamFlags,
pub created_at: Instant,
pub operations: std::collections::VecDeque<HipOperation>,
}
#[derive(Debug, Clone)]
pub struct HipStreamFlags {
pub default: bool,
pub non_blocking: bool,
pub per_thread: bool,
}
impl Default for HipStreamFlags {
fn default() -> Self {
Self {
default: true,
non_blocking: false,
per_thread: false,
}
}
}
#[derive(Debug, Clone)]
pub struct HipOperation {
pub op_type: HipOperationType,
pub src_ptr: Option<*mut c_void>,
pub dst_ptr: Option<*mut c_void>,
pub size: usize,
pub timestamp: Instant,
}
#[derive(Debug, Clone)]
pub enum HipOperationType {
MemcpyHostToDevice,
MemcpyDeviceToHost,
MemcpyDeviceToDevice,
MemcpyAsync,
MemsetAsync,
KernelLaunch,
EventRecord,
EventSynchronize,
}
pub struct RocmMemoryPool {
memory_type: RocmMemoryType,
current_size: usize,
max_size: usize,
used_size: usize,
free_blocks: std::collections::VecDeque<RocmMemoryBlock>,
allocated_blocks: HashMap<*mut c_void, RocmMemoryBlock>,
attributes: RocmMemoryAttributes,
}
#[derive(Debug, Clone)]
pub struct RocmMemoryBlock {
pub ptr: *mut c_void,
pub size: usize,
pub memory_type: RocmMemoryType,
pub allocated_at: Instant,
pub last_access: Option<Instant>,
pub ref_count: u32,
pub agent_accessible: bool,
}
#[derive(Debug, Clone)]
pub struct RocmMemoryAttributes {
pub is_coarse_grained: bool,
pub is_fine_grained: bool,
pub is_host_accessible: bool,
pub is_device_accessible: bool,
pub is_coherent: bool,
pub numa_node: Option<u32>,
}
impl Default for RocmMemoryAttributes {
fn default() -> Self {
Self {
is_coarse_grained: true,
is_fine_grained: false,
is_host_accessible: false,
is_device_accessible: true,
is_coherent: false,
numa_node: None,
}
}
}
impl RocmMemoryPool {
pub fn new(memory_type: RocmMemoryType, max_size: usize) -> Self {
let attributes = match memory_type {
RocmMemoryType::CoarseGrained => RocmMemoryAttributes {
is_coarse_grained: true,
is_fine_grained: false,
is_host_accessible: false,
is_device_accessible: true,
is_coherent: false,
numa_node: None,
},
RocmMemoryType::FineGrained => RocmMemoryAttributes {
is_coarse_grained: false,
is_fine_grained: true,
is_host_accessible: true,
is_device_accessible: true,
is_coherent: true,
numa_node: Some(0),
},
RocmMemoryType::HostVisible => RocmMemoryAttributes {
is_coarse_grained: false,
is_fine_grained: false,
is_host_accessible: true,
is_device_accessible: true,
is_coherent: false,
numa_node: None,
},
_ => RocmMemoryAttributes::default(),
};
Self {
memory_type,
current_size: 0,
max_size,
used_size: 0,
free_blocks: std::collections::VecDeque::new(),
allocated_blocks: HashMap::new(),
attributes,
}
}
pub fn allocate(&mut self, size: usize) -> Result<*mut c_void, RocmError> {
for i in 0..self.free_blocks.len() {
if self.free_blocks[i].size >= size {
let Some(mut block) = self.free_blocks.remove(i) else {
continue;
};
if block.size > size * 2 {
let remaining_block = RocmMemoryBlock {
ptr: unsafe { block.ptr.add(size) },
size: block.size - size,
memory_type: block.memory_type.clone(),
allocated_at: block.allocated_at,
last_access: None,
ref_count: 0,
agent_accessible: block.agent_accessible,
};
self.free_blocks.push_back(remaining_block);
block.size = size;
}
block.last_access = Some(Instant::now());
block.ref_count = 1;
let ptr = block.ptr;
self.allocated_blocks.insert(ptr, block);
self.used_size += size;
return Ok(ptr);
}
}
if self.current_size + size > self.max_size {
return Err(RocmError::OutOfMemory(
"Pool size limit exceeded".to_string(),
));
}
let ptr = self.hip_malloc(size)?;
let block = RocmMemoryBlock {
ptr,
size,
memory_type: self.memory_type.clone(),
allocated_at: Instant::now(),
last_access: Some(Instant::now()),
ref_count: 1,
agent_accessible: self.attributes.is_device_accessible,
};
self.allocated_blocks.insert(ptr, block);
self.current_size += size;
self.used_size += size;
Ok(ptr)
}
pub fn free(&mut self, ptr: *mut c_void) -> Result<(), RocmError> {
if let Some(block) = self.allocated_blocks.remove(&ptr) {
self.used_size -= block.size;
self.free_blocks.push_back(RocmMemoryBlock {
ptr: block.ptr,
size: block.size,
memory_type: block.memory_type,
allocated_at: block.allocated_at,
last_access: None,
ref_count: 0,
agent_accessible: block.agent_accessible,
});
self.coalesce_free_blocks();
Ok(())
} else {
Err(RocmError::InvalidPointer(
"Pointer not found in pool".to_string(),
))
}
}
fn coalesce_free_blocks(&mut self) {
let mut blocks: Vec<RocmMemoryBlock> = self.free_blocks.drain(..).collect();
blocks.sort_by_key(|block| block.ptr as usize);
let mut coalesced = Vec::new();
let mut current_block: Option<RocmMemoryBlock> = None;
for block in blocks {
match current_block.take() {
None => current_block = Some(block),
Some(mut prev_block) => {
let prev_end = prev_block.ptr as usize + prev_block.size;
let block_start = block.ptr as usize;
if prev_end == block_start && prev_block.memory_type == block.memory_type {
prev_block.size += block.size;
current_block = Some(prev_block);
} else {
coalesced.push(prev_block);
current_block = Some(block);
}
}
}
}
if let Some(block) = current_block {
coalesced.push(block);
}
self.free_blocks = coalesced.into();
}
fn hip_malloc(&self, size: usize) -> Result<*mut c_void, RocmError> {
match self.memory_type {
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(
"Unsupported memory type for allocation".to_string(),
)),
}
}
}
pub struct HipStreamManager {
streams: Vec<HipStream>,
stream_pool: std::collections::VecDeque<HipStream>,
next_stream_id: u32,
config: HipStreamConfig,
}
#[derive(Debug, Clone)]
pub struct HipStreamConfig {
pub default_priority: i32,
pub enable_priorities: bool,
pub max_operations_per_stream: usize,
}
impl Default for HipStreamConfig {
fn default() -> Self {
Self {
default_priority: 0,
enable_priorities: true,
max_operations_per_stream: 1000,
}
}
}
impl HipStreamManager {
pub fn new(config: HipStreamConfig) -> Self {
Self {
streams: Vec::new(),
stream_pool: std::collections::VecDeque::new(),
next_stream_id: 0,
config,
}
}
pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, RocmError> {
let stream_id = self.next_stream_id;
self.next_stream_id += 1;
let mut stream = self.stream_pool.pop_front().unwrap_or_else(|| HipStream {
handle: std::ptr::null_mut(), id: stream_id,
priority: priority.unwrap_or(self.config.default_priority),
flags: HipStreamFlags::default(),
created_at: Instant::now(),
operations: std::collections::VecDeque::new(),
});
stream.id = stream_id;
stream.priority = priority.unwrap_or(self.config.default_priority);
stream.created_at = Instant::now();
stream.operations.clear();
self.streams.push(stream);
Ok(stream_id)
}
pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), RocmError> {
if let Some(pos) = self.streams.iter().position(|s| s.id == stream_id) {
let stream = self.streams.remove(pos);
self.stream_pool.push_back(stream);
Ok(())
} else {
Err(RocmError::InvalidStream("Stream not found".to_string()))
}
}
pub fn add_operation(
&mut self,
stream_id: u32,
operation: HipOperation,
) -> Result<(), RocmError> {
if let Some(stream) = self.streams.iter_mut().find(|s| s.id == stream_id) {
if stream.operations.len() >= self.config.max_operations_per_stream {
return Err(RocmError::StreamFull(
"Stream operation queue is full".to_string(),
));
}
stream.operations.push_back(operation);
Ok(())
} else {
Err(RocmError::InvalidStream("Stream not found".to_string()))
}
}
pub fn synchronize_stream(&mut self, stream_id: u32) -> Result<(), RocmError> {
let mut operations = Vec::new();
if let Some(stream) = self.streams.iter_mut().find(|s| s.id == stream_id) {
while let Some(operation) = stream.operations.pop_front() {
operations.push(operation);
}
} else {
return Err(RocmError::InvalidStream("Stream not found".to_string()));
}
for operation in operations {
self.execute_operation(operation)?;
}
Ok(())
}
fn execute_operation(&self, _operation: HipOperation) -> Result<(), RocmError> {
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct RocmStats {
pub total_allocations: u64,
pub total_deallocations: u64,
pub bytes_allocated: u64,
pub bytes_deallocated: u64,
pub device_memory_used: usize,
pub host_memory_used: usize,
pub coarse_grained_used: usize,
pub fine_grained_used: usize,
pub stream_operations: u64,
pub kernel_launches: u64,
pub memory_transfers: u64,
pub average_allocation_time: Duration,
pub peak_memory_usage: usize,
}
impl RocmMemoryBackend {
pub fn new(config: RocmConfig) -> Result<Self, RocmError> {
let device_properties = Self::query_device_properties(config.device_id)?;
let mut memory_pools = HashMap::new();
if config.enable_memory_pools {
let pool_size = device_properties.total_global_memory / 4;
memory_pools.insert(
RocmMemoryType::Device,
RocmMemoryPool::new(RocmMemoryType::Device, pool_size),
);
memory_pools.insert(
RocmMemoryType::Host,
RocmMemoryPool::new(RocmMemoryType::Host, pool_size),
);
if config.enable_coarse_memory {
memory_pools.insert(
RocmMemoryType::CoarseGrained,
RocmMemoryPool::new(RocmMemoryType::CoarseGrained, pool_size),
);
}
if config.enable_fine_memory {
memory_pools.insert(
RocmMemoryType::FineGrained,
RocmMemoryPool::new(RocmMemoryType::FineGrained, pool_size / 2),
);
}
if config.enable_host_visible {
memory_pools.insert(
RocmMemoryType::HostVisible,
RocmMemoryPool::new(RocmMemoryType::HostVisible, pool_size / 4),
);
}
}
let stream_manager = HipStreamManager::new(HipStreamConfig::default());
Ok(Self {
config,
device_properties,
contexts: HashMap::new(),
memory_pools,
stats: RocmStats::default(),
stream_manager,
})
}
fn query_device_properties(device_id: u32) -> Result<RocmDeviceProperties, RocmError> {
Ok(RocmDeviceProperties {
device_id,
name: format!("AMD GPU {}", device_id),
arch: "gfx906".to_string(), gcn_arch_name: "Vega20".to_string(),
total_global_memory: 16 * 1024 * 1024 * 1024, local_memory_size: 64 * 1024, max_work_group_size: 1024,
max_work_item_dimensions: 3,
max_work_item_sizes: [1024, 1024, 1024],
compute_units: 64,
wavefront_size: 64,
memory_clock_frequency: 1000000, memory_bus_width: 4096,
l2_cache_size: 4 * 1024 * 1024, max_constant_buffer_size: 64 * 1024, pci_bus_id: 0x03,
pci_device_id: 0x66AF,
supports_cooperative_launch: true,
supports_dynamic_parallelism: false,
})
}
pub fn allocate(
&mut self,
size: usize,
memory_type: RocmMemoryType,
) -> Result<*mut c_void, RocmError> {
let start_time = Instant::now();
let ptr = if self.config.enable_memory_pools {
if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
pool.allocate(size)?
} else {
return Err(RocmError::UnsupportedMemoryType(
"Memory type not supported".to_string(),
));
}
} else {
self.direct_allocate(size, memory_type.clone())?
};
self.stats.total_allocations += 1;
self.stats.bytes_allocated += size as u64;
match memory_type {
RocmMemoryType::Device => self.stats.device_memory_used += size,
RocmMemoryType::Host => self.stats.host_memory_used += size,
RocmMemoryType::CoarseGrained => self.stats.coarse_grained_used += size,
RocmMemoryType::FineGrained => self.stats.fine_grained_used += size,
_ => {}
}
let allocation_time = start_time.elapsed();
let total_time = self.stats.average_allocation_time.as_nanos() as u64
* (self.stats.total_allocations - 1)
+ allocation_time.as_nanos() as u64;
self.stats.average_allocation_time =
Duration::from_nanos(total_time / self.stats.total_allocations);
let current_usage = self.stats.device_memory_used
+ self.stats.host_memory_used
+ self.stats.coarse_grained_used
+ self.stats.fine_grained_used;
if current_usage > self.stats.peak_memory_usage {
self.stats.peak_memory_usage = current_usage;
}
Ok(ptr)
}
fn direct_allocate(
&self,
size: usize,
memory_type: RocmMemoryType,
) -> Result<*mut c_void, RocmError> {
match memory_type {
RocmMemoryType::Device => sim_alloc(size), RocmMemoryType::Host => sim_alloc(size), RocmMemoryType::CoarseGrained => sim_alloc(size), RocmMemoryType::FineGrained => sim_alloc(size), _ => Err(RocmError::UnsupportedMemoryType(
"Unsupported memory type".to_string(),
)),
}
}
pub fn free(&mut self, ptr: *mut c_void, memory_type: RocmMemoryType) -> Result<(), RocmError> {
if self.config.enable_memory_pools {
if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
pool.free(ptr)?;
} else {
return Err(RocmError::UnsupportedMemoryType(
"Memory type not supported".to_string(),
));
}
} else {
sim_dealloc(ptr);
}
self.stats.total_deallocations += 1;
Ok(())
}
pub fn memcpy(
&mut self,
dst: *mut c_void,
src: *const c_void,
size: usize,
kind: RocmMemcpyKind,
) -> Result<(), RocmError> {
let operation = HipOperation {
op_type: match kind {
RocmMemcpyKind::HostToDevice => HipOperationType::MemcpyHostToDevice,
RocmMemcpyKind::DeviceToHost => HipOperationType::MemcpyDeviceToHost,
RocmMemcpyKind::DeviceToDevice => HipOperationType::MemcpyDeviceToDevice,
RocmMemcpyKind::HostToHost => HipOperationType::MemcpyAsync,
},
src_ptr: Some(src as *mut c_void),
dst_ptr: Some(dst),
size,
timestamp: Instant::now(),
};
self.stream_manager.execute_operation(operation)?;
self.stats.memory_transfers += 1;
Ok(())
}
pub fn memcpy_async(
&mut self,
dst: *mut c_void,
src: *const c_void,
size: usize,
kind: RocmMemcpyKind,
stream_id: u32,
) -> Result<(), RocmError> {
let operation = HipOperation {
op_type: match kind {
RocmMemcpyKind::HostToDevice => HipOperationType::MemcpyHostToDevice,
RocmMemcpyKind::DeviceToHost => HipOperationType::MemcpyDeviceToHost,
RocmMemcpyKind::DeviceToDevice => HipOperationType::MemcpyDeviceToDevice,
RocmMemcpyKind::HostToHost => HipOperationType::MemcpyAsync,
},
src_ptr: Some(src as *mut c_void),
dst_ptr: Some(dst),
size,
timestamp: Instant::now(),
};
self.stream_manager.add_operation(stream_id, operation)?;
Ok(())
}
pub fn create_context(&mut self, flags: HipContextFlags) -> Result<u32, RocmError> {
let context_id = self.contexts.len() as u32;
let memory_info = HipMemoryInfo {
total_memory: self.device_properties.total_global_memory,
free_memory: self.device_properties.total_global_memory - self.stats.device_memory_used,
used_memory: self.stats.device_memory_used,
coarse_memory: self.stats.coarse_grained_used,
fine_memory: self.stats.fine_grained_used,
};
let context = HipContext {
handle: std::ptr::null_mut(), device_id: self.config.device_id,
flags,
created_at: Instant::now(),
streams: Vec::new(),
memory_info,
};
self.contexts.insert(context_id, context);
Ok(context_id)
}
pub fn get_device_properties(&self) -> &RocmDeviceProperties {
&self.device_properties
}
pub fn get_stats(&self) -> &RocmStats {
&self.stats
}
pub fn device_synchronize(&mut self) -> Result<(), RocmError> {
let stream_ids: Vec<u32> = self.stream_manager.streams.iter().map(|s| s.id).collect();
for stream_id in stream_ids {
self.stream_manager.synchronize_stream(stream_id)?;
}
Ok(())
}
pub fn create_stream(&mut self, priority: Option<i32>) -> Result<u32, RocmError> {
self.stream_manager.create_stream(priority)
}
pub fn destroy_stream(&mut self, stream_id: u32) -> Result<(), RocmError> {
self.stream_manager.destroy_stream(stream_id)
}
pub fn query_memory_attributes(
&self,
ptr: *mut c_void,
) -> Result<RocmMemoryAttributes, RocmError> {
self.memory_pools
.values()
.find(|pool| pool.allocated_blocks.contains_key(&ptr))
.map(|pool| pool.attributes.clone())
.ok_or_else(|| {
RocmError::InvalidPointer("pointer was not allocated by this backend".to_string())
})
}
}
unsafe impl Send for RocmMemoryBackend {}
unsafe impl Sync for RocmMemoryBackend {}
#[derive(Debug, Clone)]
pub enum RocmMemcpyKind {
HostToDevice,
DeviceToHost,
DeviceToDevice,
HostToHost,
}
#[derive(Debug, Clone)]
pub enum RocmError {
DeviceNotFound(String),
OutOfMemory(String),
InvalidPointer(String),
InvalidStream(String),
StreamFull(String),
UnsupportedOperation(String),
UnsupportedMemoryType(String),
ContextCreationFailed(String),
KernelLaunchFailed(String),
SynchronizationFailed(String),
InternalError(String),
}
impl std::fmt::Display for RocmError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RocmError::DeviceNotFound(msg) => write!(f, "Device not found: {}", msg),
RocmError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
RocmError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
RocmError::InvalidStream(msg) => write!(f, "Invalid stream: {}", msg),
RocmError::StreamFull(msg) => write!(f, "Stream full: {}", msg),
RocmError::UnsupportedOperation(msg) => write!(f, "Unsupported operation: {}", msg),
RocmError::UnsupportedMemoryType(msg) => write!(f, "Unsupported memory type: {}", msg),
RocmError::ContextCreationFailed(msg) => write!(f, "Context creation failed: {}", msg),
RocmError::KernelLaunchFailed(msg) => write!(f, "Kernel launch failed: {}", msg),
RocmError::SynchronizationFailed(msg) => write!(f, "Synchronization failed: {}", msg),
RocmError::InternalError(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl std::error::Error for RocmError {}
pub struct ThreadSafeRocmBackend {
backend: Arc<Mutex<RocmMemoryBackend>>,
}
impl ThreadSafeRocmBackend {
pub fn new(config: RocmConfig) -> Result<Self, RocmError> {
let backend = RocmMemoryBackend::new(config)?;
Ok(Self {
backend: Arc::new(Mutex::new(backend)),
})
}
pub fn allocate(
&self,
size: usize,
memory_type: RocmMemoryType,
) -> Result<*mut c_void, RocmError> {
let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
backend.allocate(size, memory_type)
}
pub fn free(&self, ptr: *mut c_void, memory_type: RocmMemoryType) -> Result<(), RocmError> {
let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
backend.free(ptr, memory_type)
}
pub fn get_stats(&self) -> RocmStats {
let backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
backend.get_stats().clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sim_alloc_zero_size_is_a_safe_sentinel_not_a_ub_call() {
let ptr = sim_alloc(0).expect("zero-size request must succeed");
assert!(!ptr.is_null());
sim_dealloc(ptr);
}
#[test]
fn sim_alloc_real_allocation_round_trips_and_frees_cleanly() {
for size in [1usize, 7, 256, 4096, 1_000_003] {
let ptr = sim_alloc(size).expect("allocation must succeed") as *mut u8;
assert!(!ptr.is_null());
unsafe {
for i in 0..size {
ptr.add(i).write(0xAB);
}
for i in 0..size {
assert_eq!(ptr.add(i).read(), 0xAB);
}
sim_dealloc(ptr as *mut c_void);
}
}
}
#[test]
fn sim_dealloc_null_is_a_no_op() {
sim_dealloc(std::ptr::null_mut());
}
#[test]
fn test_rocm_backend_creation() {
let config = RocmConfig::default();
let backend = RocmMemoryBackend::new(config);
assert!(backend.is_ok());
}
#[test]
fn test_memory_pool() {
let mut pool = RocmMemoryPool::new(RocmMemoryType::CoarseGrained, 1024 * 1024);
let ptr = pool.allocate(1024);
assert!(ptr.is_ok());
let ptr = ptr.expect("unwrap failed");
let result = pool.free(ptr);
assert!(result.is_ok());
}
#[test]
fn test_hip_stream_manager() {
let mut manager = HipStreamManager::new(HipStreamConfig::default());
let stream_id = manager.create_stream(Some(1));
assert!(stream_id.is_ok());
let stream_id = stream_id.expect("unwrap failed");
let result = manager.destroy_stream(stream_id);
assert!(result.is_ok());
}
#[test]
fn test_thread_safe_backend() {
let config = RocmConfig::default();
let backend = ThreadSafeRocmBackend::new(config);
assert!(backend.is_ok());
let backend = backend.expect("unwrap failed");
let stats = backend.get_stats();
assert_eq!(stats.total_allocations, 0);
}
}