use super::{Error, allocate, empty_tensor};
use crate::{Managed, ManagedTensorBase, OpaqueContext};
use std::{alloc::Layout, mem::ManuallyDrop, ptr::NonNull};
pub struct Allocation<M> {
managed: NonNull<M>,
extra: NonNull<i64>,
extra_len: usize,
layout: Layout,
}
impl<M: ManagedTensorBase> Allocation<M> {
pub fn allocate(extra: usize) -> Result<Self, Error> {
let parts = allocation_parts::<M>(extra)?;
let managed = allocate::<M>(parts.layout);
unsafe {
let base = managed.as_ptr().cast::<u8>();
base.add(parts.header)
.cast::<usize>()
.write(parts.layout.size());
let extra_ptr = base.add(parts.extra).cast::<i64>();
extra_ptr.write_bytes(0, extra);
Ok(Self {
managed,
extra: NonNull::new_unchecked(extra_ptr),
extra_len: extra,
layout: parts.layout,
})
}
}
pub fn extra_mut(&mut self) -> &mut [i64] {
unsafe { std::slice::from_raw_parts_mut(self.extra.as_ptr(), self.extra_len) }
}
pub fn initialize<C: OpaqueContext>(
self,
ctx: C,
ndim: usize,
) -> Result<Initialized<M>, Error> {
let ndim = i32::try_from(ndim).map_err(|_| Error::NdimOverflow { ndim })?;
let this = ManuallyDrop::new(self);
unsafe {
this.managed.as_ptr().write(M::from_parts(
empty_tensor(ndim),
ctx.into_raw(),
Some(drop_allocation::<C, M>),
));
Ok(super::Initialized {
managed: Managed::from_raw_unchecked(this.managed.as_ptr()),
storage: Metadata {
extra: this.extra,
extra_len: this.extra_len,
},
})
}
}
}
impl<M> Drop for Allocation<M> {
fn drop(&mut self) {
unsafe { std::alloc::dealloc(self.managed.as_ptr().cast(), self.layout) };
}
}
pub struct Metadata {
extra: NonNull<i64>,
extra_len: usize,
}
pub type Initialized<M> = super::Initialized<M, Metadata>;
impl<M: ManagedTensorBase> super::Initialized<M, Metadata> {
pub fn extra_mut(&mut self) -> &mut [i64] {
unsafe {
std::slice::from_raw_parts_mut(self.storage.extra.as_ptr(), self.storage.extra_len)
}
}
}
struct Parts {
layout: Layout,
header: usize,
extra: usize,
}
fn allocation_parts<M>(extra: usize) -> Result<Parts, Error> {
let extra_layout = Layout::array::<i64>(extra).map_err(|_| Error::LayoutOverflow)?;
let (layout, header) = Layout::new::<M>()
.extend(Layout::new::<usize>())
.map_err(|_| Error::LayoutOverflow)?;
let (layout, extra) = layout
.extend(extra_layout)
.map_err(|_| Error::LayoutOverflow)?;
Ok(Parts {
layout: layout.pad_to_align(),
header,
extra,
})
}
unsafe extern "C" fn drop_allocation<C, M>(managed: *mut M)
where
C: OpaqueContext,
M: ManagedTensorBase,
{
if managed.is_null() {
return;
}
unsafe {
let header = Layout::new::<M>().extend(Layout::new::<usize>()).unwrap().1;
let size = managed.cast::<u8>().add(header).cast::<usize>().read();
let align = align_of::<M>()
.max(align_of::<usize>())
.max(align_of::<i64>());
let layout = Layout::from_size_align_unchecked(size, align);
C::drop_raw((*managed).manager_ctx());
std::ptr::drop_in_place(managed);
std::alloc::dealloc(managed.cast(), layout);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DlpackFlags, ffi::DLManagedTensorVersioned};
use crate::{ManagedTensorBase, ffi::DLManagedTensor};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
#[test]
fn extra_buffer_can_hold_shape_and_strides() {
let mut allocation = Allocation::<DLManagedTensor>::allocate(4).unwrap();
allocation.extra_mut().copy_from_slice(&[2, 3, 3, 1]);
let mut initialized = allocation.initialize(Box::new(()), 2).unwrap();
let extra = initialized.extra_mut().as_mut_ptr();
initialized.tensor_mut().shape = extra;
initialized.tensor_mut().strides = unsafe { extra.add(2) };
let tensor = unsafe { initialized.finish() };
assert_eq!(tensor.validate().unwrap().shape(), &[2, 3]);
assert_eq!(tensor.validate().unwrap().strides().unwrap(), &[3, 1]);
}
#[test]
fn borrowed_shape_and_copied_strides_are_independent() {
let shape = [2_i64, 3];
let mut allocation = Allocation::<DLManagedTensor>::allocate(2).unwrap();
allocation.extra_mut().copy_from_slice(&[3, 1]);
let mut initialized = allocation.initialize(Box::new(()), 2).unwrap();
initialized.tensor_mut().shape = shape.as_ptr().cast_mut();
initialized.tensor_mut().strides = initialized.extra_mut().as_mut_ptr();
let tensor = unsafe { initialized.finish() };
assert_eq!(tensor.validate().unwrap().shape(), &shape);
}
#[test]
fn initialization_paths_release_ownership() {
drop(Allocation::<DLManagedTensor>::allocate(3).unwrap());
let drops = Arc::new(AtomicUsize::new(0));
let context = Box::new(DropCounter(Arc::clone(&drops)));
let allocation = Allocation::<DLManagedTensor>::allocate(0).unwrap();
drop(allocation.initialize(context, 0).unwrap());
assert_eq!(drops.load(Ordering::Relaxed), 1);
let context = Box::new(DropCounter(Arc::clone(&drops)));
let allocation = Allocation::<DLManagedTensor>::allocate(0).unwrap();
assert!(allocation.initialize(context, usize::MAX).is_err());
assert_eq!(drops.load(Ordering::Relaxed), 2);
}
#[test]
fn deleter_ignores_public_ndim() {
let allocation = Allocation::<DLManagedTensor>::allocate(2).unwrap();
let initialized = allocation.initialize(Box::new(()), 1).unwrap();
let tensor = unsafe { initialized.finish() };
unsafe { (*tensor.as_ptr()).tensor_mut().ndim = i32::MAX };
drop(tensor);
}
#[test]
fn versioned_flags_are_configurable() {
let allocation = Allocation::<DLManagedTensorVersioned>::allocate(0).unwrap();
let mut initialized = allocation.initialize(Box::new(()), 0).unwrap();
initialized.set_flags(DlpackFlags::READ_ONLY).unwrap();
assert_eq!(
unsafe { initialized.finish() }.flags(),
DlpackFlags::READ_ONLY
);
}
#[test]
fn versioned_version_is_configurable() {
let allocation = Allocation::<DLManagedTensorVersioned>::allocate(0).unwrap();
let mut initialized = allocation.initialize(Box::new(()), 0).unwrap();
let version = crate::ffi::DLPackVersion { major: 1, minor: 2 };
initialized.set_version(version).unwrap();
assert_eq!(initialized.version().major, 1);
assert_eq!(initialized.version().minor, 2);
let actual = unsafe { initialized.finish() }.version();
assert_eq!(actual.major, version.major);
assert_eq!(actual.minor, version.minor);
}
#[test]
fn versioned_version_rejects_incompatible_major() {
let allocation = Allocation::<DLManagedTensorVersioned>::allocate(0).unwrap();
let mut initialized = allocation.initialize(Box::new(()), 0).unwrap();
let version = crate::ffi::DLPackVersion {
major: crate::ffi::DLPACK_MAJOR_VERSION + 1,
minor: 0,
};
let error = match initialized.set_version(version) {
Ok(_) => panic!("incompatible major version must be rejected"),
Err(error) => error,
};
assert_eq!(error.actual, version.major);
}
}