use crate::ffi::{DLDataType, DLDevice, DLManagedTensor, DLManagedTensorVersioned};
use crate::safe::{self, TensorInfo};
use pyo3::ffi as pyffi;
use pyo3::prelude::*;
use pyo3::types::{PyCapsule, PyCapsuleMethods};
use std::ffi::{c_void, CStr};
use std::ptr::NonNull;
const NAME_DLTENSOR: &CStr = c"dltensor";
const NAME_DLTENSOR_VERSIONED: &CStr = c"dltensor_versioned";
const NAME_USED_DLTENSOR: &CStr = c"used_dltensor";
const NAME_USED_DLTENSOR_VERSIONED: &CStr = c"used_dltensor_versioned";
unsafe extern "C" fn capsule_destructor_legacy(capsule: *mut pyffi::PyObject) {
let name_ptr = unsafe { pyffi::PyCapsule_GetName(capsule) };
if name_ptr.is_null() {
return;
}
let name = unsafe { CStr::from_ptr(name_ptr) };
if name != NAME_DLTENSOR {
return;
}
let ptr = unsafe { pyffi::PyCapsule_GetPointer(capsule, name_ptr) };
if ptr.is_null() {
return;
}
let mt = ptr as *mut DLManagedTensor;
if let Some(del) = unsafe { (*mt).deleter } {
unsafe { del(mt) };
}
}
unsafe extern "C" fn capsule_destructor_versioned(capsule: *mut pyffi::PyObject) {
let name_ptr = unsafe { pyffi::PyCapsule_GetName(capsule) };
if name_ptr.is_null() {
return;
}
let name = unsafe { CStr::from_ptr(name_ptr) };
if name != NAME_DLTENSOR_VERSIONED {
return;
}
let ptr = unsafe { pyffi::PyCapsule_GetPointer(capsule, name_ptr) };
if ptr.is_null() {
return;
}
let mt = ptr as *mut DLManagedTensorVersioned;
if let Some(del) = unsafe { (*mt).deleter } {
unsafe { del(mt) };
}
}
pub trait IntoDLPack: Sized + Send + 'static {
fn tensor_info(&self) -> TensorInfo;
fn into_capsule(self, py: Python<'_>) -> PyResult<Bound<'_, PyCapsule>> {
let info = self.tensor_info();
let mt = safe::pack(self, info);
let ptr = NonNull::new(mt as *mut c_void).ok_or_else(|| {
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("pack returned null")
})?;
unsafe {
PyCapsule::new_with_pointer_and_destructor(
py,
ptr,
NAME_DLTENSOR,
Some(capsule_destructor_legacy),
)
}
}
fn into_capsule_versioned(self, py: Python<'_>, flags: u64) -> PyResult<Bound<'_, PyCapsule>> {
let info = self.tensor_info();
let mt = safe::pack_versioned(self, info, flags);
let ptr = NonNull::new(mt as *mut c_void).ok_or_else(|| {
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("pack_versioned returned null")
})?;
unsafe {
PyCapsule::new_with_pointer_and_destructor(
py,
ptr,
NAME_DLTENSOR_VERSIONED,
Some(capsule_destructor_versioned),
)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DLPackVariant {
Legacy,
Versioned,
}
pub struct PyTensor {
_capsule: Py<PyCapsule>,
ptr: *mut c_void,
version: DLPackVariant,
}
unsafe impl Send for PyTensor {}
impl PyTensor {
pub fn from_pyany(py: Python<'_>, obj: &Bound<'_, PyAny>) -> PyResult<Self> {
let capsule_obj = obj.call_method0("__dlpack__")?;
let capsule: Bound<'_, PyCapsule> = capsule_obj.cast_into()?;
let cap_name = capsule.name()?;
let name_cstr: &CStr = match &cap_name {
Some(n) => {
unsafe { n.as_cstr() }
}
None => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"DLPack capsule has no name",
));
}
};
let version = if name_cstr == NAME_DLTENSOR {
DLPackVariant::Legacy
} else if name_cstr == NAME_DLTENSOR_VERSIONED {
DLPackVariant::Versioned
} else {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"unexpected DLPack capsule name: expected 'dltensor' or 'dltensor_versioned', got {:?}",
name_cstr
)));
};
let nn_ptr: NonNull<c_void> = capsule.pointer_checked(Some(name_cstr))?;
let new_name = match version {
DLPackVariant::Legacy => NAME_USED_DLTENSOR.as_ptr(),
DLPackVariant::Versioned => NAME_USED_DLTENSOR_VERSIONED.as_ptr(),
};
let rc = unsafe { pyffi::PyCapsule_SetName(capsule.as_ptr(), new_name) };
if rc != 0 {
return Err(PyErr::fetch(py));
}
let owned_cap: Py<PyCapsule> = capsule.into();
Ok(PyTensor {
_capsule: owned_cap,
ptr: nn_ptr.as_ptr(),
version,
})
}
pub fn dlpack_version(&self) -> DLPackVariant {
self.version
}
fn dl_tensor(&self) -> &crate::ffi::DLTensor {
match self.version {
DLPackVariant::Legacy => {
unsafe { &(*(self.ptr as *const DLManagedTensor)).dl_tensor }
}
DLPackVariant::Versioned => {
unsafe { &(*(self.ptr as *const DLManagedTensorVersioned)).dl_tensor }
}
}
}
pub fn ndim(&self) -> usize {
self.dl_tensor().ndim as usize
}
pub fn shape(&self) -> &[i64] {
let t = self.dl_tensor();
unsafe { std::slice::from_raw_parts(t.shape, t.ndim as usize) }
}
pub fn strides(&self) -> Option<&[i64]> {
let t = self.dl_tensor();
if t.strides.is_null() {
None
} else {
Some(unsafe { std::slice::from_raw_parts(t.strides, t.ndim as usize) })
}
}
pub fn dtype(&self) -> DLDataType {
self.dl_tensor().dtype
}
pub fn device(&self) -> DLDevice {
self.dl_tensor().device
}
pub fn data_ptr(&self) -> *mut c_void {
self.dl_tensor().data
}
pub fn byte_offset(&self) -> u64 {
self.dl_tensor().byte_offset
}
pub fn numel(&self) -> usize {
self.shape().iter().map(|&d| d as usize).product()
}
pub fn element_size(&self) -> usize {
let dt = self.dtype();
(dt.bits as usize).div_ceil(8) * dt.lanes as usize
}
pub fn nbytes(&self) -> usize {
self.numel() * self.element_size()
}
pub fn is_contiguous(&self) -> bool {
match self.strides() {
None => true,
Some(strides) => {
let shape = self.shape();
let ndim = shape.len();
if ndim == 0 {
return true;
}
let mut expected = 1i64;
for i in (0..ndim).rev() {
if strides[i] != expected {
return false;
}
expected *= shape[i];
}
true
}
}
}
}
impl Drop for PyTensor {
fn drop(&mut self) {
let ptr = self.ptr;
let version = self.version;
Python::attach(|_py| {
match version {
DLPackVariant::Legacy => {
let mt = ptr as *mut DLManagedTensor;
if let Some(del) = unsafe { (*mt).deleter } {
unsafe { del(mt) };
}
}
DLPackVariant::Versioned => {
let mt = ptr as *mut DLManagedTensorVersioned;
if let Some(del) = unsafe { (*mt).deleter } {
unsafe { del(mt) };
}
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::safe::{cpu_device, dtype_f32};
struct DummyBuffer {
data: Vec<f32>,
}
impl IntoDLPack for DummyBuffer {
fn tensor_info(&self) -> TensorInfo {
TensorInfo::contiguous(
self.data.as_ptr() as *mut c_void,
cpu_device(),
dtype_f32(),
vec![self.data.len() as i64],
)
}
}
#[test]
fn trait_impl_compiles() {
let _buf = DummyBuffer {
data: vec![1.0f32, 2.0, 3.0],
};
let info = _buf.tensor_info();
assert_eq!(info.shape, vec![3i64]);
}
}