use std::fmt;
use oxicuda_driver::error::{CudaError, CudaResult};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum AccessFlags {
#[default]
None,
Read,
ReadWrite,
}
impl fmt::Display for AccessFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::Read => write!(f, "Read"),
Self::ReadWrite => write!(f, "ReadWrite"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VirtualAddressRange {
base: u64,
size: usize,
alignment: usize,
}
impl VirtualAddressRange {
#[inline]
pub fn base(&self) -> u64 {
self.base
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
#[inline]
pub fn alignment(&self) -> usize {
self.alignment
}
pub fn contains(&self, addr: u64) -> bool {
addr >= self.base && addr < self.base.saturating_add(self.size as u64)
}
#[inline]
pub fn end(&self) -> u64 {
self.base.saturating_add(self.size as u64)
}
}
impl fmt::Display for VirtualAddressRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"VA[0x{:016x}..0x{:016x}, {} bytes, align={}]",
self.base,
self.end(),
self.size,
self.alignment,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PhysicalAllocation {
handle: u64,
size: usize,
device_ordinal: i32,
}
impl PhysicalAllocation {
#[inline]
pub fn handle(&self) -> u64 {
self.handle
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
#[inline]
pub fn device_ordinal(&self) -> i32 {
self.device_ordinal
}
}
impl fmt::Display for PhysicalAllocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PhysAlloc[handle=0x{:016x}, {} bytes, dev={}]",
self.handle, self.size, self.device_ordinal,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MappingRecord {
pub va_offset: usize,
pub size: usize,
pub phys_handle: u64,
pub access: AccessFlags,
}
pub struct VirtualMemoryManager;
impl VirtualMemoryManager {
pub fn reserve(size: usize, alignment: usize) -> CudaResult<VirtualAddressRange> {
if size == 0 {
return Err(CudaError::InvalidValue);
}
if alignment == 0 || !alignment.is_power_of_two() {
return Err(CudaError::InvalidValue);
}
if size % alignment != 0 {
return Err(CudaError::InvalidValue);
}
let synthetic_base = 0x0000_7F00_0000_0000_u64
.wrapping_add(size as u64)
.wrapping_add(alignment as u64);
Ok(VirtualAddressRange {
base: synthetic_base,
size,
alignment,
})
}
pub fn release(_va: VirtualAddressRange) -> CudaResult<()> {
Err(CudaError::NotSupported)
}
pub fn alloc_physical(size: usize, device_ordinal: i32) -> CudaResult<PhysicalAllocation> {
if size == 0 {
return Err(CudaError::InvalidValue);
}
Err(CudaError::NotSupported)?;
Ok(PhysicalAllocation {
handle: 0,
size,
device_ordinal,
})
}
pub fn free_physical(_phys: PhysicalAllocation) -> CudaResult<()> {
Err(CudaError::NotSupported)
}
pub fn map(
va: &VirtualAddressRange,
phys: &PhysicalAllocation,
offset: usize,
) -> CudaResult<()> {
if va.alignment > 0 && offset % va.alignment != 0 {
return Err(CudaError::InvalidValue);
}
let end = offset
.checked_add(phys.size)
.ok_or(CudaError::InvalidValue)?;
if end > va.size {
return Err(CudaError::InvalidValue);
}
Err(CudaError::NotSupported)
}
pub fn unmap(va: &VirtualAddressRange, offset: usize, size: usize) -> CudaResult<()> {
let end = offset.checked_add(size).ok_or(CudaError::InvalidValue)?;
if end > va.size {
return Err(CudaError::InvalidValue);
}
Err(CudaError::NotSupported)
}
pub fn set_access(
_va: &VirtualAddressRange,
_device_ordinal: i32,
_flags: AccessFlags,
) -> CudaResult<()> {
Err(CudaError::NotSupported)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reserve_valid_range() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
let va = va.ok();
assert!(va.is_some());
if let Some(va) = va {
assert_eq!(va.size(), 4096);
assert_eq!(va.alignment(), 4096);
assert!(va.base() > 0);
}
}
#[test]
fn reserve_zero_size_fails() {
let result = VirtualMemoryManager::reserve(0, 4096);
assert_eq!(result, Err(CudaError::InvalidValue));
}
#[test]
fn reserve_zero_alignment_fails() {
let result = VirtualMemoryManager::reserve(4096, 0);
assert_eq!(result, Err(CudaError::InvalidValue));
}
#[test]
fn reserve_non_power_of_two_alignment_fails() {
let result = VirtualMemoryManager::reserve(4096, 3);
assert_eq!(result, Err(CudaError::InvalidValue));
}
#[test]
fn reserve_misaligned_size_fails() {
let result = VirtualMemoryManager::reserve(4097, 4096);
assert_eq!(result, Err(CudaError::InvalidValue));
}
#[test]
fn reserve_large_range() {
let gib = 1 << 30;
let mib2 = 1 << 21;
let va = VirtualMemoryManager::reserve(gib, mib2);
assert!(va.is_ok());
if let Ok(va) = va {
assert_eq!(va.size(), gib);
assert_eq!(va.alignment(), mib2);
}
}
#[test]
fn virtual_address_range_contains() {
let va = VirtualMemoryManager::reserve(8192, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
assert!(va.contains(va.base()));
assert!(va.contains(va.base() + 1));
assert!(va.contains(va.base() + 8191));
assert!(!va.contains(va.end()));
assert!(!va.contains(va.base().wrapping_sub(1)));
}
}
#[test]
fn virtual_address_range_end() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
assert_eq!(va.end(), va.base() + 4096);
}
}
#[test]
fn virtual_address_range_display() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let disp = format!("{va}");
assert!(disp.contains("VA["));
assert!(disp.contains("4096 bytes"));
}
}
#[test]
fn alloc_physical_zero_size_fails() {
let result = VirtualMemoryManager::alloc_physical(0, 0);
assert_eq!(result, Err(CudaError::InvalidValue));
}
#[test]
fn alloc_physical_returns_not_supported() {
let result = VirtualMemoryManager::alloc_physical(4096, 0);
assert_eq!(result, Err(CudaError::NotSupported));
}
#[test]
fn release_returns_not_supported() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let result = VirtualMemoryManager::release(va);
assert_eq!(result, Err(CudaError::NotSupported));
}
}
#[test]
fn map_validates_alignment() {
let va = VirtualMemoryManager::reserve(8192, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let phys = PhysicalAllocation {
handle: 1,
size: 4096,
device_ordinal: 0,
};
let result = VirtualMemoryManager::map(&va, &phys, 1);
assert_eq!(result, Err(CudaError::InvalidValue));
}
}
#[test]
fn map_validates_bounds() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let phys = PhysicalAllocation {
handle: 1,
size: 8192, device_ordinal: 0,
};
let result = VirtualMemoryManager::map(&va, &phys, 0);
assert_eq!(result, Err(CudaError::InvalidValue));
}
}
#[test]
fn map_returns_not_supported_when_valid() {
let va = VirtualMemoryManager::reserve(8192, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let phys = PhysicalAllocation {
handle: 1,
size: 4096,
device_ordinal: 0,
};
let result = VirtualMemoryManager::map(&va, &phys, 0);
assert_eq!(result, Err(CudaError::NotSupported));
}
}
#[test]
fn unmap_validates_bounds() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let result = VirtualMemoryManager::unmap(&va, 0, 8192);
assert_eq!(result, Err(CudaError::InvalidValue));
}
}
#[test]
fn unmap_returns_not_supported_when_valid() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let result = VirtualMemoryManager::unmap(&va, 0, 4096);
assert_eq!(result, Err(CudaError::NotSupported));
}
}
#[test]
fn set_access_returns_not_supported() {
let va = VirtualMemoryManager::reserve(4096, 4096);
assert!(va.is_ok());
if let Ok(va) = va {
let result = VirtualMemoryManager::set_access(&va, 0, AccessFlags::ReadWrite);
assert_eq!(result, Err(CudaError::NotSupported));
}
}
#[test]
fn access_flags_default() {
assert_eq!(AccessFlags::default(), AccessFlags::None);
}
#[test]
fn access_flags_display() {
assert_eq!(format!("{}", AccessFlags::None), "None");
assert_eq!(format!("{}", AccessFlags::Read), "Read");
assert_eq!(format!("{}", AccessFlags::ReadWrite), "ReadWrite");
}
#[test]
fn physical_allocation_display() {
let phys = PhysicalAllocation {
handle: 0x1234,
size: 4096,
device_ordinal: 0,
};
let disp = format!("{phys}");
assert!(disp.contains("4096 bytes"));
assert!(disp.contains("dev=0"));
}
#[test]
fn mapping_record_fields() {
let record = MappingRecord {
va_offset: 0,
size: 4096,
phys_handle: 42,
access: AccessFlags::ReadWrite,
};
assert_eq!(record.va_offset, 0);
assert_eq!(record.size, 4096);
assert_eq!(record.phys_handle, 42);
assert_eq!(record.access, AccessFlags::ReadWrite);
}
#[test]
fn free_physical_returns_not_supported() {
let phys = PhysicalAllocation {
handle: 1,
size: 4096,
device_ordinal: 0,
};
let result = VirtualMemoryManager::free_physical(phys);
assert_eq!(result, Err(CudaError::NotSupported));
}
}