use std::{marker::PhantomData, sync::{Arc, Weak}};
pub mod result;
pub mod sys;
#[derive(Debug, thiserror::Error)]
pub enum DriverError {
#[error("HIP error: {0:?}")]
Hip(sys::hipError_t),
#[error("kernel name contained a null byte")]
InvalidName,
}
#[derive(Debug, Clone, Copy)]
pub enum DeviceFlags {
Auto,
Spin,
Yield,
BlockingSync
}
impl DeviceFlags {
fn to_raw(self) -> core::ffi::c_uint {
match self {
Self::Auto => 0x0,
Self::Spin => 0x1,
Self::Yield => 0x2,
Self::BlockingSync => 0x4,
}
}
}
pub struct HipContext {
ordinal: i32,
default_stream: Arc<HipStream>,
gfx_arch: String,
flags: DeviceFlags,
}
impl HipContext {
pub fn new(ordinal: i32) -> Result<Arc<Self>, DriverError> {
Self::new_with_flags(ordinal, DeviceFlags::BlockingSync)
}
pub fn new_with_flags(ordinal: i32, flags: DeviceFlags) -> Result<Arc<Self>, DriverError> {
result::set_device(ordinal)?;
result::set_device_flags(flags.to_raw())?; let gfx_arch = result::device_gcn_arch_name(ordinal)?;
let raw_stream = result::stream_create()?;
let ctx = Arc::new_cyclic(|weak_ctx: &Weak<HipContext>| {
let stream = Arc::new(HipStream {
raw: raw_stream,
device_ordinal: ordinal,
ctx: weak_ctx.clone(),
});
HipContext { ordinal, default_stream: stream, gfx_arch, flags }
});
Ok(ctx)
}
pub fn compute_capability(&self) -> Result<(i32, i32), DriverError> {
let arch = self.gfx_arch.strip_prefix("gfx").unwrap_or(&self.gfx_arch);
let bytes = arch.as_bytes();
let major_len = if bytes.len() >= 2 && bytes[0].is_ascii_digit() && bytes[1].is_ascii_digit() {
2
} else if !bytes.is_empty() && bytes[0].is_ascii_digit() {
1
} else {
0
};
let (major_str, minor_str) = arch.split_at(major_len);
let major: i32 = major_str.parse().unwrap_or(0);
let minor: i32 = minor_str
.parse()
.or_else(|_| i32::from_str_radix(minor_str, 16))
.unwrap_or(0);
Ok((major, minor))
}
pub fn ordinal(&self) -> i32 {
self.ordinal
}
pub fn default_stream(&self) -> Arc<HipStream> {
self.default_stream.clone()
}
pub fn gfx_arch(&self) -> &str {
&self.gfx_arch
}
pub fn name(&self) -> Result<String, DriverError> {
result::device_name(self.ordinal)
}
pub fn alloc<T>(self: &Arc<Self>, len: usize) -> Result<HipSlice<T>, DriverError> {
HipSlice::alloc(self.default_stream.clone(), len)
}
pub fn bind_to_thread(&self) -> Result<(), DriverError> {
result::set_device(self.ordinal)?;
let _ = result::set_device_flags(self.flags.to_raw());
Ok(())
}
pub fn load_module(&self, hsaco: crate::hiprtc::Hsaco) -> Result<Arc<HipModule>, DriverError> {
HipModule::from_hsaco(hsaco.as_bytes())
}
}
pub struct HipStream {
raw: sys::hipStream_t,
#[allow(dead_code)]
device_ordinal: i32,
ctx: Weak<HipContext>,
}
impl HipStream {
pub fn synchronize(&self) -> Result<(), DriverError> {
result::stream_synchronize(self.raw)
}
pub fn hip_stream(&self) -> sys::hipStream_t {
self.raw
}
pub fn context(&self) -> Arc<HipContext> {
self.ctx.upgrade().expect("HipContext dropped before HipStream")
}
pub fn alloc<T>(self: &Arc<Self>, len: usize) -> Result<HipSlice<T>, DriverError> {
HipSlice::alloc(self.clone(), len)
}
pub fn alloc_zeros<T>(self: &Arc<Self>, len: usize) -> Result<HipSlice<T>, DriverError> {
let slice = self.alloc::<T>(len)?;
if !slice.is_empty() {
let bytes = slice.len * std::mem::size_of::<T>();
unsafe { result::memset_d8_async(slice.device_ptr(self).0, 0, bytes, self.raw)?; }
}
Ok(slice)
}
pub fn clone_dtoh<T: Copy>(
self: &Arc<Self>,
src: &HipSlice<T>,
) -> Result<Vec<T>, DriverError> {
src.clone_dtoh(self)
}
pub fn memcpy_htod<T: Copy>(
&self,
src: &[T],
dst: &mut HipSlice<T>,
) -> Result<(), DriverError> {
if src.is_empty() { return Ok(()); }
assert!(src.len() <= dst.len, "memcpy_htod: src.len > dst.len");
let bytes = std::mem::size_of_val(src);
let src_bytes = unsafe {
std::slice::from_raw_parts(src.as_ptr() as *const u8, bytes)
};
unsafe { result::memcpy_htod_async(dst.ptr, src_bytes, self.raw) }
}
pub fn memcpy_dtoh<T: Copy>(
&self,
src: &HipSlice<T>,
dst: &mut [T],
) -> Result<(), DriverError> {
if dst.is_empty() { return Ok(()); }
assert!(dst.len() <= src.len, "memcpy_dtoh: dst.len > src.len");
let bytes = std::mem::size_of_val(dst);
let dst_bytes = unsafe {
std::slice::from_raw_parts_mut(dst.as_mut_ptr() as *mut u8, bytes)
};
unsafe { result::memcpy_dtoh_async(dst_bytes, src.ptr, self.raw) }
}
pub fn clone_htod<T: Copy>(
self: &Arc<Self>,
src: &[T],
) -> Result<HipSlice<T>, DriverError> {
let slice = self.alloc::<T>(src.len())?;
if !src.is_empty() {
let bytes = std::mem::size_of_val(src);
let src_bytes = unsafe {
std::slice::from_raw_parts(src.as_ptr() as *const u8, bytes)
};
unsafe { result::memcpy_htod_async(slice.ptr, src_bytes, self.raw)?; }
self.synchronize()?;
}
Ok(slice)
}
pub unsafe fn upgrade_device_ptr<T>(
self: &Arc<Self>,
ptr: u64,
len: usize,
) -> HipSlice<T> {
HipSlice {
ptr,
len,
stream: self.clone(),
owned: false,
_marker: PhantomData,
}
}
}
impl Drop for HipStream {
fn drop(&mut self) {
let _ = result::stream_destroy(self.raw);
}
}
unsafe impl Send for HipStream {}
unsafe impl Sync for HipStream {}
impl std::fmt::Debug for HipStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HipStream")
.field("raw", &self.raw)
.field("device_ordinal", &self.device_ordinal)
.finish()
}
}
impl std::fmt::Debug for HipContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HipContext")
.field("ordinal", &self.ordinal)
.field("gfx_arch", &self.gfx_arch)
.field("flags", &self.flags)
.finish()
}
}
pub struct HipSlice<T> {
pub(crate) ptr: u64,
pub(crate) len: usize,
#[allow(dead_code)]
pub(crate) stream: Arc<HipStream>,
pub(crate) owned: bool,
pub(crate) _marker: PhantomData<*const T>,
}
impl<T> HipSlice<T> {
fn alloc(stream: Arc<HipStream>, len: usize) -> Result<Self, DriverError> {
let bytes = len.checked_mul(std::mem::size_of::<T>()).expect("size overflow");
let ptr = if bytes == 0 { 0 } else { result::malloc(bytes)? };
Ok(Self {
ptr,
len,
stream,
owned: true,
_marker: PhantomData,
})
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn device_ptr(&self, _stream: &Arc<HipStream>) -> (u64, ()) {
(self.ptr, ())
}
}
impl<T: Copy> HipSlice<T> {
pub fn clone_dtoh(&self, stream: &Arc<HipStream>) -> Result<Vec<T>, DriverError> {
let mut out: Vec<T> = Vec::with_capacity(self.len);
if self.len > 0 {
let bytes = self.len * std::mem::size_of::<T>();
let out_bytes = unsafe {
std::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, bytes)
};
unsafe { result::memcpy_dtoh_async(out_bytes, self.ptr, stream.raw)?; }
stream.synchronize()?;
unsafe { out.set_len(self.len); }
}
Ok(out)
}
}
impl<T> Drop for HipSlice<T> {
fn drop(&mut self) {
if self.owned && self.ptr != 0 {
let _ = result::free(self.ptr);
}
}
}
unsafe impl<T: Send> Send for HipSlice<T> {}
unsafe impl<T: Sync> Sync for HipSlice<T> {}
impl<T> std::fmt::Debug for HipSlice<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HipSlice")
.field("ptr", &self.ptr)
.field("len", &self.len)
.field("owned", &self.owned)
.finish()
}
}
pub trait DevicePtr {
fn device_ptr(&self) -> u64;
}
impl<T> DevicePtr for HipSlice<T> {
fn device_ptr(&self) -> u64 {
self.ptr
}
}
pub struct HipModule {
raw: sys::hipModule_t,
}
impl HipModule {
pub fn from_hsaco(bytes: &[u8]) -> Result<Arc<Self>, DriverError> {
let raw = result::module_load_data(bytes)?;
Ok(Arc::new(Self { raw }))
}
pub fn get_function(self: &Arc<Self>, name: &str) -> Result<HipFunction, DriverError> {
let raw = result::module_get_function(self.raw, name)?;
Ok(HipFunction {
raw,
_module: self.clone(),
})
}
pub fn load_function(self: &Arc<Self>, name: &str) -> Result<HipFunction, DriverError> {
self.get_function(name)
}
}
impl Drop for HipModule {
fn drop(&mut self) {
let _ = result::module_unload(self.raw);
}
}
unsafe impl Send for HipModule {}
unsafe impl Sync for HipModule {}
#[derive(Clone)]
pub struct HipFunction {
raw: sys::hipFunction_t,
_module: Arc<HipModule>,
}
impl HipFunction {
pub fn raw(&self) -> sys::hipFunction_t {
self.raw
}
pub unsafe fn launch(
&self,
grid: (u32, u32, u32),
block: (u32, u32, u32),
shared_mem_bytes: u32,
stream: &HipStream,
params: &mut [*mut std::ffi::c_void],
) -> Result<(), DriverError> {
unsafe {
result::module_launch_kernel(
self.raw,
grid,
block,
shared_mem_bytes,
stream.hip_stream(),
params,
)
}
}
}
unsafe impl Send for HipFunction {}
unsafe impl Sync for HipFunction {}
impl std::fmt::Debug for HipFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HipFunction").field("raw", &self.raw).finish()
}
}