use crate::hip::{
result::{self, HipError},
sys::{self},
};
use core::ffi::{c_int, c_uint};
use std::{
ffi::CString,
marker::PhantomData,
ops::{Bound, RangeBounds},
sync::{
Arc,
atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
},
};
const HIP_EVENT_DISABLE_TIMING: c_uint = 0x2;
const HIP_EVENT_BLOCKING_SYNC: c_uint = 0x1;
const HIP_HOST_MALLOC_WRITE_COMBINED: c_uint = 0x4;
const HIP_DEVICE_SCHEDULE_BLOCKING_SYNC: c_uint = 0x4;
#[derive(Debug, Clone, Copy)]
pub enum EventWaitFlags {
Default,
External,
}
impl EventWaitFlags {
#[inline]
fn to_raw(self) -> c_uint {
match self {
Self::Default => 0x0,
Self::External => 0x1,
}
}
}
#[derive(Debug)]
pub struct HipContext {
pub(crate) hip_device: sys::hipDevice_t,
pub(crate) ordinal: usize,
pub(crate) has_async_alloc: bool,
pub(crate) num_streams: AtomicUsize,
pub(crate) event_tracking: AtomicBool,
pub(crate) error_state: AtomicU32,
}
unsafe impl Send for HipContext {}
unsafe impl Sync for HipContext {}
impl PartialEq for HipContext {
fn eq(&self, other: &Self) -> bool {
self.ordinal == other.ordinal
}
}
impl Eq for HipContext {}
impl HipContext {
pub fn new(ordinal: usize) -> Result<Arc<Self>, HipError> {
result::init()?;
let hip_device = result::device::get(ordinal as i32)?;
let has_async_alloc = result::device::get_attribute(
hip_device,
sys::hipDeviceAttribute_t::hipDeviceAttributeMemoryPoolsSupported,
)? > 0;
let ctx = Arc::new(HipContext {
hip_device,
ordinal,
has_async_alloc,
num_streams: AtomicUsize::new(0),
event_tracking: AtomicBool::new(true),
error_state: AtomicU32::new(0),
});
ctx.bind_to_thread()?;
Ok(ctx)
}
pub fn has_async_alloc(&self) -> bool {
self.has_async_alloc
}
pub fn ordinal(&self) -> usize {
self.ordinal
}
pub fn device_count() -> Result<i32, HipError> {
Ok(result::device::get_count()?)
}
pub fn current_device() -> Result<i32, HipError> {
result::device::current()
}
pub fn properties(&self) -> Result<sys::hipDeviceProp_tR0600, HipError> {
result::device::get_properties(self.ordinal as core::ffi::c_int)
}
pub fn gfx_arch(&self) -> Result<String, HipError> {
let props = self.properties()?;
let cstr = unsafe { core::ffi::CStr::from_ptr(props.gcnArchName.as_ptr()) };
let raw = cstr.to_string_lossy();
Ok(raw.split(':').next().unwrap_or(&raw).to_string())
}
pub fn gfx_version(&self) -> Result<crate::hiprtc::GfxVersion, HipError> {
use std::str::FromStr;
let arch = self.gfx_arch()?;
crate::hiprtc::GfxVersion::from_str(&arch)
.map_err(|_| HipError(sys::hipError_t::hipErrorInvalidValue))
}
pub unsafe fn reset() -> Result<(), HipError> {
unsafe { result::device::reset() }
}
pub fn name(&self) -> Result<String, HipError> {
result::device::get_name(self.hip_device)
}
pub fn uuid(&self) -> Result<sys::hipUUID, HipError> {
result::device::get_uuid(self.hip_device)
}
pub fn compute_capability(&self) -> Result<(i32, i32), HipError> {
let major = result::device::get_attribute(
self.hip_device,
sys::hipDeviceAttribute_t::hipDeviceAttributeComputeCapabilityMajor,
)?;
let minor = result::device::get_attribute(
self.hip_device,
sys::hipDeviceAttribute_t::hipDeviceAttributeComputeCapabilityMinor,
)?;
Ok((major, minor))
}
pub fn total_mem(&self) -> Result<usize, HipError> {
result::device::total_mem(self.hip_device)
}
pub fn mem_get_info(&self) -> Result<(usize, usize), HipError> {
self.bind_to_thread()?;
result::mem_get_info()
}
pub fn hip_device(&self) -> sys::hipDevice_t {
self.hip_device
}
pub fn bind_to_thread(&self) -> Result<(), HipError> {
self.check_err()?;
let mut curr: i32 = -1;
result::device::get_device(&mut curr)?;
if curr != self.ordinal as _ {
result::device::set_device(self.ordinal as _)?;
}
Ok(())
}
pub fn attribute(&self, attr: sys::hipDeviceAttribute_t) -> Result<i32, HipError> {
self.check_err()?;
result::device::get_attribute(self.hip_device, attr)
}
pub fn synchronize(&self) -> Result<(), HipError> {
self.bind_to_thread()?;
result::device::synchronize()
}
pub fn set_blocking_synchronize(&self) -> Result<(), HipError> {
self.bind_to_thread()?;
unsafe { sys::hipSetDeviceFlags(HIP_DEVICE_SCHEDULE_BLOCKING_SYNC).result() }
}
pub fn get_limit(&self, limit: sys::hipLimit_t) -> Result<usize, HipError> {
self.bind_to_thread()?;
result::device::get_limit(limit)
}
pub fn set_limit(&self, limit: sys::hipLimit_t, value: usize) -> Result<(), HipError> {
self.bind_to_thread()?;
result::device::set_limit(limit, value)
}
pub fn get_cache_config(&self) -> Result<sys::hipFuncCache_t, HipError> {
self.bind_to_thread()?;
result::device::get_cache_config()
}
pub fn set_cache_config(&self, config: sys::hipFuncCache_t) -> Result<(), HipError> {
self.bind_to_thread()?;
result::device::set_cache_config(config)
}
pub fn is_in_multi_stream_mode(&self) -> bool {
self.num_streams.load(Ordering::Relaxed) > 0
}
pub fn is_event_tracking(&self) -> bool {
self.event_tracking.load(Ordering::Relaxed)
}
pub fn is_managing_stream_synchronization(&self) -> bool {
self.is_in_multi_stream_mode() && self.is_event_tracking()
}
pub unsafe fn enable_event_tracking(&self) {
self.event_tracking.store(true, Ordering::Relaxed);
}
pub unsafe fn disable_event_tracking(&self) {
self.event_tracking.store(false, Ordering::Relaxed);
}
pub fn check_err(&self) -> Result<(), HipError> {
let raw = self.error_state.swap(0, Ordering::Relaxed);
if raw == 0 {
Ok(())
} else {
Err(HipError(unsafe {
std::mem::transmute::<u32, sys::hipError_t>(raw)
}))
}
}
pub fn record_err<T>(&self, result: Result<T, HipError>) {
if let Err(err) = result {
self.error_state.store(err.0 as u32, Ordering::Relaxed);
}
}
}
#[derive(Debug)]
pub struct HipEvent {
pub(crate) hip_event: sys::hipEvent_t,
pub(crate) ctx: Arc<HipContext>,
}
unsafe impl Send for HipEvent {}
unsafe impl Sync for HipEvent {}
impl Drop for HipEvent {
fn drop(&mut self) {
self.ctx.record_err(self.ctx.bind_to_thread());
self.ctx.record_err(result::event::destroy(self.hip_event));
}
}
impl HipContext {
pub fn new_event(self: &Arc<Self>, flags: Option<c_uint>) -> Result<HipEvent, HipError> {
let flags = flags.unwrap_or(HIP_EVENT_DISABLE_TIMING);
self.bind_to_thread()?;
let hip_event = result::event::create_with_flags(flags)?;
Ok(HipEvent {
hip_event,
ctx: self.clone(),
})
}
}
impl HipEvent {
pub fn hip_event(&self) -> sys::hipEvent_t {
self.hip_event
}
pub fn context(&self) -> &Arc<HipContext> {
&self.ctx
}
pub fn record(&self, stream: &HipStream) -> Result<(), HipError> {
assert!(
Arc::ptr_eq(&self.ctx, &stream.ctx),
"HipEvent::record: event and stream belong to different contexts",
);
self.ctx.bind_to_thread()?;
result::event::record(self.hip_event, stream.hip_stream)
}
pub fn synchronize(&self) -> Result<(), HipError> {
self.ctx.bind_to_thread()?;
result::event::synchronize(self.hip_event)
}
pub fn elapsed_ms(&self, end: &Self) -> Result<f32, HipError> {
self.synchronize()?;
end.synchronize()?;
result::event::elapsed(self.hip_event, end.hip_event)
}
pub fn is_complete(&self) -> Result<bool, HipError> {
self.ctx.bind_to_thread()?;
result::event::query(self.hip_event)
}
}
#[derive(Debug)]
pub struct HipStream {
pub(crate) hip_stream: sys::hipStream_t,
pub(crate) ctx: Arc<HipContext>,
}
impl PartialEq for HipStream {
fn eq(&self, other: &Self) -> bool {
self.hip_stream == other.hip_stream
}
}
impl Eq for HipStream {}
unsafe impl Send for HipStream {}
unsafe impl Sync for HipStream {}
const HIP_STREAM_PER_THREAD: usize = 0x2;
impl Drop for HipStream {
fn drop(&mut self) {
self.ctx.record_err(self.ctx.bind_to_thread());
let hip_stream = std::mem::replace(&mut self.hip_stream, std::ptr::null_mut());
if !hip_stream.is_null() && hip_stream as usize != HIP_STREAM_PER_THREAD {
self.ctx.num_streams.fetch_sub(1, Ordering::Relaxed);
self.ctx.record_err(result::stream::destroy(hip_stream));
}
}
}
impl HipContext {
pub fn default_stream(self: &Arc<Self>) -> Arc<HipStream> {
Arc::new(HipStream {
hip_stream: std::ptr::null_mut(),
ctx: self.clone(),
})
}
pub fn per_thread_stream(self: &Arc<Self>) -> Arc<HipStream> {
Arc::new(HipStream {
hip_stream: HIP_STREAM_PER_THREAD as sys::hipStream_t,
ctx: self.clone(),
})
}
pub fn new_stream(self: &Arc<Self>) -> Result<Arc<HipStream>, HipError> {
self.bind_to_thread()?;
let prev = self.num_streams.fetch_add(1, Ordering::Relaxed);
if prev == 0 && self.is_event_tracking() {
self.synchronize()?;
}
let hip_stream = result::stream::create(result::stream::StreamKind::NonBlocking)?;
Ok(Arc::new(HipStream {
hip_stream,
ctx: self.clone(),
}))
}
pub fn new_stream_with_priority(
self: &Arc<Self>,
priority: i32,
) -> Result<Arc<HipStream>, HipError> {
self.bind_to_thread()?;
let prev = self.num_streams.fetch_add(1, Ordering::Relaxed);
if prev == 0 && self.is_event_tracking() {
self.synchronize()?;
}
let hip_stream = result::stream::create_with_priority(
result::stream::StreamKind::NonBlocking,
priority,
)?;
Ok(Arc::new(HipStream {
hip_stream,
ctx: self.clone(),
}))
}
}
impl HipStream {
pub fn fork(&self) -> Result<Arc<Self>, HipError> {
self.ctx.bind_to_thread()?;
self.ctx.num_streams.fetch_add(1, Ordering::Relaxed);
let hip_stream = result::stream::create(result::stream::StreamKind::NonBlocking)?;
let stream = Arc::new(HipStream {
hip_stream,
ctx: self.ctx.clone(),
});
stream.join(self)?;
Ok(stream)
}
pub fn hip_stream(&self) -> sys::hipStream_t {
self.hip_stream
}
pub fn context(&self) -> &Arc<HipContext> {
&self.ctx
}
pub fn synchronize(&self) -> Result<(), HipError> {
self.ctx.bind_to_thread()?;
result::stream::synchronize(self.hip_stream)
}
pub fn record_event(&self, flags: Option<c_uint>) -> Result<HipEvent, HipError> {
let event = self.ctx.new_event(flags)?;
event.record(self)?;
Ok(event)
}
pub fn wait(&self, event: &HipEvent) -> Result<(), HipError> {
self.ctx.bind_to_thread()?;
unsafe {
result::stream::wait_event(
self.hip_stream,
event.hip_event,
EventWaitFlags::Default.to_raw(),
)
}
}
pub fn join(&self, other: &HipStream) -> Result<(), HipError> {
self.wait(&other.record_event(None)?)
}
pub unsafe fn upgrade_device_ptr<T>(
self: &Arc<Self>,
ptr: sys::hipDeviceptr_t,
len: usize,
) -> Result<HipSlice<T>, HipError> {
let (read, write) = if self.ctx.is_event_tracking() {
(
Some(self.ctx.new_event(None)?),
Some(self.ctx.new_event(None)?),
)
} else {
(None, None)
};
Ok(HipSlice {
hip_device_ptr: ptr,
len,
read,
write,
stream: self.clone(),
marker: PhantomData,
})
}
}
#[derive(Debug)]
#[must_use]
pub enum SyncOnDrop<'a> {
Record(Option<(&'a HipEvent, &'a HipStream)>),
Sync(Option<&'a HipStream>),
}
impl<'a> SyncOnDrop<'a> {
pub fn record_event(event: &'a Option<HipEvent>, stream: &'a HipStream) -> Self {
match event {
Some(e) => SyncOnDrop::Record(Some((e, stream))),
None => SyncOnDrop::Record(None),
}
}
pub fn sync_stream(stream: &'a HipStream) -> Self {
SyncOnDrop::Sync(Some(stream))
}
}
impl Drop for SyncOnDrop<'_> {
fn drop(&mut self) {
match self {
SyncOnDrop::Record(target) => {
if let Some((event, stream)) = std::mem::take(target) {
stream.ctx.record_err(event.record(stream));
}
}
SyncOnDrop::Sync(target) => {
if let Some(stream) = std::mem::take(target) {
stream.ctx.record_err(stream.synchronize());
}
}
}
}
}
pub trait DeviceSlice<T> {
fn len(&self) -> usize;
fn num_bytes(&self) -> usize {
self.len() * std::mem::size_of::<T>()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn stream(&self) -> &Arc<HipStream>;
}
pub trait DevicePtr<T>: DeviceSlice<T> {
fn device_ptr<'a>(&'a self, stream: &'a HipStream) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>);
}
pub trait DevicePtrMut<T>: DeviceSlice<T> {
fn device_ptr_mut<'a>(
&'a mut self,
stream: &'a HipStream,
) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>);
}
pub trait HostSlice<T> {
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
unsafe fn stream_synced_slice<'a>(&'a self, stream: &'a HipStream)
-> (&'a [T], SyncOnDrop<'a>);
unsafe fn stream_synced_mut_slice<'a>(
&'a mut self,
stream: &'a HipStream,
) -> (&'a mut [T], SyncOnDrop<'a>);
}
pub unsafe trait DeviceRepr {}
unsafe impl DeviceRepr for bool {}
unsafe impl DeviceRepr for i8 {}
unsafe impl DeviceRepr for i16 {}
unsafe impl DeviceRepr for i32 {}
unsafe impl DeviceRepr for i64 {}
unsafe impl DeviceRepr for i128 {}
unsafe impl DeviceRepr for isize {}
unsafe impl DeviceRepr for u8 {}
unsafe impl DeviceRepr for u16 {}
unsafe impl DeviceRepr for u32 {}
unsafe impl DeviceRepr for u64 {}
unsafe impl DeviceRepr for u128 {}
unsafe impl DeviceRepr for usize {}
unsafe impl DeviceRepr for f32 {}
unsafe impl DeviceRepr for f64 {}
unsafe impl DeviceRepr for half::f16 {}
unsafe impl DeviceRepr for half::bf16 {}
#[cfg(feature = "f8")]
unsafe impl DeviceRepr for float8::F8E4M3 {}
#[cfg(feature = "f8")]
unsafe impl DeviceRepr for float8::F8E5M2 {}
#[cfg(feature = "f4")]
unsafe impl DeviceRepr for float4::F4E2M1 {}
#[cfg(feature = "f4")]
unsafe impl DeviceRepr for float4::E8M0 {}
unsafe impl<const N: usize, T: DeviceRepr> DeviceRepr for [T; N] {}
pub unsafe trait ValidAsZeroBits {}
unsafe impl ValidAsZeroBits for bool {}
unsafe impl ValidAsZeroBits for i8 {}
unsafe impl ValidAsZeroBits for i16 {}
unsafe impl ValidAsZeroBits for i32 {}
unsafe impl ValidAsZeroBits for i64 {}
unsafe impl ValidAsZeroBits for i128 {}
unsafe impl ValidAsZeroBits for isize {}
unsafe impl ValidAsZeroBits for u8 {}
unsafe impl ValidAsZeroBits for u16 {}
unsafe impl ValidAsZeroBits for u32 {}
unsafe impl ValidAsZeroBits for u64 {}
unsafe impl ValidAsZeroBits for u128 {}
unsafe impl ValidAsZeroBits for usize {}
unsafe impl ValidAsZeroBits for f32 {}
unsafe impl ValidAsZeroBits for f64 {}
unsafe impl ValidAsZeroBits for half::f16 {}
unsafe impl ValidAsZeroBits for half::bf16 {}
#[cfg(feature = "f8")]
unsafe impl ValidAsZeroBits for float8::F8E4M3 {}
#[cfg(feature = "f8")]
unsafe impl ValidAsZeroBits for float8::F8E5M2 {}
#[cfg(feature = "f4")]
unsafe impl ValidAsZeroBits for float4::F4E2M1 {}
#[cfg(feature = "f4")]
unsafe impl ValidAsZeroBits for float4::E8M0 {}
unsafe impl<const N: usize, T: ValidAsZeroBits> ValidAsZeroBits for [T; N] {}
macro_rules! impl_tuples_zero {
($t:tt) => {
impl_tuples_zero!(@ $t);
};
($l:tt $(,$t:tt)+) => {
impl_tuples_zero!($($t),+);
impl_tuples_zero!(@ $l $(,$t)+);
};
(@ $($t:tt),+) => {
unsafe impl<$($t: ValidAsZeroBits,)+> ValidAsZeroBits for ($($t,)+) {}
};
}
impl_tuples_zero!(A, B, C, D, E, F, G, H, I, J, K, L);
macro_rules! impl_tuples_repr {
($t:tt) => {
impl_tuples_repr!(@ $t);
};
($l:tt $(,$t:tt)+) => {
impl_tuples_repr!($($t),+);
impl_tuples_repr!(@ $l $(,$t)+);
};
(@ $($t:tt),+) => {
unsafe impl<$($t: DeviceRepr,)+> DeviceRepr for ($($t,)+) {}
};
}
impl_tuples_repr!(A, B, C, D, E, F, G, H, I, J, K, L);
impl<T, const N: usize> HostSlice<T> for [T; N] {
fn len(&self) -> usize {
N
}
unsafe fn stream_synced_slice<'a>(
&'a self,
_stream: &'a HipStream,
) -> (&'a [T], SyncOnDrop<'a>) {
(self.as_slice(), SyncOnDrop::Sync(None))
}
unsafe fn stream_synced_mut_slice<'a>(
&'a mut self,
_stream: &'a HipStream,
) -> (&'a mut [T], SyncOnDrop<'a>) {
(self.as_mut_slice(), SyncOnDrop::Sync(None))
}
}
impl<T> HostSlice<T> for [T] {
fn len(&self) -> usize {
<[T]>::len(self)
}
unsafe fn stream_synced_slice<'a>(
&'a self,
_stream: &'a HipStream,
) -> (&'a [T], SyncOnDrop<'a>) {
(self, SyncOnDrop::Sync(None))
}
unsafe fn stream_synced_mut_slice<'a>(
&'a mut self,
_stream: &'a HipStream,
) -> (&'a mut [T], SyncOnDrop<'a>) {
(self, SyncOnDrop::Sync(None))
}
}
impl<T> HostSlice<T> for Vec<T> {
fn len(&self) -> usize {
Vec::len(self)
}
unsafe fn stream_synced_slice<'a>(
&'a self,
_stream: &'a HipStream,
) -> (&'a [T], SyncOnDrop<'a>) {
(self.as_slice(), SyncOnDrop::Sync(None))
}
unsafe fn stream_synced_mut_slice<'a>(
&'a mut self,
_stream: &'a HipStream,
) -> (&'a mut [T], SyncOnDrop<'a>) {
(self.as_mut_slice(), SyncOnDrop::Sync(None))
}
}
#[derive(Debug)]
pub struct HipSlice<T> {
pub(crate) hip_device_ptr: sys::hipDeviceptr_t,
pub(crate) len: usize,
pub(crate) read: Option<HipEvent>,
pub(crate) write: Option<HipEvent>,
pub(crate) stream: Arc<HipStream>,
pub(crate) marker: PhantomData<*const T>,
}
unsafe impl<T: Send> Send for HipSlice<T> {}
unsafe impl<T: Sync> Sync for HipSlice<T> {}
impl<T> Drop for HipSlice<T> {
fn drop(&mut self) {
if let Some(r) = &self.read {
let _ = r.synchronize();
}
if let Some(w) = &self.write {
let _ = w.synchronize();
}
if self.hip_device_ptr.is_null() {
return;
}
let ptr = self.hip_device_ptr as u64;
if self.stream.ctx.has_async_alloc {
self.stream
.ctx
.record_err(result::free_async(ptr, self.stream.hip_stream));
} else {
self.stream.ctx.record_err(self.stream.synchronize());
self.stream.ctx.record_err(result::free_sync(ptr));
}
}
}
impl<T> HipSlice<T> {
pub fn len(&self) -> usize {
self.len
}
pub fn num_bytes(&self) -> usize {
self.len * std::mem::size_of::<T>()
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn ordinal(&self) -> usize {
self.stream.ctx.ordinal
}
pub fn context(&self) -> &Arc<HipContext> {
&self.stream.ctx
}
pub fn stream(&self) -> &Arc<HipStream> {
&self.stream
}
pub fn transmute<U>(self) -> Option<HipSlice<U>> {
let bytes = self.num_bytes();
let u_size = std::mem::size_of::<U>();
if u_size == 0 || bytes % u_size != 0 {
return None;
}
let new_len = bytes / u_size;
let s = std::mem::ManuallyDrop::new(self);
Some(HipSlice {
hip_device_ptr: s.hip_device_ptr,
len: new_len,
read: unsafe { std::ptr::read(&s.read) },
write: unsafe { std::ptr::read(&s.write) },
stream: unsafe { std::ptr::read(&s.stream) },
marker: PhantomData,
})
}
pub fn leak(self) -> (sys::hipDeviceptr_t, usize) {
let s = std::mem::ManuallyDrop::new(self);
if let Some(r) = unsafe { std::ptr::read(&s.read) } {
let _ = r.synchronize();
}
if let Some(w) = unsafe { std::ptr::read(&s.write) } {
let _ = w.synchronize();
}
let _stream: Arc<HipStream> = unsafe { std::ptr::read(&s.stream) };
(s.hip_device_ptr, s.len)
}
}
impl<'a, T> HipView<'a, T> {
pub fn transmute<U>(self) -> Option<HipView<'a, U>> {
let bytes = self.len * std::mem::size_of::<T>();
let u_size = std::mem::size_of::<U>();
if u_size == 0 || bytes % u_size != 0 {
return None;
}
Some(HipView {
ptr: self.ptr,
len: bytes / u_size,
read: self.read,
write: self.write,
stream: self.stream,
marker: PhantomData,
})
}
}
impl<'a, T> HipViewMut<'a, T> {
pub fn transmute_mut<U>(self) -> Option<HipViewMut<'a, U>> {
let bytes = self.len * std::mem::size_of::<T>();
let u_size = std::mem::size_of::<U>();
if u_size == 0 || bytes % u_size != 0 {
return None;
}
Some(HipViewMut {
ptr: self.ptr,
len: bytes / u_size,
read: self.read,
write: self.write,
stream: self.stream,
marker: PhantomData,
})
}
}
impl<T: DeviceRepr> Clone for HipSlice<T> {
fn clone(&self) -> Self {
self.stream
.clone_dtod(self)
.expect("HipSlice::clone: device-to-device copy failed")
}
}
impl<T: Clone + Default + DeviceRepr> TryFrom<HipSlice<T>> for Vec<T> {
type Error = HipError;
fn try_from(value: HipSlice<T>) -> Result<Self, Self::Error> {
value.stream.clone().clone_dtoh(&value)
}
}
#[derive(Debug)]
pub struct HipView<'a, T> {
pub(crate) ptr: sys::hipDeviceptr_t,
pub(crate) len: usize,
pub(crate) read: &'a Option<HipEvent>,
pub(crate) write: &'a Option<HipEvent>,
pub(crate) stream: &'a Arc<HipStream>,
pub(crate) marker: PhantomData<&'a [T]>,
}
#[derive(Debug)]
pub struct HipViewMut<'a, T> {
pub(crate) ptr: sys::hipDeviceptr_t,
pub(crate) len: usize,
pub(crate) read: &'a Option<HipEvent>,
pub(crate) write: &'a Option<HipEvent>,
pub(crate) stream: &'a Arc<HipStream>,
pub(crate) marker: PhantomData<&'a mut [T]>,
}
impl<T> HipSlice<T> {
pub fn as_view(&self) -> HipView<'_, T> {
HipView {
ptr: self.hip_device_ptr,
len: self.len,
read: &self.read,
write: &self.write,
stream: &self.stream,
marker: PhantomData,
}
}
pub fn as_view_mut(&mut self) -> HipViewMut<'_, T> {
HipViewMut {
ptr: self.hip_device_ptr,
len: self.len,
read: &self.read,
write: &self.write,
stream: &self.stream,
marker: PhantomData,
}
}
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> HipView<'_, T> {
let (start, end) = resolve_range(range, self.len);
let ptr = unsafe { (self.hip_device_ptr as *mut u8).add(start * std::mem::size_of::<T>()) }
as sys::hipDeviceptr_t;
HipView {
ptr,
len: end - start,
read: &self.read,
write: &self.write,
stream: &self.stream,
marker: PhantomData,
}
}
pub fn slice_mut<R: RangeBounds<usize>>(&mut self, range: R) -> HipViewMut<'_, T> {
let (start, end) = resolve_range(range, self.len);
let ptr = unsafe { (self.hip_device_ptr as *mut u8).add(start * std::mem::size_of::<T>()) }
as sys::hipDeviceptr_t;
HipViewMut {
ptr,
len: end - start,
read: &self.read,
write: &self.write,
stream: &self.stream,
marker: PhantomData,
}
}
}
impl<'a, T> HipView<'a, T> {
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> HipView<'_, T> {
let (start, end) = resolve_range(range, self.len);
let ptr = unsafe { (self.ptr as *mut u8).add(start * std::mem::size_of::<T>()) }
as sys::hipDeviceptr_t;
HipView {
ptr,
len: end - start,
read: self.read,
write: self.write,
stream: self.stream,
marker: PhantomData,
}
}
}
impl<'a, T> HipViewMut<'a, T> {
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_view(&self) -> HipView<'_, T> {
HipView {
ptr: self.ptr,
len: self.len,
read: self.read,
write: self.write,
stream: self.stream,
marker: PhantomData,
}
}
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> HipView<'_, T> {
let (start, end) = resolve_range(range, self.len);
let ptr = unsafe { (self.ptr as *mut u8).add(start * std::mem::size_of::<T>()) }
as sys::hipDeviceptr_t;
HipView {
ptr,
len: end - start,
read: self.read,
write: self.write,
stream: self.stream,
marker: PhantomData,
}
}
pub fn slice_mut<R: RangeBounds<usize>>(&mut self, range: R) -> HipViewMut<'_, T> {
let (start, end) = resolve_range(range, self.len);
let ptr = unsafe { (self.ptr as *mut u8).add(start * std::mem::size_of::<T>()) }
as sys::hipDeviceptr_t;
HipViewMut {
ptr,
len: end - start,
read: self.read,
write: self.write,
stream: self.stream,
marker: PhantomData,
}
}
}
fn resolve_range<R: RangeBounds<usize>>(range: R, total_len: usize) -> (usize, usize) {
let start = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n + 1,
Bound::Excluded(&n) => n,
Bound::Unbounded => total_len,
};
assert!(
start <= end && end <= total_len,
"slice range out of bounds"
);
(start, end)
}
impl<T> DeviceSlice<T> for HipSlice<T> {
fn len(&self) -> usize {
self.len
}
fn stream(&self) -> &Arc<HipStream> {
&self.stream
}
}
impl<T> DeviceSlice<T> for HipView<'_, T> {
fn len(&self) -> usize {
self.len
}
fn stream(&self) -> &Arc<HipStream> {
self.stream
}
}
impl<T> DeviceSlice<T> for HipViewMut<'_, T> {
fn len(&self) -> usize {
self.len
}
fn stream(&self) -> &Arc<HipStream> {
self.stream
}
}
impl<T> DevicePtr<T> for HipSlice<T> {
fn device_ptr<'a>(&'a self, stream: &'a HipStream) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>) {
if self.stream.ctx.is_managing_stream_synchronization() {
if let Some(write) = self.write.as_ref() {
stream.ctx.record_err(stream.wait(write));
}
}
(
self.hip_device_ptr,
SyncOnDrop::record_event(&self.read, stream),
)
}
}
impl<T> DevicePtr<T> for HipView<'_, T> {
fn device_ptr<'a>(&'a self, stream: &'a HipStream) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>) {
if self.stream.ctx.is_managing_stream_synchronization() {
if let Some(write) = self.write.as_ref() {
stream.ctx.record_err(stream.wait(write));
}
}
(self.ptr, SyncOnDrop::record_event(self.read, stream))
}
}
impl<T> DevicePtr<T> for HipViewMut<'_, T> {
fn device_ptr<'a>(&'a self, stream: &'a HipStream) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>) {
if self.stream.ctx.is_managing_stream_synchronization() {
if let Some(write) = self.write.as_ref() {
stream.ctx.record_err(stream.wait(write));
}
}
(self.ptr, SyncOnDrop::record_event(self.read, stream))
}
}
impl<T> DevicePtrMut<T> for HipSlice<T> {
fn device_ptr_mut<'a>(
&'a mut self,
stream: &'a HipStream,
) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>) {
if self.stream.ctx.is_managing_stream_synchronization() {
if let Some(read) = self.read.as_ref() {
stream.ctx.record_err(stream.wait(read));
}
if let Some(write) = self.write.as_ref() {
stream.ctx.record_err(stream.wait(write));
}
}
(
self.hip_device_ptr,
SyncOnDrop::record_event(&self.write, stream),
)
}
}
impl<T> DevicePtrMut<T> for HipViewMut<'_, T> {
fn device_ptr_mut<'a>(
&'a mut self,
stream: &'a HipStream,
) -> (sys::hipDeviceptr_t, SyncOnDrop<'a>) {
if self.stream.ctx.is_managing_stream_synchronization() {
if let Some(read) = self.read.as_ref() {
stream.ctx.record_err(stream.wait(read));
}
if let Some(write) = self.write.as_ref() {
stream.ctx.record_err(stream.wait(write));
}
}
(self.ptr, SyncOnDrop::record_event(self.write, stream))
}
}
impl HipStream {
pub unsafe fn alloc<T: DeviceRepr>(
self: &Arc<Self>,
len: usize,
) -> Result<HipSlice<T>, HipError> {
self.ctx.bind_to_thread()?;
let bytes = len
.checked_mul(std::mem::size_of::<T>())
.expect("size overflow");
let raw = if self.ctx.has_async_alloc {
result::malloc_async(bytes, self.hip_stream)?
} else {
result::malloc_sync(bytes)?
};
let (read, write) = if self.ctx.is_event_tracking() {
(
Some(self.ctx.new_event(None)?),
Some(self.ctx.new_event(None)?),
)
} else {
(None, None)
};
Ok(HipSlice {
hip_device_ptr: raw as sys::hipDeviceptr_t,
len,
read,
write,
stream: self.clone(),
marker: PhantomData,
})
}
pub fn alloc_zeros<T: DeviceRepr + ValidAsZeroBits>(
self: &Arc<Self>,
len: usize,
) -> Result<HipSlice<T>, HipError> {
let mut dst = unsafe { self.alloc::<T>(len) }?;
self.memset_zeros(&mut dst)?;
Ok(dst)
}
pub fn memset_zeros<T: DeviceRepr + ValidAsZeroBits, Dst: DevicePtrMut<T>>(
self: &Arc<Self>,
dst: &mut Dst,
) -> Result<(), HipError> {
self.ctx.bind_to_thread()?;
let bytes = dst.num_bytes();
let (dptr, _record) = dst.device_ptr_mut(self);
unsafe { result::memset_d8_async(dptr as u64, 0, bytes, self.hip_stream) }
}
pub fn clone_htod<T: DeviceRepr, Src: HostSlice<T> + ?Sized>(
self: &Arc<Self>,
src: &Src,
) -> Result<HipSlice<T>, HipError> {
let mut dst = unsafe { self.alloc::<T>(src.len()) }?;
self.memcpy_htod(src, &mut dst)?;
Ok(dst)
}
pub fn clone_dtoh<T: DeviceRepr + Clone + Default, Src: DevicePtr<T>>(
self: &Arc<Self>,
src: &Src,
) -> Result<Vec<T>, HipError> {
let mut dst: Vec<T> = vec![T::default(); src.len()];
self.memcpy_dtoh(src, &mut dst[..])?;
Ok(dst)
}
pub fn clone_dtod<T: DeviceRepr, Src: DevicePtr<T>>(
self: &Arc<Self>,
src: &Src,
) -> Result<HipSlice<T>, HipError> {
let mut dst = unsafe { self.alloc::<T>(src.len()) }?;
self.memcpy_dtod(src, &mut dst)?;
Ok(dst)
}
pub fn memcpy_htod<T: DeviceRepr, Src: HostSlice<T> + ?Sized, Dst: DevicePtrMut<T>>(
self: &Arc<Self>,
src: &Src,
dst: &mut Dst,
) -> Result<(), HipError> {
assert!(dst.len() >= src.len(), "memcpy_htod: dst smaller than src");
self.ctx.bind_to_thread()?;
let bytes = src.len() * std::mem::size_of::<T>();
let (src_slice, _record_src) = unsafe { src.stream_synced_slice(self) };
let (dptr, _record_dst) = dst.device_ptr_mut(self);
let src_bytes =
unsafe { std::slice::from_raw_parts(src_slice.as_ptr() as *const u8, bytes) };
unsafe { result::memcpy_htod_async(dptr as u64, src_bytes, self.hip_stream) }
}
pub fn memcpy_dtoh<T: DeviceRepr, Src: DevicePtr<T>, Dst: HostSlice<T> + ?Sized>(
self: &Arc<Self>,
src: &Src,
dst: &mut Dst,
) -> Result<(), HipError> {
assert!(dst.len() >= src.len(), "memcpy_dtoh: dst smaller than src");
self.ctx.bind_to_thread()?;
let bytes = src.len() * std::mem::size_of::<T>();
let (sptr, _record_src) = src.device_ptr(self);
let (dst_slice, _record_dst) = unsafe { dst.stream_synced_mut_slice(self) };
let dst_bytes =
unsafe { std::slice::from_raw_parts_mut(dst_slice.as_mut_ptr() as *mut u8, bytes) };
unsafe { result::memcpy_dtoh_async(dst_bytes, sptr as u64, self.hip_stream) }
}
pub fn memcpy_dtod<T: DeviceRepr, Src: DevicePtr<T>, Dst: DevicePtrMut<T>>(
self: &Arc<Self>,
src: &Src,
dst: &mut Dst,
) -> Result<(), HipError> {
assert!(dst.len() >= src.len(), "memcpy_dtod: dst smaller than src");
self.ctx.bind_to_thread()?;
let bytes = src.len() * std::mem::size_of::<T>();
let src_ctx = src.stream().context();
let dst_ctx = self.context();
if src_ctx == dst_ctx {
let (sptr, _record_src) = src.device_ptr(self);
let (dptr, _record_dst) = dst.device_ptr_mut(self);
unsafe { result::memcpy_dtod_async(dptr as u64, sptr as u64, bytes, self.hip_stream) }
} else {
let (sptr, _record_src) = src.device_ptr(src.stream());
let (dptr, _record_dsdt) = dst.device_ptr_mut(self);
self.wait(&src.stream().record_event(None)?)?;
unsafe {
result::memcpy_peer_async(
dptr as u64,
dst_ctx.ordinal() as c_int,
sptr as u64,
src_ctx.ordinal() as c_int,
bytes,
self.hip_stream,
)
}
}
}
}
pub struct PinnedHostSlice<T> {
pub(crate) ptr: *mut T,
pub(crate) len: usize,
pub(crate) event: HipEvent,
}
impl<T> std::fmt::Debug for PinnedHostSlice<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PinnedHostSlice")
.field("ptr", &self.ptr)
.field("len", &self.len)
.finish()
}
}
unsafe impl<T: Send> Send for PinnedHostSlice<T> {}
unsafe impl<T: Sync> Sync for PinnedHostSlice<T> {}
impl<T> Drop for PinnedHostSlice<T> {
fn drop(&mut self) {
let ctx = self.event.ctx.clone();
ctx.record_err(self.event.synchronize());
ctx.record_err(result::free_host(self.ptr as *mut _));
}
}
impl HipContext {
pub unsafe fn alloc_pinned<T: DeviceRepr>(
self: &Arc<Self>,
len: usize,
) -> Result<PinnedHostSlice<T>, HipError> {
self.bind_to_thread()?;
let bytes = len
.checked_mul(std::mem::size_of::<T>())
.expect("size overflow");
let ptr = result::malloc_host(bytes, HIP_HOST_MALLOC_WRITE_COMBINED)? as *mut T;
assert!(!ptr.is_null());
let event = self.new_event(Some(HIP_EVENT_BLOCKING_SYNC))?;
Ok(PinnedHostSlice { ptr, len, event })
}
}
impl<T> PinnedHostSlice<T> {
pub fn context(&self) -> &Arc<HipContext> {
&self.event.ctx
}
pub fn len(&self) -> usize {
self.len
}
pub fn num_bytes(&self) -> usize {
self.len * std::mem::size_of::<T>()
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<T: ValidAsZeroBits> PinnedHostSlice<T> {
pub fn as_ptr(&self) -> Result<*const T, HipError> {
self.event.synchronize()?;
Ok(self.ptr)
}
pub fn as_mut_ptr(&mut self) -> Result<*mut T, HipError> {
self.event.synchronize()?;
Ok(self.ptr)
}
pub fn as_slice(&self) -> Result<&[T], HipError> {
self.event.synchronize()?;
Ok(unsafe { std::slice::from_raw_parts(self.ptr, self.len) })
}
pub fn as_mut_slice(&mut self) -> Result<&mut [T], HipError> {
self.event.synchronize()?;
Ok(unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) })
}
}
impl<T> HostSlice<T> for PinnedHostSlice<T> {
fn len(&self) -> usize {
self.len
}
unsafe fn stream_synced_slice<'a>(
&'a self,
stream: &'a HipStream,
) -> (&'a [T], SyncOnDrop<'a>) {
stream.ctx.record_err(stream.wait(&self.event));
(
unsafe { std::slice::from_raw_parts(self.ptr, self.len) },
SyncOnDrop::Record(Some((&self.event, stream))),
)
}
unsafe fn stream_synced_mut_slice<'a>(
&'a mut self,
stream: &'a HipStream,
) -> (&'a mut [T], SyncOnDrop<'a>) {
stream.ctx.record_err(stream.wait(&self.event));
(
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) },
SyncOnDrop::Record(Some((&self.event, stream))),
)
}
}
#[derive(Debug)]
pub struct HipModule {
pub(crate) hip_module: sys::hipModule_t,
pub(crate) ctx: Arc<HipContext>,
}
unsafe impl Send for HipModule {}
unsafe impl Sync for HipModule {}
impl Drop for HipModule {
fn drop(&mut self) {
self.ctx.record_err(self.ctx.bind_to_thread());
self.ctx
.record_err(unsafe { result::module::unload(self.hip_module) });
}
}
impl HipContext {
pub fn load_module(
self: &Arc<Self>,
hsaco: crate::hiprtc::Hsaco,
) -> Result<Arc<HipModule>, HipError> {
self.bind_to_thread()?;
let bytes = hsaco
.as_bytes()
.map_err(|_| HipError(sys::hipError_t::hipErrorFileNotFound))?;
let hip_module = unsafe { result::module::load_data(bytes.as_ref()) }?;
Ok(Arc::new(HipModule {
hip_module,
ctx: self.clone(),
}))
}
}
impl HipModule {
pub fn load_function(self: &Arc<Self>, name: &str) -> Result<HipFunction, HipError> {
let cname =
CString::new(name).map_err(|_| HipError(sys::hipError_t::hipErrorInvalidValue))?;
let hip_function = result::module::get_function(self.hip_module, cname.as_c_str())?;
Ok(HipFunction {
hip_function,
module: self.clone(),
})
}
pub fn get_global<'a>(
self: &'a Arc<Self>,
name: &str,
stream: &'a Arc<HipStream>,
) -> Result<HipViewMut<'a, u8>, HipError> {
let cname =
CString::new(name).map_err(|_| HipError(sys::hipError_t::hipErrorInvalidValue))?;
let (raw_ptr, bytes) = result::module::get_global(self.hip_module, cname.as_c_str())?;
Ok(HipViewMut {
ptr: raw_ptr as sys::hipDeviceptr_t,
len: bytes,
read: &NO_EVENT,
write: &NO_EVENT,
stream,
marker: PhantomData,
})
}
}
static NO_EVENT: Option<HipEvent> = None;
#[derive(Debug, Clone)]
pub struct HipFunction {
pub(crate) hip_function: sys::hipFunction_t,
#[allow(unused)]
pub(crate) module: Arc<HipModule>,
}
unsafe impl Send for HipFunction {}
unsafe impl Sync for HipFunction {}
impl HipFunction {
pub fn hip_function(&self) -> sys::hipFunction_t {
self.hip_function
}
pub fn get_attribute(&self, attribute: sys::hipFunction_attribute) -> Result<i32, HipError> {
result::function::get_function_attribute(self.hip_function, attribute)
}
pub fn num_regs(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_NUM_REGS)
}
pub fn shared_size_bytes(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES)
}
pub fn const_size_bytes(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_CONST_SIZE_BYTES)
}
pub fn local_size_bytes(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES)
}
pub fn max_threads_per_block(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK)
}
pub fn ptx_version(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_PTX_VERSION)
}
pub fn binary_version(&self) -> Result<i32, HipError> {
self.get_attribute(sys::hipFunction_attribute::HIP_FUNC_ATTRIBUTE_BINARY_VERSION)
}
pub fn occupancy_max_active_blocks_per_multiprocessor(
&self,
block_size: i32,
dynamic_smem_bytes: usize,
flags: Option<c_uint>,
) -> Result<i32, HipError> {
match flags {
None => result::occupancy::max_active_blocks_per_multiprocessor(
self.hip_function,
block_size,
dynamic_smem_bytes,
),
Some(f) => result::occupancy::max_active_blocks_per_multiprocessor_with_flags(
self.hip_function,
block_size,
dynamic_smem_bytes,
f,
),
}
}
pub fn occupancy_max_potential_block_size(
&self,
dynamic_smem_bytes: usize,
block_size_limit: i32,
flags: Option<c_uint>,
) -> Result<(i32, i32), HipError> {
match flags {
None => result::occupancy::max_potential_block_size(
self.hip_function,
dynamic_smem_bytes,
block_size_limit,
),
Some(f) => result::occupancy::max_potential_block_size_with_flags(
self.hip_function,
dynamic_smem_bytes,
block_size_limit,
f,
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transmutes() {
let ctx = HipContext::new(0).unwrap();
let stream = ctx.default_stream();
let buf: HipSlice<u32> = stream.clone_htod(&[0xDEADBEEF_u32, 0xCAFEBABE]).unwrap();
let as_u8: HipSlice<u8> = buf.transmute().unwrap();
assert_eq!(as_u8.len(), 8);
let host: Vec<u8> = stream.clone_dtoh(&as_u8).unwrap();
assert_eq!(host.len(), 8);
}
#[test]
fn test_threading() {
let ctx = HipContext::new(0).unwrap();
let threads: Vec<_> = (0..2)
.map(|_| {
let ctx = ctx.clone();
std::thread::spawn(move || {
ctx.bind_to_thread().unwrap();
let stream = ctx.default_stream();
let _: HipSlice<f32> = stream.alloc_zeros(64).unwrap();
})
})
.collect();
for t in threads {
t.join().unwrap();
}
}
#[test]
fn test_post_build_arc_count() {
let ctx = HipContext::new(0).unwrap();
assert_eq!(Arc::strong_count(&ctx), 1);
}
#[test]
fn test_post_alloc_arc_counts() {
let ctx = HipContext::new(0).unwrap();
assert_eq!(Arc::strong_count(&ctx), 1);
let stream = ctx.default_stream();
assert_eq!(Arc::strong_count(&ctx), 2);
let t: HipSlice<f32> = stream.alloc_zeros(64).unwrap();
assert_eq!(Arc::strong_count(&ctx), 4);
assert_eq!(Arc::strong_count(&stream), 2);
drop(t);
assert_eq!(Arc::strong_count(&ctx), 2);
assert_eq!(Arc::strong_count(&stream), 1);
drop(stream);
assert_eq!(Arc::strong_count(&ctx), 1);
}
#[test]
#[ignore = "must be run in isolation"]
fn test_post_alloc_memory() {
let ctx = HipContext::new(0).unwrap();
let stream = ctx.default_stream();
let (free0, _total) = ctx.mem_get_info().unwrap();
let buf: HipSlice<f32> = stream.alloc_zeros(10_000_000).unwrap();
let (free1, _) = ctx.mem_get_info().unwrap();
assert!(free0 > free1, "free memory did not decrease after alloc");
drop(buf);
ctx.synchronize().unwrap();
let (free2, _) = ctx.mem_get_info().unwrap();
assert!(free2 > free1, "free memory did not recover after drop");
assert_eq!(
free2, free0,
"freed memory did not match pre-allocated memory"
)
}
#[test]
fn test_ctx_copy_to_views() {
let ctx = HipContext::new(0).unwrap();
let stream = ctx.default_stream();
let mut big: HipSlice<f32> = stream.alloc_zeros(50).unwrap();
for i in 0..5 {
let chunk: Vec<f32> = (0..10).map(|j| (i * 10 + j) as f32).collect();
let src = stream.clone_htod(&chunk).unwrap();
let mut view = big.slice_mut(i * 10..(i + 1) * 10);
stream.memcpy_dtod(&src, &mut view).unwrap();
}
let out: Vec<f32> = stream.clone_dtoh(&big).unwrap();
for (i, &v) in out.iter().enumerate() {
assert_eq!(v, i as f32);
}
}
#[test]
fn test_leak_and_upgrade() {
let ctx = HipContext::new(0).unwrap();
let stream = ctx.default_stream();
let buf: HipSlice<f32> = stream.clone_htod(&[1.0_f32, 2.0, 3.0, 4.0]).unwrap();
let (raw, len) = buf.leak();
assert_eq!(len, 4);
let rebuilt: HipSlice<f32> = unsafe { stream.upgrade_device_ptr(raw, len) }.unwrap();
let out: Vec<f32> = stream.clone_dtoh(&rebuilt).unwrap();
assert_eq!(out, vec![1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn test_slice_is_freed_with_correct_context() {
let ctx_a = HipContext::new(0).unwrap();
let stream_a = ctx_a.default_stream();
let buf: HipSlice<f32> = stream_a.alloc_zeros(1024).unwrap();
let ctx_b = HipContext::new(0).unwrap();
ctx_b.bind_to_thread().unwrap();
drop(buf);
ctx_a.check_err().unwrap();
}
#[test]
fn test_copy_uses_correct_context() {
let ctx_a = HipContext::new(0).unwrap();
let stream_a = ctx_a.default_stream();
let buf: HipSlice<f32> = stream_a.clone_htod(&[1.0_f32; 32]).unwrap();
let ctx_b = HipContext::new(0).unwrap();
ctx_b.bind_to_thread().unwrap();
let out: Vec<f32> = stream_a.clone_dtoh(&buf).unwrap();
assert!(out.iter().all(|&v| v == 1.0));
}
#[test]
fn test_htod_copy_pinned() {
let ctx = HipContext::new(0).unwrap();
let stream = ctx.default_stream();
let mut pinned: PinnedHostSlice<f32> = unsafe { ctx.alloc_pinned(64).unwrap() };
for (i, v) in pinned.as_mut_slice().unwrap().iter_mut().enumerate() {
*v = i as f32;
}
let dev: HipSlice<f32> = stream.clone_htod(&pinned).unwrap();
let back: Vec<f32> = stream.clone_dtoh(&dev).unwrap();
for (i, &v) in back.iter().enumerate() {
assert_eq!(v, i as f32);
}
}
#[test]
fn test_pinned_copy_is_faster() {
use std::time::Instant;
let ctx = HipContext::new(0).unwrap();
let stream = ctx.new_stream().unwrap();
let n = 100_000;
let n_samples = 5;
let not_pinned = vec![0.0f32; n];
let start = Instant::now();
for _ in 0..n_samples {
let _ = stream.clone_htod(¬_pinned).unwrap();
stream.synchronize().unwrap();
}
let unpinned_elapsed = start.elapsed() / n_samples;
let pinned = unsafe { ctx.alloc_pinned::<f32>(n) }.unwrap();
let start = Instant::now();
for _ in 0..n_samples {
let _ = stream.clone_htod(&pinned).unwrap();
stream.synchronize().unwrap();
}
let pinned_elapsed = start.elapsed() / n_samples;
assert!(
pinned_elapsed.as_secs_f32() * 1.5 < unpinned_elapsed.as_secs_f32(),
"{unpinned_elapsed:?} vs {pinned_elapsed:?}",
);
}
#[test]
fn test_context_htod_dtoh() {
let ctx = HipContext::new(0).unwrap();
let stream = ctx.default_stream();
let dev: HipSlice<f32> = stream.clone_htod(&[1.0_f32, 2.0, 3.0]).unwrap();
let host: Vec<f32> = stream.clone_dtoh(&dev).unwrap();
assert_eq!(host, vec![1.0, 2.0, 3.0]);
}
#[test]
fn test_context_cross_thread_bind() {
let ctx = HipContext::new(0).unwrap();
let ctx_for_thread = ctx.clone();
std::thread::spawn(move || {
ctx_for_thread.bind_to_thread().unwrap();
let stream = ctx_for_thread.default_stream();
let _: HipSlice<f32> = stream.alloc_zeros(32).unwrap();
})
.join()
.unwrap();
}
}