use super::*;
#[derive(Clone, Copy)]
pub enum MemorySite<'a> {
Host,
Device(&'a Device, u32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransitionKind {
Unknown,
None,
Range,
Global,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CacheExecutor {
None,
Queue,
Program,
HostDirect,
HostApi,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CacheOperation {
None,
ReleaseToSystem,
AcquireFromSystem,
}
#[derive(Clone, Copy, Debug)]
pub struct CacheTransition {
pub kind: TransitionKind,
pub executor: CacheExecutor,
pub operation: CacheOperation,
pub range_granularity: u64,
native: amdf_cache_transition_t,
}
impl CacheTransition {
fn from_native(native: amdf_cache_transition_t) -> Result<Self> {
Ok(Self {
kind: match native.kind {
AMDF_CACHE_TRANSITION_KIND_UNKNOWN => TransitionKind::Unknown,
AMDF_CACHE_TRANSITION_KIND_NONE => TransitionKind::None,
AMDF_CACHE_TRANSITION_KIND_RANGE => TransitionKind::Range,
AMDF_CACHE_TRANSITION_KIND_GLOBAL => TransitionKind::Global,
_ => return Err(missing("cache transition kind")),
},
executor: match native.executor {
AMDF_CACHE_TRANSITION_EXECUTOR_NONE => CacheExecutor::None,
AMDF_CACHE_TRANSITION_EXECUTOR_QUEUE => CacheExecutor::Queue,
AMDF_CACHE_TRANSITION_EXECUTOR_PROGRAM => CacheExecutor::Program,
AMDF_CACHE_TRANSITION_EXECUTOR_HOST_DIRECT => CacheExecutor::HostDirect,
AMDF_CACHE_TRANSITION_EXECUTOR_HOST_API => CacheExecutor::HostApi,
_ => return Err(missing("cache transition executor")),
},
operation: match native.operation {
AMDF_CACHE_OPERATION_NONE => CacheOperation::None,
AMDF_CACHE_OPERATION_RELEASE_TO_SYSTEM => CacheOperation::ReleaseToSystem,
AMDF_CACHE_OPERATION_ACQUIRE_FROM_SYSTEM => CacheOperation::AcquireFromSystem,
_ => return Err(missing("cache operation")),
},
range_granularity: native.range_granularity,
native,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AtomicScope {
None,
Device,
Fabric,
System,
}
impl AtomicScope {
fn from_native(scope: amdf_atomic_scope_t) -> Result<Self> {
match scope {
AMDF_ATOMIC_SCOPE_NONE => Ok(Self::None),
AMDF_ATOMIC_SCOPE_DEVICE => Ok(Self::Device),
AMDF_ATOMIC_SCOPE_FABRIC => Ok(Self::Fabric),
AMDF_ATOMIC_SCOPE_SYSTEM => Ok(Self::System),
_ => Err(missing("atomic scope")),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct VisibilityFacts {
pub shared_backing_reachable: bool,
pub release: CacheTransition,
pub acquire: CacheTransition,
pub atomic_scope_32: AtomicScope,
pub atomic_scope_64: AtomicScope,
pub estimated_fixed_cost_ns: Option<u64>,
}
impl VisibilityFacts {
pub(super) fn from_native(info: amdf_memory_pair_info_t) -> Result<Self> {
Ok(Self {
shared_backing_reachable: info.flags
& AMDF_MEMORY_PAIR_FLAG_SHARED_BACKING_REACHABLE as u64
!= 0,
release: CacheTransition::from_native(info.release)?,
acquire: CacheTransition::from_native(info.acquire)?,
atomic_scope_32: AtomicScope::from_native(info.atomic_reach.scope_32)?,
atomic_scope_64: AtomicScope::from_native(info.atomic_reach.scope_64)?,
estimated_fixed_cost_ns: (info.flags & AMDF_MEMORY_PAIR_FLAG_FIXED_COST_KNOWN as u64
!= 0)
.then_some(info.estimated_fixed_cost_nanoseconds),
})
}
}
pub struct MemoryVisibility {
buffer: Buffer,
facts: VisibilityFacts,
}
impl std::ops::Deref for MemoryVisibility {
type Target = VisibilityFacts;
fn deref(&self) -> &Self::Target {
&self.facts
}
}
impl Buffer {
fn site(&self, site: MemorySite<'_>) -> Result<amdf_memory_site_t> {
let mut result = amdf_memory_site_t {
type_: AMDF_STRUCTURE_TYPE_MEMORY_SITE,
structure_size: size_of::<amdf_memory_site_t>() as u32,
..Default::default()
};
match site {
MemorySite::Host => {
if let Some(source) = &self.0.source {
return source.site(site);
}
result.kind = AMDF_MEMORY_SITE_KIND_HOST;
result.value.host_mapping = self.0.mapping;
}
MemorySite::Device(device, family) => {
let Some(index) = self
.0
.devices
.iter()
.position(|entry| Arc::ptr_eq(&entry.0, &device.0))
else {
return self.0.source.as_ref().map_or_else(
|| {
Err(Error::Message(
"visibility device has no access to backing".into(),
))
},
|source| source.site(site),
);
};
result.kind = AMDF_MEMORY_SITE_KIND_DEVICE;
result.value.device = amdf_memory_site_t__bindgen_ty_1__bindgen_ty_1 {
memory: self.0.raw,
access_ordinal: index as u32,
queue_family_ordinal: family,
};
}
}
Ok(result)
}
pub fn visibility(
&self,
producer: MemorySite<'_>,
consumer: MemorySite<'_>,
) -> Result<MemoryVisibility> {
let mut info = amdf_memory_pair_info_t {
type_: AMDF_STRUCTURE_TYPE_MEMORY_PAIR_INFO,
structure_size: size_of::<amdf_memory_pair_info_t>() as u32,
..Default::default()
};
check("memory_query_pair_info", unsafe {
entry!(self.0.instance.api.core, memory_query_pair_info)(
&self.site(producer)?,
&self.site(consumer)?,
&mut info,
)
})?;
Ok(MemoryVisibility {
buffer: self.clone(),
facts: VisibilityFacts::from_native(info)?,
})
}
}
impl MemoryVisibility {
pub unsafe fn release_host(&self, offset: usize, length: usize) -> Result<()> {
unsafe { self.host_transition(self.release, offset, length) }
}
pub unsafe fn acquire_host(&self, offset: usize, length: usize) -> Result<()> {
unsafe { self.host_transition(self.acquire, offset, length) }
}
unsafe fn host_transition(
&self,
transition: CacheTransition,
offset: usize,
length: usize,
) -> Result<()> {
if offset
.checked_add(length)
.is_none_or(|end| end > self.buffer.len())
{
return Err(Error::Message("visibility range out of bounds".into()));
}
if !self.shared_backing_reachable {
return Err(Error::Unsupported("shared backing is unreachable".into()));
}
if transition.kind == TransitionKind::None {
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
return Ok(());
}
if !matches!(
transition.executor,
CacheExecutor::HostApi | CacheExecutor::HostDirect
) {
return Err(Error::Unsupported(
"transition is not a qualified host action".into(),
));
}
let mut buffer = &self.buffer;
while let Some(source) = &buffer.0.source {
buffer = source;
}
check("prepared host visibility", unsafe {
entry!(buffer.0.instance.api.core, host_mapping_cache_control)(
buffer.0.mapping,
transition.native.host_operation,
offset as u64,
length as u64,
)
})
}
}
pub struct PreparedHostVisibility {
visibility: MemoryVisibility,
transition: CacheTransition,
ranges: Vec<std::ops::Range<usize>>,
}
impl MemoryVisibility {
pub fn prepare_release_host(
&self,
ranges: &[std::ops::Range<usize>],
) -> Result<PreparedHostVisibility> {
self.prepare_host(self.release, ranges)
}
pub fn prepare_acquire_host(
&self,
ranges: &[std::ops::Range<usize>],
) -> Result<PreparedHostVisibility> {
self.prepare_host(self.acquire, ranges)
}
fn prepare_host(
&self,
transition: CacheTransition,
ranges: &[std::ops::Range<usize>],
) -> Result<PreparedHostVisibility> {
if !self.shared_backing_reachable || transition.kind == TransitionKind::Unknown {
return Err(Error::Unsupported(
"unqualified host visibility relation".into(),
));
}
if transition.kind != TransitionKind::None
&& !matches!(
transition.executor,
CacheExecutor::HostDirect | CacheExecutor::HostApi
)
{
return Err(Error::Unsupported(
"transition is not a qualified host action".into(),
));
}
let mut ranges = coalesce_ranges(ranges, self.buffer.len())?;
if !ranges.is_empty()
&& matches!(
transition.kind,
TransitionKind::None | TransitionKind::Global
)
{
ranges.clear();
ranges.push(0..self.buffer.len());
}
Ok(PreparedHostVisibility {
visibility: MemoryVisibility {
buffer: self.buffer.clone(),
facts: self.facts,
},
transition,
ranges,
})
}
}
fn coalesce_ranges(
ranges: &[std::ops::Range<usize>],
length: usize,
) -> Result<Vec<std::ops::Range<usize>>> {
if ranges.iter().any(|r| r.start > r.end || r.end > length) {
return Err(Error::Message("visibility range out of bounds".into()));
}
let mut sorted: Vec<_> = ranges.iter().filter(|r| !r.is_empty()).cloned().collect();
sorted.sort_unstable_by_key(|r| r.start);
let mut result: Vec<std::ops::Range<usize>> = Vec::with_capacity(sorted.len());
for range in sorted {
if let Some(last) = result.last_mut()
&& last.end >= range.start
{
last.end = last.end.max(range.end);
} else {
result.push(range);
}
}
Ok(result)
}
impl PreparedHostVisibility {
pub fn operation_count(&self) -> usize {
self.ranges.len()
}
pub unsafe fn execute(&self) -> Result<()> {
for range in &self.ranges {
unsafe {
self.visibility
.host_transition(self.transition, range.start, range.len())
}?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn host_ranges_preserve_holes_and_reject_out_of_bounds() {
assert_eq!(
coalesce_ranges(&[8..16, 0..4, 3..10, 24..32, 16..16], 32).unwrap(),
vec![0..16, 24..32]
);
assert!(coalesce_ranges(std::slice::from_ref(&(0..33)), 32).is_err());
assert!(coalesce_ranges(&[std::ops::Range { start: 9, end: 8 }], 32).is_err());
assert!(
coalesce_ranges(std::slice::from_ref(&(0..0)), 0)
.unwrap()
.is_empty()
);
}
}