use std::{fmt, marker::PhantomData, ptr};
use cugraph_sys::{
cugraph_error_t, cugraph_type_erased_device_array_create, cugraph_type_erased_device_array_t,
cugraph_type_erased_device_array_view, cugraph_type_erased_device_array_view_copy_from_host,
cugraph_type_erased_device_array_view_copy_to_host, cugraph_type_erased_device_array_view_create,
cugraph_type_erased_device_array_view_free, cugraph_type_erased_device_array_view_pointer,
cugraph_type_erased_device_array_view_size, cugraph_type_erased_device_array_view_t,
cugraph_type_erased_device_array_view_type, cugraph_type_erased_host_array_view_create,
cugraph_type_erased_host_array_view_free, cugraph_type_erased_host_array_view_t,
};
use crate::{
cuda::{DeviceResourceKind, drop_device_resource},
error::{Error, ErrorKind, Result, check_error, require_non_null},
handle::{EnteredContext, ResourceHandleLease},
types::{CugraphElement, DataType},
};
pub(crate) struct RawDeviceArray {
raw: *mut cugraph_type_erased_device_array_t,
raw_view: *mut cugraph_type_erased_device_array_view_t,
device: i32,
_resource_handle_lease: Option<ResourceHandleLease>,
_nosend_nosync: PhantomData<*mut ()>,
}
fn checked_byte_len(len: usize, dtype: DataType, operation: &'static str) -> Result<usize> {
len.checked_mul(dtype.size_of()).ok_or_else(|| {
Error::new(ErrorKind::InvalidInput, operation)
.with_message("array byte size overflow")
.with_context("len", len.to_string())
.with_context("dtype", format!("{:?}", dtype))
.with_context("element_size", dtype.size_of().to_string())
})
}
impl RawDeviceArray {
pub(crate) unsafe fn from_raw(
raw: *mut cugraph_type_erased_device_array_t,
device: i32,
operation: &'static str,
pointer_name: &'static str,
resource_handle_lease: Option<ResourceHandleLease>,
) -> Result<Self> {
let raw = require_non_null(raw, operation, pointer_name)?;
let raw_view = unsafe { cugraph_type_erased_device_array_view(raw) };
let raw_view = match require_non_null(raw_view, operation, "device_array_view") {
Ok(raw_view) => raw_view,
Err(error) => {
let mut raw = raw;
drop_device_resource(&mut raw, device, operation, DeviceResourceKind::DeviceArray);
return Err(error);
},
};
Ok(Self { raw, raw_view, device, _resource_handle_lease: resource_handle_lease, _nosend_nosync: PhantomData })
}
pub(crate) fn new(handle: &EnteredContext<'_>, len: usize, dtype: DataType) -> Result<Self> {
let mut raw: *mut cugraph_type_erased_device_array_t = ptr::null_mut();
let mut error: *mut cugraph_error_t = ptr::null_mut();
unsafe {
let code =
cugraph_type_erased_device_array_create(handle.handle_ptr(), len, dtype.into(), &mut raw, &mut error);
check_error(code, error, handle.device(), "RawDeviceArray::new")?;
}
let raw = require_non_null(raw, "RawDeviceArray::new", "device_array")?;
let raw_view = unsafe { cugraph_type_erased_device_array_view(raw) };
let raw_view = match require_non_null(raw_view, "RawDeviceArray::new", "device_array_view") {
Ok(raw_view) => raw_view,
Err(error) => {
let mut raw = raw;
drop_device_resource(&mut raw, handle.device(), "RawDeviceArray::new", DeviceResourceKind::DeviceArray);
return Err(error);
},
};
Ok(Self {
raw,
raw_view,
device: handle.device(),
_resource_handle_lease: handle.runtime_bound_lease(),
_nosend_nosync: PhantomData,
})
}
pub(crate) fn raw_view(&self) -> RawDeviceArrayView<'_> {
RawDeviceArrayView {
raw: self.raw_view,
owned: false,
device: Some(self.device),
_lifetime: PhantomData,
_nosend_nosync: PhantomData,
}
}
fn copy_from_host(&mut self, handle: &EnteredContext<'_>, data: &[u8]) -> Result<()> {
let view = self.raw_view();
let mut error: *mut cugraph_error_t = ptr::null_mut();
unsafe {
let code = cugraph_type_erased_device_array_view_copy_from_host(
handle.handle_ptr(),
view.raw,
data.as_ptr() as *const i8,
&mut error,
);
check_error(code, error, handle.device(), "RawDeviceArray::copy_from_host")?;
}
Ok(())
}
}
impl Drop for RawDeviceArray {
fn drop(&mut self) {
unsafe { cugraph_type_erased_device_array_view_free(self.raw_view) };
drop_device_resource(&mut self.raw, self.device, "RawDeviceArray::drop.array", DeviceResourceKind::DeviceArray);
}
}
pub(crate) struct RawDeviceArrayView<'a> {
pub(crate) raw: *mut cugraph_type_erased_device_array_view_t,
owned: bool,
device: Option<i32>,
_lifetime: PhantomData<&'a ()>,
_nosend_nosync: PhantomData<*mut ()>,
}
impl fmt::Debug for RawDeviceArrayView<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawDeviceArrayView")
.field("len", &self.len())
.field("dtype", &self.dtype().ok())
.finish_non_exhaustive()
}
}
impl RawDeviceArrayView<'_> {
#[cfg(test)]
pub(crate) unsafe fn from_borrowed(
raw: *mut cugraph_type_erased_device_array_view_t,
device: Option<i32>,
operation: &'static str,
pointer_name: &'static str,
) -> Result<Self> {
let raw = require_non_null(raw, operation, pointer_name)?;
Ok(unsafe { Self::from_borrowed_unchecked(raw, device) })
}
pub(crate) unsafe fn from_borrowed_unchecked(
raw: *mut cugraph_type_erased_device_array_view_t,
device: Option<i32>,
) -> Self {
RawDeviceArrayView { raw, owned: false, device, _lifetime: PhantomData, _nosend_nosync: PhantomData }
}
pub(crate) unsafe fn from_owned_view(
raw: *mut cugraph_type_erased_device_array_view_t,
device: Option<i32>,
operation: &'static str,
pointer_name: &'static str,
) -> Result<Self> {
let raw = require_non_null(raw, operation, pointer_name)?;
Ok(RawDeviceArrayView { raw, owned: true, device, _lifetime: PhantomData, _nosend_nosync: PhantomData })
}
pub(crate) unsafe fn try_from_raw(
pointer: *mut std::ffi::c_void,
len: usize,
dtype: DataType,
operation: &'static str,
) -> Result<Self> {
checked_byte_len(len, dtype, operation)?;
let raw = unsafe { cugraph_type_erased_device_array_view_create(pointer, len, dtype.into()) };
if raw.is_null() {
return Err(Error::new(ErrorKind::OutOfMemory, operation)
.with_message("failed to allocate device array view metadata")
.set_temporary());
}
Ok(RawDeviceArrayView { raw, owned: true, device: None, _lifetime: PhantomData, _nosend_nosync: PhantomData })
}
pub(crate) fn device_ptr(&self) -> *mut std::ffi::c_void {
unsafe { cugraph_type_erased_device_array_view_pointer(self.raw) as *mut std::ffi::c_void }
}
pub(crate) fn len(&self) -> usize {
unsafe { cugraph_type_erased_device_array_view_size(self.raw) }
}
pub(crate) fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn dtype(&self) -> Result<DataType> {
let c_type = unsafe { cugraph_type_erased_device_array_view_type(self.raw) };
DataType::try_from(c_type).map_err(|err| err.with_operation("RawDeviceArrayView::dtype"))
}
pub(crate) fn as_ptr(&self) -> *mut cugraph_type_erased_device_array_view_t {
self.raw
}
pub(crate) fn device(&self) -> Option<i32> {
self.device
}
pub(crate) fn raw_view(&self) -> RawDeviceArrayView<'_> {
unsafe { RawDeviceArrayView::from_borrowed_unchecked(self.raw, self.device) }
}
pub(crate) fn to_host_vec(&self, handle: &EnteredContext<'_>) -> Result<Vec<u8>> {
let dtype = self.dtype()?;
let byte_len = checked_byte_len(self.len(), dtype, "RawDeviceArrayView::to_host_vec")?;
let mut buf = vec![0u8; byte_len];
let mut error: *mut cugraph_error_t = ptr::null_mut();
unsafe {
let code = cugraph_type_erased_device_array_view_copy_to_host(
handle.handle_ptr(),
buf.as_mut_ptr() as *mut i8,
self.raw,
&mut error,
);
check_error(code, error, handle.device(), "RawDeviceArrayView::to_host_vec")?;
}
Ok(buf)
}
}
impl Drop for RawDeviceArrayView<'_> {
fn drop(&mut self) {
if self.owned {
unsafe { cugraph_type_erased_device_array_view_free(self.raw) };
}
}
}
pub struct DeviceArray<T: CugraphElement> {
inner: RawDeviceArray,
_element: PhantomData<T>,
}
impl<T: CugraphElement> fmt::Debug for DeviceArray<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("DeviceArray");
d.field("dtype", &T::data_type());
d.field("len", &self.inner.raw_view().len());
d.finish_non_exhaustive()
}
}
impl<T: CugraphElement> DeviceArray<T> {
pub(crate) unsafe fn from_raw(
raw: *mut cugraph_type_erased_device_array_t,
device: i32,
operation: &'static str,
pointer_name: &'static str,
resource_handle_lease: Option<ResourceHandleLease>,
) -> Result<Self> {
let inner = unsafe { RawDeviceArray::from_raw(raw, device, operation, pointer_name, resource_handle_lease) }?;
let dtype = inner.raw_view().dtype()?;
if dtype != T::data_type() {
return Err(Error::new(ErrorKind::InvalidState, operation).with_message(format!(
"{pointer_name} dtype {dtype} does not match requested dtype {}",
T::data_type()
)));
}
Ok(Self { inner, _element: PhantomData })
}
pub fn new(handle: &EnteredContext<'_>, len: usize) -> Result<Self> {
let inner = RawDeviceArray::new(handle, len, T::data_type())?;
Ok(Self { inner, _element: PhantomData })
}
pub fn from_slice(handle: &EnteredContext<'_>, data: &[T]) -> Result<Self> {
let mut arr = Self::new(handle, data.len())?;
let byte_len = std::mem::size_of_val(data);
let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) };
arr.inner.copy_from_host(handle, bytes)?;
Ok(arr)
}
pub fn view(&self) -> Result<DeviceArrayView<'_, T>> {
Ok(DeviceArrayView { inner: self.inner.raw_view(), _element: PhantomData })
}
}
pub struct DeviceArrayView<'a, T: CugraphElement> {
inner: RawDeviceArrayView<'a>,
_element: PhantomData<T>,
}
impl<T: CugraphElement> fmt::Debug for DeviceArrayView<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DeviceArrayView")
.field("len", &self.len())
.field("dtype", &T::data_type())
.finish_non_exhaustive()
}
}
impl<'a, T: CugraphElement> DeviceArrayView<'a, T> {
#[cfg(test)] pub(crate) unsafe fn from_raw(pointer: *mut std::ffi::c_void, len: usize) -> Result<Self> {
let inner =
unsafe { RawDeviceArrayView::try_from_raw(pointer, len, T::data_type(), "DeviceArrayView::from_raw") }?;
Ok(DeviceArrayView { inner, _element: PhantomData })
}
pub(crate) fn from_raw_unchecked(raw: RawDeviceArrayView<'a>) -> Self {
DeviceArrayView { inner: raw, _element: PhantomData }
}
pub fn device_ptr(&self) -> *mut std::ffi::c_void {
self.inner.device_ptr()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn as_ptr(&self) -> *mut cugraph_type_erased_device_array_view_t {
self.inner.as_ptr()
}
pub(crate) fn raw_view(&self) -> RawDeviceArrayView<'_> {
self.inner.raw_view()
}
pub fn to_vec(&self, handle: &EnteredContext<'_>) -> Result<Vec<T>> {
let type_size = std::mem::size_of::<T>();
let bytes = self.inner.to_host_vec(handle)?;
let len = bytes.len() / type_size;
let mut result = Vec::with_capacity(len);
for chunk in bytes.chunks_exact(type_size) {
let value = unsafe { std::ptr::read_unaligned(chunk.as_ptr() as *const T) };
result.push(value);
}
Ok(result)
}
}
pub(crate) struct HostArrayView<'a> {
pub(crate) raw: *mut cugraph_type_erased_host_array_view_t,
_lifetime: PhantomData<&'a ()>,
_nosend_nosync: PhantomData<*mut ()>,
}
impl fmt::Debug for HostArrayView<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HostArrayView").finish_non_exhaustive()
}
}
impl<'a> HostArrayView<'a> {
pub(crate) fn new(data: &'a [u8], n_elems: usize, dtype: DataType) -> Result<Self> {
let required = checked_byte_len(n_elems, dtype, "HostArrayView::new")?;
if data.len() < required {
return Err(Error::new(ErrorKind::InvalidInput, "HostArrayView::new")
.with_message("slice is too small for the requested number of elements")
.with_context("required_bytes", required.to_string())
.with_context("actual_bytes", data.len().to_string())
.with_context("n_elems", n_elems.to_string())
.with_context("dtype", format!("{:?}", dtype)));
}
let data_ptr = data.as_ptr().cast::<std::ffi::c_void>().cast_mut();
let raw = unsafe { cugraph_type_erased_host_array_view_create(data_ptr, n_elems, dtype.into()) };
if raw.is_null() {
return Err(Error::new(ErrorKind::OutOfMemory, "HostArrayView::new")
.with_message("failed to allocate host array view metadata")
.set_temporary());
}
Ok(HostArrayView { raw, _lifetime: PhantomData, _nosend_nosync: PhantomData })
}
pub(crate) fn as_ptr(&self) -> *mut cugraph_type_erased_host_array_view_t {
self.raw
}
}
impl Drop for HostArrayView<'_> {
fn drop(&mut self) {
unsafe { cugraph_type_erased_host_array_view_free(self.raw) };
}
}
#[cfg(test)]
mod tests {
use static_assertions::assert_not_impl_any;
use super::*;
use crate::error::ErrorKind;
#[test]
fn test_array_types_not_send_sync() {
assert_not_impl_any!(DeviceArray<i32>: Send, Sync);
assert_not_impl_any!(DeviceArrayView<'static, i32>: Send, Sync);
assert_not_impl_any!(HostArrayView<'static>: Send, Sync);
}
#[test]
fn test_host_array_view_rejects_undersized_slice() {
let err = HostArrayView::new(&[0u8; 3], 2, DataType::Int32).expect_err("must reject undersized slice");
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert_eq!(err.operation(), "HostArrayView::new");
}
#[test]
fn test_host_array_view_rejects_overflowing_size() {
let err = HostArrayView::new(&[], usize::MAX, DataType::Int64).expect_err("must reject overflowing size math");
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert_eq!(err.operation(), "HostArrayView::new");
}
#[test]
fn test_raw_device_array_view_try_from_raw_rejects_overflowing_size() {
let err = unsafe {
RawDeviceArrayView::try_from_raw(
std::ptr::null_mut(),
usize::MAX,
DataType::Int64,
"RawDeviceArrayView::try_from_raw",
)
}
.expect_err("must reject overflowing size math");
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert_eq!(err.operation(), "RawDeviceArrayView::try_from_raw");
}
#[test]
fn test_raw_device_array_view_from_borrowed_rejects_null() {
let err = unsafe { RawDeviceArrayView::from_borrowed(std::ptr::null_mut(), None, "test_op", "view") }
.expect_err("null borrowed view should be rejected");
assert_eq!(err.kind(), ErrorKind::Unexpected);
assert_eq!(err.operation(), "test_op");
}
#[test]
fn test_host_array_view_accepts_exact_size() {
let view = HostArrayView::new(&[0u8; 8], 2, DataType::Int32).expect("exact size should succeed");
drop(view);
}
#[test]
fn test_host_array_view_accepts_oversized_slice() {
let view = HostArrayView::new(&[0u8; 16], 2, DataType::Int32).expect("oversized slice should succeed");
drop(view);
}
}