use crate::DlpackElement;
use crate::DlpackFlags;
use crate::ManagedTensorBase;
use crate::ffi::{DLManagedTensorVersioned, DLPackVersion};
use crate::tensor;
use std::ptr::NonNull;
pub struct ManagedBox<M: ManagedTensorBase>(NonNull<M>);
impl<M> ManagedBox<M>
where
M: ManagedTensorBase,
{
pub unsafe fn new(ptr: *mut M) -> Option<Self> {
NonNull::new(ptr).map(ManagedBox)
}
pub unsafe fn new_unchecked(ptr: *mut M) -> Self {
Self(unsafe { NonNull::new_unchecked(ptr) })
}
pub fn into_raw(self) -> *mut M {
let ptr = self.0.as_ptr();
std::mem::forget(self);
ptr
}
pub fn as_ptr(&self) -> *mut M {
self.0.as_ptr()
}
pub fn tensor(&self) -> &crate::ffi::DLTensor {
unsafe { self.0.as_ref() }.tensor()
}
pub fn shape(&self) -> Result<&[i64], tensor::Error> {
self.tensor().shape()
}
pub fn strides(&self) -> Result<Option<&[i64]>, tensor::Error> {
self.tensor().strides()
}
pub fn num_elements(&self) -> Result<usize, tensor::Error> {
self.tensor().num_elements()
}
pub fn num_bytes(&self) -> Result<usize, tensor::Error> {
self.tensor().num_bytes()
}
pub fn cpu_data_slice<T: DlpackElement>(&self) -> Result<&[T], tensor::Error> {
self.tensor().cpu_data_slice()
}
pub unsafe fn cpu_data_slice_mut_unchecked<T: DlpackElement>(
&mut self,
) -> Result<&mut [T], tensor::Error> {
if self.flags().contains(DlpackFlags::READ_ONLY) {
return Err(tensor::Error::ReadOnly);
}
let tensor = self.tensor();
if !tensor.is_compact()? {
return Err(tensor::Error::NonCompactStrides);
}
let len = tensor.num_elements()?;
let data = tensor.cpu_data_ptr::<T>()?.cast_mut();
Ok(unsafe { std::slice::from_raw_parts_mut(data, len) })
}
pub fn cpu_data_slice_mut<T: DlpackElement>(&mut self) -> Result<&mut [T], tensor::Error> {
if !self.flags().contains(DlpackFlags::IS_COPIED) {
return Err(tensor::Error::NotCopied);
}
unsafe { self.cpu_data_slice_mut_unchecked() }
}
}
impl<M> std::ops::Deref for ManagedBox<M>
where
M: ManagedTensorBase,
{
type Target = M;
fn deref(&self) -> &Self::Target {
unsafe { self.0.as_ref() }
}
}
impl ManagedBox<DLManagedTensorVersioned> {
pub fn flags(&self) -> DlpackFlags {
unsafe { self.0.as_ref() }.flags
}
pub unsafe fn flags_mut(&mut self) -> &mut DlpackFlags {
&mut unsafe { self.0.as_mut() }.flags
}
pub fn version(&self) -> DLPackVersion {
unsafe { self.0.as_ref() }.version
}
}
impl<M> Drop for ManagedBox<M>
where
M: ManagedTensorBase,
{
fn drop(&mut self) {
unsafe {
M::drop_raw(self.0.as_ptr());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{builder::Builder, ffi::DLManagedTensor, metadata};
use std::ffi::c_void;
fn dlpack_with_flags<M: ManagedTensorBase>(flags: DlpackFlags) -> ManagedBox<M> {
let data = Box::new(vec![1i32, 2, 3]);
let data_ptr = data.as_ptr() as *mut c_void;
let builder = Builder::new(data, metadata::CopiedArray::new([3i64], [1i64]))
.data(data_ptr)
.dtype(crate::ffi::DLDataType::of::<i32>());
unsafe { builder.flags_unchecked(flags) }.build::<M>()
}
#[test]
fn versioned_flags_roundtrip_through_builder() {
let dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(DlpackFlags::READ_ONLY);
assert_eq!(dlpack.flags(), DlpackFlags::READ_ONLY);
}
#[test]
fn versioned_flags_default_to_empty() {
let dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(DlpackFlags::empty());
assert_eq!(dlpack.flags(), DlpackFlags::empty());
}
#[test]
fn mutable_cpu_slice_unchecked_updates_writable_tensor() {
let mut dlpack = dlpack_with_flags::<DLManagedTensor>(DlpackFlags::empty());
unsafe {
dlpack.cpu_data_slice_mut_unchecked::<i32>().unwrap()[1] = 7;
}
assert_eq!(dlpack.cpu_data_slice::<i32>().unwrap(), &[1, 7, 3]);
}
#[test]
fn mutable_cpu_slice_unchecked_rejects_read_only_tensor() {
let mut dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(DlpackFlags::READ_ONLY);
let error = unsafe { dlpack.cpu_data_slice_mut_unchecked::<i32>() }.unwrap_err();
assert!(matches!(error, tensor::Error::ReadOnly));
}
#[test]
fn mutable_cpu_slice_unchecked_rejects_non_compact_strides() {
let data = Box::new(vec![1i32, 2, 3, 4]);
let data_ptr = data.as_ptr() as *mut c_void;
let mut dlpack = Builder::new(data, metadata::CopiedArray::new([2, 2], [1, 2]))
.data(data_ptr)
.dtype(crate::ffi::DLDataType::of::<i32>())
.build::<DLManagedTensor>();
let error = unsafe { dlpack.cpu_data_slice_mut_unchecked::<i32>() }.unwrap_err();
assert!(matches!(error, tensor::Error::NonCompactStrides));
}
#[test]
fn mutable_cpu_slice_updates_is_copied_tensor() {
let mut dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(DlpackFlags::IS_COPIED);
dlpack.cpu_data_slice_mut::<i32>().unwrap()[1] = 7;
assert_eq!(dlpack.cpu_data_slice::<i32>().unwrap(), &[1, 7, 3]);
}
#[test]
fn mutable_cpu_slice_rejects_tensor_without_is_copied() {
let mut dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(DlpackFlags::empty());
let error = dlpack.cpu_data_slice_mut::<i32>().unwrap_err();
assert!(matches!(error, tensor::Error::NotCopied));
}
#[test]
fn mutable_cpu_slice_rejects_read_only_tensor_even_if_copied() {
let mut dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(
DlpackFlags::READ_ONLY | DlpackFlags::IS_COPIED,
);
let error = dlpack.cpu_data_slice_mut::<i32>().unwrap_err();
assert!(matches!(error, tensor::Error::ReadOnly));
}
#[test]
fn mutable_cpu_slice_rejects_legacy_tensor_as_never_copied() {
let mut dlpack = dlpack_with_flags::<DLManagedTensor>(DlpackFlags::empty());
let error = dlpack.cpu_data_slice_mut::<i32>().unwrap_err();
assert!(matches!(error, tensor::Error::NotCopied));
}
#[test]
fn flags_mut_updates_versioned_tensor() {
let mut dlpack = dlpack_with_flags::<DLManagedTensorVersioned>(DlpackFlags::empty());
unsafe {
*dlpack.flags_mut() |= DlpackFlags::READ_ONLY;
}
assert_eq!(dlpack.flags(), DlpackFlags::READ_ONLY);
}
}