1use std::{
4 ffi::c_void,
5 mem::ManuallyDrop,
6 ptr::{self, NonNull},
7 slice,
8};
9
10use crate::{
11 DataType, DataTypeCode, Device, DeviceKind, TensorBuffer, TensorDescriptor, TensorError,
12 TensorStorage, TensorView,
13};
14
15pub const DLPACK_MAJOR: u32 = 1;
17pub const DLPACK_MINOR: u32 = 0;
19
20const FLAG_READ_ONLY: u64 = 1;
21const MAX_RANK: usize = 64;
22
23#[derive(Debug, thiserror::Error)]
25pub enum DlpackError {
26 #[error("DLPack managed tensor pointer is null")]
28 NullManagedTensor,
29 #[error("unsupported DLPack ABI version {major}.{minor}; expected major {DLPACK_MAJOR}")]
31 IncompatibleVersion {
32 major: u32,
34 minor: u32,
36 },
37 #[error("invalid DLPack tensor rank {0}")]
39 InvalidRank(i32),
40 #[error("DLPack tensor shape pointer is null for non-zero rank")]
42 NullShape,
43 #[error("invalid DLPack shape dimension {0}")]
45 InvalidDimension(i64),
46 #[error("unsupported DLPack dtype code={code}, bits={bits}, lanes={lanes}")]
48 UnsupportedDataType {
49 code: u8,
51 bits: u8,
53 lanes: u16,
55 },
56 #[error("unsupported DLPack device type {0}")]
58 UnsupportedDevice(i32),
59 #[error("non-empty DLPack host tensor has a null data pointer")]
61 NullData,
62 #[error("DLPack metadata does not fit the host integer representation")]
64 IntegerConversion,
65 #[error(transparent)]
67 Tensor(#[from] TensorError),
68}
69
70#[repr(C)]
71#[derive(Clone, Copy)]
72struct RawVersion {
73 major: u32,
74 minor: u32,
75}
76
77#[repr(C)]
78#[derive(Clone, Copy)]
79struct RawDevice {
80 device_type: i32,
81 device_id: i32,
82}
83
84#[repr(C)]
85#[derive(Clone, Copy)]
86struct RawDataType {
87 code: u8,
88 bits: u8,
89 lanes: u16,
90}
91
92#[repr(C)]
93struct RawTensor {
94 data: *mut c_void,
95 device: RawDevice,
96 ndim: i32,
97 dtype: RawDataType,
98 shape: *mut i64,
99 strides: *mut i64,
100 byte_offset: u64,
101}
102
103#[repr(C)]
104struct RawManagedTensorVersioned {
105 version: RawVersion,
106 manager_ctx: *mut c_void,
107 deleter: Option<unsafe extern "C" fn(*mut RawManagedTensorVersioned)>,
108 flags: u64,
109 dl_tensor: RawTensor,
110}
111
112#[repr(C)]
113struct RawManagedTensorLegacy {
114 dl_tensor: RawTensor,
115 manager_ctx: *mut c_void,
116 deleter: Option<unsafe extern "C" fn(*mut RawManagedTensorLegacy)>,
117}
118
119struct ExportContext {
120 _allocation: TensorStorage,
121 _shape: Box<[i64]>,
122 _strides: Box<[i64]>,
123}
124
125pub struct DlpackExport {
130 raw: NonNull<RawManagedTensorVersioned>,
131}
132
133pub struct DlpackLegacyExport {
137 raw: NonNull<RawManagedTensorLegacy>,
138}
139
140pub unsafe fn release_dlpack_raw(raw: *mut c_void) {
148 unsafe { call_deleter(raw.cast()) };
150}
151
152pub unsafe fn release_dlpack_legacy_raw(raw: *mut c_void) {
159 unsafe { call_legacy_deleter(raw.cast()) };
161}
162
163impl DlpackExport {
164 pub fn from_tensor(tensor: &TensorBuffer) -> Result<Self, DlpackError> {
166 let descriptor = tensor.descriptor();
167 if !descriptor.device().is_host_accessible() {
168 return Err(TensorError::DeviceNotHostAccessible(descriptor.device()).into());
169 }
170 let shape = descriptor
171 .shape()
172 .iter()
173 .map(|&dimension| i64::try_from(dimension).map_err(|_| DlpackError::IntegerConversion))
174 .collect::<Result<Vec<_>, _>>()?
175 .into_boxed_slice();
176 let strides = match descriptor.strides() {
177 Some(values) => values
178 .iter()
179 .map(|&stride| i64::try_from(stride).map_err(|_| DlpackError::IntegerConversion))
180 .collect::<Result<Vec<_>, _>>()?,
181 None => compact_strides(descriptor.shape())?,
182 }
183 .into_boxed_slice();
184 let ndim =
185 i32::try_from(descriptor.shape().len()).map_err(|_| DlpackError::IntegerConversion)?;
186 let byte_offset =
187 u64::try_from(descriptor.byte_offset()).map_err(|_| DlpackError::IntegerConversion)?;
188 let allocation = tensor.shared_allocation();
189 let data = if allocation.is_empty() {
190 ptr::null_mut()
191 } else {
192 allocation.as_ptr().cast_mut().cast()
193 };
194 let shape_ptr = if shape.is_empty() { ptr::null_mut() } else { shape.as_ptr().cast_mut() };
195 let strides_ptr =
196 if strides.is_empty() { ptr::null_mut() } else { strides.as_ptr().cast_mut() };
197 let context =
198 Box::new(ExportContext { _allocation: allocation, _shape: shape, _strides: strides });
199 let manager_ctx = Box::into_raw(context).cast();
200 let raw = Box::new(RawManagedTensorVersioned {
201 version: RawVersion { major: DLPACK_MAJOR, minor: DLPACK_MINOR },
202 manager_ctx,
203 deleter: Some(delete_export),
204 flags: FLAG_READ_ONLY,
205 dl_tensor: RawTensor {
206 data,
207 device: encode_device(descriptor.device()),
208 ndim,
209 dtype: encode_dtype(descriptor.dtype()),
210 shape: shape_ptr,
211 strides: strides_ptr,
212 byte_offset,
213 },
214 });
215 Ok(Self { raw: NonNull::from(Box::leak(raw)) })
216 }
217
218 pub fn as_raw(&self) -> *mut c_void {
220 self.raw.as_ptr().cast()
221 }
222
223 pub fn into_raw(self) -> *mut c_void {
225 let this = ManuallyDrop::new(self);
226 this.raw.as_ptr().cast()
227 }
228}
229
230impl Drop for DlpackExport {
231 fn drop(&mut self) {
232 unsafe { call_deleter(self.raw.as_ptr()) };
234 }
235}
236
237impl DlpackLegacyExport {
238 pub fn from_tensor(tensor: &TensorBuffer) -> Result<Self, DlpackError> {
240 let (context, tensor) = build_export(tensor)?;
241 let raw = Box::new(RawManagedTensorLegacy {
242 dl_tensor: tensor,
243 manager_ctx: Box::into_raw(context).cast(),
244 deleter: Some(delete_legacy_export),
245 });
246 Ok(Self { raw: NonNull::from(Box::leak(raw)) })
247 }
248
249 pub fn into_raw(self) -> *mut c_void {
251 let this = ManuallyDrop::new(self);
252 this.raw.as_ptr().cast()
253 }
254}
255
256impl Drop for DlpackLegacyExport {
257 fn drop(&mut self) {
258 unsafe { call_legacy_deleter(self.raw.as_ptr()) };
260 }
261}
262
263#[derive(Debug)]
265pub struct DlpackImport {
266 raw: ImportedRaw,
267 descriptor: TensorDescriptor,
268 allocation_len: usize,
269 data: *const u8,
270 version: (u32, u32),
271 flags: u64,
272}
273
274#[derive(Debug)]
275enum ImportedRaw {
276 Versioned(NonNull<RawManagedTensorVersioned>),
277 Legacy(NonNull<RawManagedTensorLegacy>),
278}
279
280impl DlpackImport {
281 pub unsafe fn from_raw(raw: *mut c_void) -> Result<Self, DlpackError> {
289 let raw = NonNull::new(raw.cast::<RawManagedTensorVersioned>())
290 .ok_or(DlpackError::NullManagedTensor)?;
291 let guard = IncomingGuard { raw: Some(raw) };
292
293 let managed = unsafe { raw.as_ref() };
296 let version = (managed.version.major, managed.version.minor);
297 if version.0 != DLPACK_MAJOR {
298 return Err(DlpackError::IncompatibleVersion { major: version.0, minor: version.1 });
299 }
300 let tensor = &managed.dl_tensor;
301 if tensor.ndim < 0 || tensor.ndim as usize > MAX_RANK {
302 return Err(DlpackError::InvalidRank(tensor.ndim));
303 }
304 let rank = tensor.ndim as usize;
305 if rank != 0 && tensor.shape.is_null() {
306 return Err(DlpackError::NullShape);
307 }
308 let shape_values = if rank == 0 {
309 &[][..]
310 } else {
311 unsafe { slice::from_raw_parts(tensor.shape, rank) }
313 };
314 let shape = shape_values
315 .iter()
316 .map(|&dimension| {
317 usize::try_from(dimension).map_err(|_| DlpackError::InvalidDimension(dimension))
318 })
319 .collect::<Result<Vec<_>, _>>()?;
320 let strides = if tensor.strides.is_null() {
321 None
322 } else {
323 let values = unsafe { slice::from_raw_parts(tensor.strides, rank) };
325 Some(
326 values
327 .iter()
328 .map(|&stride| {
329 isize::try_from(stride).map_err(|_| DlpackError::IntegerConversion)
330 })
331 .collect::<Result<Vec<_>, _>>()?,
332 )
333 };
334 let dtype = decode_dtype(tensor.dtype)?;
335 let device = decode_device(tensor.device)?;
336 let byte_offset =
337 usize::try_from(tensor.byte_offset).map_err(|_| DlpackError::IntegerConversion)?;
338 let descriptor = match strides {
339 Some(strides) => {
340 TensorDescriptor::try_strided(dtype, shape, strides, byte_offset, device)?
341 }
342 None => {
343 let mut descriptor = TensorDescriptor::contiguous(dtype, shape, device);
344 if byte_offset != 0 {
345 descriptor = TensorDescriptor::try_strided(
346 dtype,
347 descriptor.shape().to_vec(),
348 compact_strides_isize(descriptor.shape())?,
349 byte_offset,
350 device,
351 )?;
352 }
353 descriptor
354 }
355 };
356 let range = descriptor.required_byte_range()?;
357 if range.end != 0 && tensor.data.is_null() {
358 return Err(DlpackError::NullData);
359 }
360 let raw = ImportedRaw::Versioned(guard.disarm());
361 Ok(Self {
362 raw,
363 descriptor,
364 allocation_len: range.end,
365 data: tensor.data.cast(),
366 version,
367 flags: managed.flags,
368 })
369 }
370
371 pub unsafe fn from_legacy_raw(raw: *mut c_void) -> Result<Self, DlpackError> {
377 let raw = NonNull::new(raw.cast::<RawManagedTensorLegacy>())
378 .ok_or(DlpackError::NullManagedTensor)?;
379 let guard = IncomingLegacyGuard { raw: Some(raw) };
380 let managed = unsafe { raw.as_ref() };
382 let (descriptor, allocation_len, data) = validate_tensor(&managed.dl_tensor)?;
383 let raw = ImportedRaw::Legacy(guard.disarm());
384 Ok(Self { raw, descriptor, allocation_len, data, version: (0, 0), flags: 0 })
385 }
386
387 pub const fn version(&self) -> (u32, u32) {
389 self.version
390 }
391
392 pub const fn flags(&self) -> u64 {
394 self.flags
395 }
396
397 pub const fn descriptor(&self) -> &TensorDescriptor {
399 &self.descriptor
400 }
401
402 pub fn view(&self) -> Result<TensorView<'_>, DlpackError> {
404 let bytes = if self.allocation_len == 0 {
405 &[][..]
406 } else {
407 unsafe { slice::from_raw_parts(self.data, self.allocation_len) }
410 };
411 Ok(TensorView::try_new(bytes, self.descriptor.clone())?)
412 }
413}
414
415impl Drop for DlpackImport {
416 fn drop(&mut self) {
417 unsafe {
419 match self.raw {
420 ImportedRaw::Versioned(raw) => call_deleter(raw.as_ptr()),
421 ImportedRaw::Legacy(raw) => call_legacy_deleter(raw.as_ptr()),
422 }
423 };
424 }
425}
426
427struct IncomingGuard {
428 raw: Option<NonNull<RawManagedTensorVersioned>>,
429}
430
431impl IncomingGuard {
432 fn disarm(mut self) -> NonNull<RawManagedTensorVersioned> {
433 self.raw.take().expect("incoming pointer is present")
434 }
435}
436
437impl Drop for IncomingGuard {
438 fn drop(&mut self) {
439 if let Some(raw) = self.raw {
440 unsafe { call_deleter(raw.as_ptr()) };
442 }
443 }
444}
445
446struct IncomingLegacyGuard {
447 raw: Option<NonNull<RawManagedTensorLegacy>>,
448}
449
450impl IncomingLegacyGuard {
451 fn disarm(mut self) -> NonNull<RawManagedTensorLegacy> {
452 self.raw.take().expect("incoming pointer is present")
453 }
454}
455
456impl Drop for IncomingLegacyGuard {
457 fn drop(&mut self) {
458 if let Some(raw) = self.raw {
459 unsafe { call_legacy_deleter(raw.as_ptr()) };
461 }
462 }
463}
464
465unsafe extern "C" fn delete_export(raw: *mut RawManagedTensorVersioned) {
466 if raw.is_null() {
467 return;
468 }
469 let managed = unsafe { Box::from_raw(raw) };
472 if !managed.manager_ctx.is_null() {
473 drop(unsafe { Box::from_raw(managed.manager_ctx.cast::<ExportContext>()) });
475 }
476}
477
478unsafe extern "C" fn delete_legacy_export(raw: *mut RawManagedTensorLegacy) {
479 if raw.is_null() {
480 return;
481 }
482 let managed = unsafe { Box::from_raw(raw) };
484 if !managed.manager_ctx.is_null() {
485 drop(unsafe { Box::from_raw(managed.manager_ctx.cast::<ExportContext>()) });
487 }
488}
489
490unsafe fn call_deleter(raw: *mut RawManagedTensorVersioned) {
491 if raw.is_null() {
492 return;
493 }
494 if let Some(deleter) = unsafe { (*raw).deleter } {
496 unsafe { deleter(raw) };
498 }
499}
500
501unsafe fn call_legacy_deleter(raw: *mut RawManagedTensorLegacy) {
502 if raw.is_null() {
503 return;
504 }
505 if let Some(deleter) = unsafe { (*raw).deleter } {
507 unsafe { deleter(raw) };
509 }
510}
511
512fn build_export(tensor: &TensorBuffer) -> Result<(Box<ExportContext>, RawTensor), DlpackError> {
513 let descriptor = tensor.descriptor();
514 if !descriptor.device().is_host_accessible() {
515 return Err(TensorError::DeviceNotHostAccessible(descriptor.device()).into());
516 }
517 let shape = descriptor
518 .shape()
519 .iter()
520 .map(|&dimension| i64::try_from(dimension).map_err(|_| DlpackError::IntegerConversion))
521 .collect::<Result<Vec<_>, _>>()?
522 .into_boxed_slice();
523 let strides = match descriptor.strides() {
524 Some(values) => values
525 .iter()
526 .map(|&stride| i64::try_from(stride).map_err(|_| DlpackError::IntegerConversion))
527 .collect::<Result<Vec<_>, _>>()?,
528 None => compact_strides(descriptor.shape())?,
529 }
530 .into_boxed_slice();
531 let ndim =
532 i32::try_from(descriptor.shape().len()).map_err(|_| DlpackError::IntegerConversion)?;
533 let byte_offset =
534 u64::try_from(descriptor.byte_offset()).map_err(|_| DlpackError::IntegerConversion)?;
535 let allocation = tensor.shared_allocation();
536 let data =
537 if allocation.is_empty() { ptr::null_mut() } else { allocation.as_ptr().cast_mut().cast() };
538 let shape_ptr = if shape.is_empty() { ptr::null_mut() } else { shape.as_ptr().cast_mut() };
539 let strides_ptr =
540 if strides.is_empty() { ptr::null_mut() } else { strides.as_ptr().cast_mut() };
541 let context =
542 Box::new(ExportContext { _allocation: allocation, _shape: shape, _strides: strides });
543 let tensor = RawTensor {
544 data,
545 device: encode_device(descriptor.device()),
546 ndim,
547 dtype: encode_dtype(descriptor.dtype()),
548 shape: shape_ptr,
549 strides: strides_ptr,
550 byte_offset,
551 };
552 Ok((context, tensor))
553}
554
555fn validate_tensor(
556 tensor: &RawTensor,
557) -> Result<(TensorDescriptor, usize, *const u8), DlpackError> {
558 if tensor.ndim < 0 || tensor.ndim as usize > MAX_RANK {
559 return Err(DlpackError::InvalidRank(tensor.ndim));
560 }
561 let rank = tensor.ndim as usize;
562 if rank != 0 && tensor.shape.is_null() {
563 return Err(DlpackError::NullShape);
564 }
565 let shape_values = if rank == 0 {
566 &[][..]
567 } else {
568 unsafe { slice::from_raw_parts(tensor.shape, rank) }
570 };
571 let shape = shape_values
572 .iter()
573 .map(|&dimension| {
574 usize::try_from(dimension).map_err(|_| DlpackError::InvalidDimension(dimension))
575 })
576 .collect::<Result<Vec<_>, _>>()?;
577 let strides = if tensor.strides.is_null() {
578 None
579 } else {
580 let values = unsafe { slice::from_raw_parts(tensor.strides, rank) };
582 Some(
583 values
584 .iter()
585 .map(|&stride| isize::try_from(stride).map_err(|_| DlpackError::IntegerConversion))
586 .collect::<Result<Vec<_>, _>>()?,
587 )
588 };
589 let dtype = decode_dtype(tensor.dtype)?;
590 let device = decode_device(tensor.device)?;
591 let byte_offset =
592 usize::try_from(tensor.byte_offset).map_err(|_| DlpackError::IntegerConversion)?;
593 let descriptor = match strides {
594 Some(strides) => TensorDescriptor::try_strided(dtype, shape, strides, byte_offset, device)?,
595 None => {
596 let mut descriptor = TensorDescriptor::contiguous(dtype, shape, device);
597 if byte_offset != 0 {
598 descriptor = TensorDescriptor::try_strided(
599 dtype,
600 descriptor.shape().to_vec(),
601 compact_strides_isize(descriptor.shape())?,
602 byte_offset,
603 device,
604 )?;
605 }
606 descriptor
607 }
608 };
609 let range = descriptor.required_byte_range()?;
610 if range.end != 0 && tensor.data.is_null() {
611 return Err(DlpackError::NullData);
612 }
613 Ok((descriptor, range.end, tensor.data.cast()))
614}
615
616fn compact_strides(shape: &[usize]) -> Result<Vec<i64>, DlpackError> {
617 let mut output = vec![0; shape.len()];
618 let mut stride = 1_i64;
619 for (index, &dimension) in shape.iter().enumerate().rev() {
620 output[index] = stride;
621 stride = stride
622 .checked_mul(
623 i64::try_from(dimension.max(1)).map_err(|_| DlpackError::IntegerConversion)?,
624 )
625 .ok_or(DlpackError::IntegerConversion)?;
626 }
627 Ok(output)
628}
629
630fn compact_strides_isize(shape: &[usize]) -> Result<Vec<isize>, DlpackError> {
631 compact_strides(shape)?
632 .into_iter()
633 .map(|stride| isize::try_from(stride).map_err(|_| DlpackError::IntegerConversion))
634 .collect()
635}
636
637fn encode_dtype(dtype: DataType) -> RawDataType {
638 RawDataType { code: dtype.code() as u8, bits: dtype.bits(), lanes: dtype.lanes() }
639}
640
641fn decode_dtype(raw: RawDataType) -> Result<DataType, DlpackError> {
642 let code = match raw.code {
643 0 => DataTypeCode::Int,
644 1 => DataTypeCode::UInt,
645 2 => DataTypeCode::Float,
646 4 => DataTypeCode::BFloat,
647 5 => DataTypeCode::Complex,
648 6 => DataTypeCode::Bool,
649 _ => {
650 return Err(DlpackError::UnsupportedDataType {
651 code: raw.code,
652 bits: raw.bits,
653 lanes: raw.lanes,
654 })
655 }
656 };
657 DataType::try_new(code, raw.bits, raw.lanes).map_err(|_| DlpackError::UnsupportedDataType {
658 code: raw.code,
659 bits: raw.bits,
660 lanes: raw.lanes,
661 })
662}
663
664fn encode_device(device: Device) -> RawDevice {
665 let device_type = match device.kind {
666 DeviceKind::Cpu => 1,
667 DeviceKind::Cuda => 2,
668 DeviceKind::CudaHost => 3,
669 DeviceKind::OpenCl => 4,
670 DeviceKind::Vulkan => 7,
671 DeviceKind::Metal => 8,
672 DeviceKind::Vpi => 9,
673 DeviceKind::Rocm => 10,
674 DeviceKind::RocmHost => 11,
675 DeviceKind::External => 12,
676 DeviceKind::CudaManaged => 13,
677 DeviceKind::OneApi => 14,
678 DeviceKind::WebGpu => 15,
679 DeviceKind::Hexagon => 16,
680 DeviceKind::Maia => 17,
681 DeviceKind::Trainium => 18,
682 DeviceKind::Tpu => 19,
683 DeviceKind::TpuHost => 20,
684 };
685 RawDevice { device_type, device_id: device.id }
686}
687
688fn decode_device(raw: RawDevice) -> Result<Device, DlpackError> {
689 let kind = match raw.device_type {
690 1 => DeviceKind::Cpu,
691 2 => DeviceKind::Cuda,
692 3 => DeviceKind::CudaHost,
693 4 => DeviceKind::OpenCl,
694 7 => DeviceKind::Vulkan,
695 8 => DeviceKind::Metal,
696 9 => DeviceKind::Vpi,
697 10 => DeviceKind::Rocm,
698 11 => DeviceKind::RocmHost,
699 12 => DeviceKind::External,
700 13 => DeviceKind::CudaManaged,
701 14 => DeviceKind::OneApi,
702 15 => DeviceKind::WebGpu,
703 16 => DeviceKind::Hexagon,
704 17 => DeviceKind::Maia,
705 18 => DeviceKind::Trainium,
706 19 => DeviceKind::Tpu,
707 20 => DeviceKind::TpuHost,
708 other => return Err(DlpackError::UnsupportedDevice(other)),
709 };
710 Ok(Device { kind, id: raw.device_id })
711}
712
713#[cfg(test)]
714mod tests {
715 use super::{
716 DlpackError, DlpackExport, DlpackImport, DlpackLegacyExport, DLPACK_MAJOR, DLPACK_MINOR,
717 };
718 use crate::{DataType, Device, TensorBuffer, TensorDescriptor};
719
720 #[test]
721 fn owned_cpu_roundtrip_is_zero_copy_and_versioned() {
722 let tensor = TensorBuffer::try_new(
723 (0_u8..24).collect(),
724 TensorDescriptor::contiguous(DataType::F32, vec![2, 3], Device::CPU),
725 )
726 .unwrap();
727 let original = tensor.allocation_bytes().as_ptr();
728 let allocation = tensor.shared_allocation();
729 let export = DlpackExport::from_tensor(&tensor).unwrap();
730 assert_eq!(allocation.strong_count(), 3);
731 let imported = unsafe { DlpackImport::from_raw(export.into_raw()) }.unwrap();
733 assert_eq!(imported.version(), (DLPACK_MAJOR, DLPACK_MINOR));
734 let view = imported.view().unwrap();
735 assert_eq!(view.descriptor().shape(), &[2, 3]);
736 assert_eq!(view.allocation_bytes().as_ptr(), original);
737 drop(imported);
738 assert_eq!(allocation.strong_count(), 2);
739 }
740
741 #[test]
742 fn negative_stride_and_byte_offset_roundtrip() {
743 let descriptor =
744 TensorDescriptor::try_strided(DataType::U8, vec![4], vec![-1], 3, Device::CPU).unwrap();
745 let tensor = TensorBuffer::try_new(vec![10, 20, 30, 40], descriptor).unwrap();
746 let export = DlpackExport::from_tensor(&tensor).unwrap();
747 let imported = unsafe { DlpackImport::from_raw(export.into_raw()) }.unwrap();
749 let view = imported.view().unwrap();
750 assert_eq!(view.descriptor().strides(), Some(&[-1][..]));
751 assert_eq!(view.descriptor().byte_offset(), 3);
752 assert_eq!(view.allocation_bytes(), &[10, 20, 30, 40]);
753 }
754
755 #[test]
756 fn legacy_cpu_roundtrip_is_zero_copy() {
757 let tensor = TensorBuffer::try_new(
758 (0_u8..12).collect(),
759 TensorDescriptor::contiguous(DataType::U8, vec![3, 4], Device::CPU),
760 )
761 .unwrap();
762 let original = tensor.allocation_bytes().as_ptr();
763 let allocation = tensor.shared_allocation();
764 let export = DlpackLegacyExport::from_tensor(&tensor).unwrap();
765 assert_eq!(allocation.strong_count(), 3);
766 let imported = unsafe { DlpackImport::from_legacy_raw(export.into_raw()) }.unwrap();
768 assert_eq!(imported.version(), (0, 0));
769 assert_eq!(imported.flags(), 0);
770 let view = imported.view().unwrap();
771 assert_eq!(view.descriptor().shape(), &[3, 4]);
772 assert_eq!(view.allocation_bytes().as_ptr(), original);
773 drop(imported);
774 assert_eq!(allocation.strong_count(), 2);
775 }
776
777 #[test]
778 fn major_mismatch_is_rejected_and_deleted() {
779 let tensor = TensorBuffer::try_new(
780 vec![1],
781 TensorDescriptor::contiguous(DataType::U8, vec![1], Device::CPU),
782 )
783 .unwrap();
784 let allocation = tensor.shared_allocation();
785 let export = DlpackExport::from_tensor(&tensor).unwrap();
786 let raw = export.into_raw().cast::<super::RawManagedTensorVersioned>();
787 unsafe { (*raw).version.major = 99 };
789 let error = unsafe { DlpackImport::from_raw(raw.cast()) }.unwrap_err();
791 assert!(matches!(error, DlpackError::IncompatibleVersion { major: 99, .. }));
792 assert_eq!(allocation.strong_count(), 2);
793 }
794
795 #[test]
796 fn malformed_dtype_is_rejected_and_deleted() {
797 let tensor = TensorBuffer::try_new(
798 vec![1],
799 TensorDescriptor::contiguous(DataType::U8, vec![1], Device::CPU),
800 )
801 .unwrap();
802 let allocation = tensor.shared_allocation();
803 let raw = DlpackExport::from_tensor(&tensor)
804 .unwrap()
805 .into_raw()
806 .cast::<super::RawManagedTensorVersioned>();
807 unsafe { (*raw).dl_tensor.dtype.code = 255 };
809 let error = unsafe { DlpackImport::from_raw(raw.cast()) }.unwrap_err();
811 assert!(matches!(error, DlpackError::UnsupportedDataType { code: 255, .. }));
812 assert_eq!(allocation.strong_count(), 2);
813 }
814
815 #[test]
816 fn null_nonempty_data_is_rejected_and_deleted() {
817 let tensor = TensorBuffer::try_new(
818 vec![1, 2],
819 TensorDescriptor::contiguous(DataType::U8, vec![2], Device::CPU),
820 )
821 .unwrap();
822 let allocation = tensor.shared_allocation();
823 let raw = DlpackExport::from_tensor(&tensor)
824 .unwrap()
825 .into_raw()
826 .cast::<super::RawManagedTensorVersioned>();
827 unsafe { (*raw).dl_tensor.data = std::ptr::null_mut() };
829 let error = unsafe { DlpackImport::from_raw(raw.cast()) }.unwrap_err();
831 assert!(matches!(error, DlpackError::NullData));
832 assert_eq!(allocation.strong_count(), 2);
833 }
834
835 #[test]
836 fn negative_shape_is_rejected_and_deleted() {
837 let tensor = TensorBuffer::try_new(
838 vec![1],
839 TensorDescriptor::contiguous(DataType::U8, vec![1], Device::CPU),
840 )
841 .unwrap();
842 let allocation = tensor.shared_allocation();
843 let raw = DlpackExport::from_tensor(&tensor)
844 .unwrap()
845 .into_raw()
846 .cast::<super::RawManagedTensorVersioned>();
847 unsafe { *(*raw).dl_tensor.shape = -1 };
849 let error = unsafe { DlpackImport::from_raw(raw.cast()) }.unwrap_err();
851 assert!(matches!(error, DlpackError::InvalidDimension(-1)));
852 assert_eq!(allocation.strong_count(), 2);
853 }
854}