use super::{Error, compact_strides, is_compact_strides};
use crate::{
DlpackElement, DlpackFlags,
ffi::{DLDataType, DLDevice, DLTensor},
};
use std::borrow::Cow;
use std::marker::PhantomData;
pub struct TensorRef<'a> {
tensor: &'a DLTensor,
shape: &'a [i64],
strides: Option<&'a [i64]>,
num_elements: usize,
num_bytes: usize,
}
pub struct TensorMut<'a> {
inner: TensorRef<'a>,
_exclusive: PhantomData<&'a mut DLTensor>,
}
impl<'a> TensorRef<'a> {
pub unsafe fn from_raw(tensor: &'a DLTensor) -> Result<Self, Error> {
let shape = unsafe { tensor.shape()? };
let strides = unsafe { tensor.strides()? };
let num_elements = unsafe { tensor.num_elements()? };
let num_bytes = unsafe { tensor.num_bytes()? };
Ok(Self {
tensor,
shape,
strides,
num_elements,
num_bytes,
})
}
pub fn device(&self) -> DLDevice {
self.tensor.device
}
pub fn dtype(&self) -> DLDataType {
self.tensor.dtype
}
pub fn ndim(&self) -> usize {
self.shape.len()
}
pub fn shape(&self) -> &'a [i64] {
self.shape
}
pub fn strides(&self) -> Option<&'a [i64]> {
self.strides
}
pub fn byte_offset(&self) -> u64 {
self.tensor.byte_offset
}
pub fn num_elements(&self) -> usize {
self.num_elements
}
pub fn num_bytes(&self) -> usize {
self.num_bytes
}
pub fn strides_or_compact(&self) -> Result<Cow<'a, [i64]>, Error> {
match self.strides {
Some(strides) => Ok(Cow::Borrowed(strides)),
None if self.shape.is_empty() => Ok(Cow::Borrowed(&[])),
None => Ok(Cow::Owned(compact_strides(self.shape)?)),
}
}
pub fn is_compact(&self) -> Result<bool, Error> {
is_compact_strides(self.shape, self.strides)
}
pub fn data_ptr(&self) -> *const std::ffi::c_void {
self.tensor.data_ptr()
}
pub unsafe fn offset_data_ptr<T: DlpackElement>(&self) -> Result<*const T, Error> {
unsafe { self.tensor.offset_data_ptr::<T>() }
}
pub unsafe fn offset_bytes_ptr(&self) -> Result<*const u8, Error> {
unsafe { self.tensor.offset_bytes_ptr() }
}
pub unsafe fn cpu_slice<T: DlpackElement>(&self) -> Result<&'a [T], Error> {
unsafe { self.tensor.cpu_slice::<T>() }
}
pub unsafe fn cpu_bytes(&self) -> Result<&'a [u8], Error> {
unsafe { self.tensor.cpu_bytes() }
}
}
impl<'a> TensorMut<'a> {
pub(crate) unsafe fn from_raw(
tensor: &'a mut DLTensor,
flags: DlpackFlags,
) -> Result<Self, Error> {
if flags.contains(DlpackFlags::READ_ONLY) {
return Err(Error::ReadOnly);
}
let inner = unsafe { TensorRef::from_raw(tensor) }?;
Ok(Self {
inner,
_exclusive: PhantomData,
})
}
pub unsafe fn cpu_slice_mut<T: DlpackElement>(&mut self) -> Result<&mut [T], Error> {
self.inner.tensor.ensure_cpu()?;
if !self.inner.is_compact()? {
return Err(Error::NonCompactStrides);
}
let data = unsafe { self.inner.offset_data_ptr::<T>()? }.cast_mut();
Ok(unsafe { std::slice::from_raw_parts_mut(data, self.inner.num_elements) })
}
pub unsafe fn cpu_bytes_mut(&mut self) -> Result<&mut [u8], Error> {
self.inner.tensor.ensure_cpu()?;
if !self.inner.is_compact()? {
return Err(Error::NonCompactStrides);
}
let data = unsafe { self.inner.offset_bytes_ptr()? }.cast_mut();
Ok(unsafe { std::slice::from_raw_parts_mut(data, self.inner.num_bytes) })
}
}
impl<'a> std::ops::Deref for TensorMut<'a> {
type Target = TensorRef<'a>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_negative_rank() {
let tensor = DLTensor {
ndim: -1,
..DLTensor::default()
};
let error = match unsafe { TensorRef::from_raw(&tensor) } {
Ok(_) => panic!("negative rank must be rejected"),
Err(error) => error,
};
assert!(matches!(error, Error::NegativeNdim { ndim: -1 }));
}
#[test]
fn rejects_null_shape_for_nonzero_rank() {
let tensor = DLTensor {
ndim: 1,
..DLTensor::default()
};
let error = match unsafe { TensorRef::from_raw(&tensor) } {
Ok(_) => panic!("null shape must be rejected"),
Err(error) => error,
};
assert!(matches!(error, Error::NullShapePtr { ndim: 1 }));
}
#[test]
fn exposes_validated_metadata() {
let shape = [2_i64, 3];
let strides = [3_i64, 1];
let tensor = DLTensor {
ndim: 2,
shape: shape.as_ptr().cast_mut(),
strides: strides.as_ptr().cast_mut(),
..DLTensor::default()
};
let tensor = unsafe { TensorRef::from_raw(&tensor) }.unwrap();
assert_eq!(tensor.shape(), &shape);
assert_eq!(tensor.strides(), Some(strides.as_slice()));
assert_eq!(tensor.num_elements(), 6);
}
}