use crate::error::{CudaError, CudaResult};
use crate::ffi::{CUmemAllocationHandleType, CUmemGenericAllocationHandle};
pub const FABRIC_HANDLE_BYTES: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FabricAccess {
#[default]
None,
ReadOnly,
ReadWrite,
}
impl FabricAccess {
#[must_use]
pub fn is_mappable(self) -> bool {
!matches!(self, FabricAccess::None)
}
#[must_use]
pub fn is_writable(self) -> bool {
matches!(self, FabricAccess::ReadWrite)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FabricAllocationProps {
pub device_ordinal: i32,
pub size: usize,
pub peer_access: FabricAccess,
pub handle_type: CUmemAllocationHandleType,
}
impl FabricAllocationProps {
#[must_use]
pub fn read_write(device_ordinal: i32, size: usize) -> Self {
Self {
device_ordinal,
size,
peer_access: FabricAccess::ReadWrite,
handle_type: CUmemAllocationHandleType::Fabric,
}
}
fn validate(&self) -> CudaResult<()> {
if self.handle_type != CUmemAllocationHandleType::Fabric {
return Err(CudaError::NotSupported);
}
if self.size == 0 {
return Err(CudaError::InvalidValue);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FabricHandle {
bytes: [u8; FABRIC_HANDLE_BYTES],
}
impl FabricHandle {
const MAGIC: [u8; 8] = *b"OXIFABRC";
fn encode(fabric_uuid: u128, size: usize, access: FabricAccess) -> Self {
let mut bytes = [0u8; FABRIC_HANDLE_BYTES];
bytes[0..8].copy_from_slice(&Self::MAGIC);
bytes[8..24].copy_from_slice(&fabric_uuid.to_le_bytes());
bytes[24..32].copy_from_slice(&(size as u64).to_le_bytes());
bytes[32] = match access {
FabricAccess::None => 0,
FabricAccess::ReadOnly => 1,
FabricAccess::ReadWrite => 2,
};
Self { bytes }
}
#[must_use]
pub fn to_bytes(&self) -> [u8; FABRIC_HANDLE_BYTES] {
self.bytes
}
pub fn from_bytes(raw: &[u8]) -> CudaResult<Self> {
if raw.len() != FABRIC_HANDLE_BYTES {
return Err(CudaError::InvalidValue);
}
if raw[0..8] != Self::MAGIC {
return Err(CudaError::InvalidHandle);
}
let mut bytes = [0u8; FABRIC_HANDLE_BYTES];
bytes.copy_from_slice(raw);
Ok(Self { bytes })
}
#[must_use]
pub fn fabric_uuid(&self) -> u128 {
let mut buf = [0u8; 16];
buf.copy_from_slice(&self.bytes[8..24]);
u128::from_le_bytes(buf)
}
#[must_use]
pub fn size(&self) -> usize {
let mut buf = [0u8; 8];
buf.copy_from_slice(&self.bytes[24..32]);
u64::from_le_bytes(buf) as usize
}
#[must_use]
pub fn access(&self) -> FabricAccess {
match self.bytes[32] {
1 => FabricAccess::ReadOnly,
2 => FabricAccess::ReadWrite,
_ => FabricAccess::None,
}
}
}
#[derive(Debug)]
pub struct FabricMemory {
handle: CUmemGenericAllocationHandle,
fabric_uuid: u128,
props: FabricAllocationProps,
export_count: u32,
released: bool,
}
impl FabricMemory {
pub fn allocate(
handle: CUmemGenericAllocationHandle,
fabric_uuid: u128,
props: FabricAllocationProps,
) -> CudaResult<Self> {
props.validate()?;
Ok(Self {
handle,
fabric_uuid,
props,
export_count: 0,
released: false,
})
}
#[must_use]
pub fn allocation_handle(&self) -> CUmemGenericAllocationHandle {
self.handle
}
#[must_use]
pub fn props(&self) -> FabricAllocationProps {
self.props
}
#[must_use]
pub fn export_count(&self) -> u32 {
self.export_count
}
pub fn export_shareable(&mut self) -> CudaResult<FabricHandle> {
if self.released {
return Err(CudaError::InvalidHandle);
}
if !self.props.peer_access.is_mappable() {
return Err(CudaError::NotPermitted);
}
self.export_count = self.export_count.saturating_add(1);
Ok(FabricHandle::encode(
self.fabric_uuid,
self.props.size,
self.props.peer_access,
))
}
pub fn retire_export(&mut self) -> CudaResult<()> {
if self.export_count == 0 {
return Err(CudaError::IllegalState);
}
self.export_count -= 1;
Ok(())
}
pub fn release(&mut self) -> CudaResult<()> {
if self.export_count != 0 {
return Err(CudaError::IllegalState);
}
self.released = true;
Ok(())
}
#[must_use]
pub fn is_released(&self) -> bool {
self.released
}
}
#[derive(Debug)]
pub struct FabricImporter {
device_ordinal: i32,
reachable: bool,
next_handle: u64,
}
impl FabricImporter {
#[must_use]
pub fn new(device_ordinal: i32) -> Self {
Self {
device_ordinal,
reachable: true,
next_handle: 1,
}
}
#[must_use]
pub fn device_ordinal(&self) -> i32 {
self.device_ordinal
}
pub fn set_reachable(&mut self, reachable: bool) {
self.reachable = reachable;
}
pub fn import(&mut self, handle: &FabricHandle) -> CudaResult<FabricImport> {
if !self.reachable {
return Err(CudaError::NotSupported);
}
let access = handle.access();
if !access.is_mappable() {
return Err(CudaError::NotPermitted);
}
let imported = self.next_handle;
self.next_handle += 1;
Ok(FabricImport {
handle: imported,
fabric_uuid: handle.fabric_uuid(),
size: handle.size(),
access,
device_ordinal: self.device_ordinal,
mapped: false,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FabricImport {
handle: u64,
fabric_uuid: u128,
size: usize,
access: FabricAccess,
device_ordinal: i32,
mapped: bool,
}
impl FabricImport {
#[must_use]
pub fn handle(&self) -> u64 {
self.handle
}
#[must_use]
pub fn fabric_uuid(&self) -> u128 {
self.fabric_uuid
}
#[must_use]
pub fn size(&self) -> usize {
self.size
}
#[must_use]
pub fn access(&self) -> FabricAccess {
self.access
}
#[must_use]
pub fn device_ordinal(&self) -> i32 {
self.device_ordinal
}
pub fn map_readable(&mut self) -> CudaResult<()> {
if !self.access.is_mappable() {
return Err(CudaError::NotPermitted);
}
self.mapped = true;
Ok(())
}
pub fn map_writable(&mut self) -> CudaResult<()> {
if !self.access.is_writable() {
return Err(CudaError::NotPermitted);
}
self.mapped = true;
Ok(())
}
#[must_use]
pub fn is_mapped(&self) -> bool {
self.mapped
}
}
#[cfg(test)]
mod tests {
use super::*;
const UUID: u128 = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210;
fn make_exporter(access: FabricAccess) -> FabricMemory {
let props = FabricAllocationProps {
device_ordinal: 0,
size: 2 * 1024 * 1024,
peer_access: access,
handle_type: CUmemAllocationHandleType::Fabric,
};
FabricMemory::allocate(42, UUID, props).expect("valid props")
}
#[test]
fn props_require_fabric_handle_type() {
let props = FabricAllocationProps {
device_ordinal: 0,
size: 4096,
peer_access: FabricAccess::ReadWrite,
handle_type: CUmemAllocationHandleType::PosixFileDescriptor,
};
assert!(matches!(
FabricMemory::allocate(1, UUID, props),
Err(CudaError::NotSupported)
));
}
#[test]
fn props_reject_zero_size() {
let props = FabricAllocationProps::read_write(0, 0);
assert!(matches!(
FabricMemory::allocate(1, UUID, props),
Err(CudaError::InvalidValue)
));
}
#[test]
fn handle_round_trips_through_bytes() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
let handle = mem.export_shareable().expect("export ok");
let wire = handle.to_bytes();
assert_eq!(wire.len(), FABRIC_HANDLE_BYTES);
let restored = FabricHandle::from_bytes(&wire).expect("decode ok");
assert_eq!(restored, handle);
assert_eq!(restored.fabric_uuid(), UUID);
assert_eq!(restored.size(), 2 * 1024 * 1024);
assert_eq!(restored.access(), FabricAccess::ReadWrite);
}
#[test]
fn from_bytes_rejects_wrong_length() {
assert!(matches!(
FabricHandle::from_bytes(&[0u8; 32]),
Err(CudaError::InvalidValue)
));
}
#[test]
fn from_bytes_rejects_bad_magic() {
let mut blob = [0u8; FABRIC_HANDLE_BYTES];
blob[0] = b'X';
assert!(matches!(
FabricHandle::from_bytes(&blob),
Err(CudaError::InvalidHandle)
));
}
#[test]
fn export_bumps_reference_count() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
assert_eq!(mem.export_count(), 0);
let _h1 = mem.export_shareable().unwrap();
let _h2 = mem.export_shareable().unwrap();
assert_eq!(mem.export_count(), 2);
}
#[test]
fn none_access_cannot_be_exported() {
let mut mem = make_exporter(FabricAccess::None);
assert!(matches!(
mem.export_shareable(),
Err(CudaError::NotPermitted)
));
}
#[test]
fn release_blocked_while_exports_outstanding() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
let _h = mem.export_shareable().unwrap();
assert!(matches!(mem.release(), Err(CudaError::IllegalState)));
mem.retire_export().unwrap();
mem.release().unwrap();
assert!(mem.is_released());
}
#[test]
fn retire_export_underflow_is_illegal_state() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
assert!(matches!(mem.retire_export(), Err(CudaError::IllegalState)));
}
#[test]
fn export_after_release_fails() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
mem.release().unwrap();
assert!(matches!(
mem.export_shareable(),
Err(CudaError::InvalidHandle)
));
}
#[test]
fn import_maps_same_physical_memory() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
let handle = mem.export_shareable().unwrap();
let wire = handle.to_bytes();
let received = FabricHandle::from_bytes(&wire).unwrap();
let mut importer = FabricImporter::new(1);
let import = importer.import(&received).expect("import ok");
assert_eq!(import.fabric_uuid(), UUID);
assert_eq!(import.size(), 2 * 1024 * 1024);
assert_eq!(import.device_ordinal(), 1);
assert_eq!(import.access(), FabricAccess::ReadWrite);
}
#[test]
fn import_assigns_distinct_handles() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
let handle = mem.export_shareable().unwrap();
let mut importer = FabricImporter::new(2);
let a = importer.import(&handle).unwrap();
let b = importer.import(&handle).unwrap();
assert_ne!(a.handle(), b.handle());
}
#[test]
fn unreachable_importer_gets_not_supported() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
let handle = mem.export_shareable().unwrap();
let mut importer = FabricImporter::new(3);
importer.set_reachable(false);
assert!(matches!(
importer.import(&handle),
Err(CudaError::NotSupported)
));
}
#[test]
fn read_only_export_blocks_writable_map() {
let mut mem = make_exporter(FabricAccess::ReadOnly);
let handle = mem.export_shareable().unwrap();
let mut importer = FabricImporter::new(1);
let mut import = importer.import(&handle).unwrap();
import.map_readable().unwrap();
assert!(import.is_mapped());
assert!(matches!(
import.map_writable(),
Err(CudaError::NotPermitted)
));
}
#[test]
fn read_write_export_allows_writable_map() {
let mut mem = make_exporter(FabricAccess::ReadWrite);
let handle = mem.export_shareable().unwrap();
let mut importer = FabricImporter::new(1);
let mut import = importer.import(&handle).unwrap();
import.map_writable().unwrap();
assert!(import.is_mapped());
}
#[test]
fn fabric_access_predicates() {
assert!(!FabricAccess::None.is_mappable());
assert!(FabricAccess::ReadOnly.is_mappable());
assert!(!FabricAccess::ReadOnly.is_writable());
assert!(FabricAccess::ReadWrite.is_writable());
}
}