1use onnx_runtime_ir::DataType;
29use onnx_runtime_ir::DeviceId;
30use std::marker::PhantomData;
31
32use crate::error::{EpError, Result};
33
34#[derive(Clone, Copy, Debug)]
36pub struct DevicePtr(pub *const std::ffi::c_void);
37
38#[derive(Clone, Copy, Debug)]
40pub struct DevicePtrMut(pub *mut std::ffi::c_void);
41
42impl DevicePtr {
43 pub fn as_ptr<T>(self) -> *const T {
45 self.0 as *const T
46 }
47
48 pub fn is_null(self) -> bool {
50 self.0.is_null()
51 }
52}
53
54impl DevicePtrMut {
55 pub fn as_ptr<T>(self) -> *mut T {
57 self.0 as *mut T
58 }
59
60 pub fn is_null(self) -> bool {
62 self.0.is_null()
63 }
64}
65
66fn validate_view(
74 data_is_null: bool,
75 dtype: DataType,
76 shape: &[usize],
77 strides: &[i64],
78 byte_offset: usize,
79) -> Result<()> {
80 if data_is_null {
81 return Err(EpError::InvalidTensorView {
82 reason: "data pointer is null".into(),
83 });
84 }
85 if shape.len() != strides.len() {
86 return Err(EpError::InvalidTensorView {
87 reason: format!(
88 "rank mismatch: shape has {} dims but strides has {}",
89 shape.len(),
90 strides.len()
91 ),
92 });
93 }
94 if dtype == DataType::String {
95 return Err(EpError::InvalidTensorView {
96 reason: "String dtype has no fixed-width raw layout".into(),
97 });
98 }
99 let esize = dtype.byte_size();
102 if esize > 1 && !byte_offset.is_multiple_of(esize) {
103 return Err(EpError::InvalidTensorView {
104 reason: format!("byte_offset {byte_offset} is not a multiple of element size {esize}"),
105 });
106 }
107 Ok(())
108}
109
110pub struct TensorView<'a> {
112 pub data: DevicePtr,
113 pub dtype: DataType,
114 pub shape: &'a [usize],
115 pub strides: &'a [i64],
117 pub byte_offset: usize,
119 pub device: DeviceId,
120 _marker: PhantomData<&'a ()>,
121}
122
123impl<'a> TensorView<'a> {
124 pub fn new(
126 data: DevicePtr,
127 dtype: DataType,
128 shape: &'a [usize],
129 strides: &'a [i64],
130 device: DeviceId,
131 ) -> Self {
132 Self {
133 data,
134 dtype,
135 shape,
136 strides,
137 byte_offset: 0,
138 device,
139 _marker: PhantomData,
140 }
141 }
142
143 pub fn absent(dtype: DataType) -> Self {
151 Self {
152 data: DevicePtr(std::ptr::null()),
153 dtype,
154 shape: &[],
155 strides: &[],
156 byte_offset: 0,
157 device: DeviceId::cpu(),
158 _marker: PhantomData,
159 }
160 }
161
162 pub fn is_absent(&self) -> bool {
167 self.data.is_null()
168 }
169
170 pub fn with_byte_offset(mut self, byte_offset: usize) -> Self {
172 self.byte_offset = byte_offset;
173 self
174 }
175
176 pub fn validate(&self) -> Result<()> {
179 validate_view(
180 self.data.is_null(),
181 self.dtype,
182 self.shape,
183 self.strides,
184 self.byte_offset,
185 )
186 }
187
188 pub fn is_contiguous(&self) -> bool {
190 onnx_runtime_ir::is_contiguous(self.shape, self.strides)
191 }
192
193 pub fn numel(&self) -> usize {
195 self.shape.iter().product()
196 }
197
198 pub fn byte_size(&self) -> usize {
201 self.dtype.storage_bytes(self.numel())
202 }
203
204 pub fn data_ptr<T>(&self) -> *const T {
207 (self.data.0 as *const u8).wrapping_add(self.byte_offset) as *const T
208 }
209}
210
211pub struct TensorMut<'a> {
213 pub data: DevicePtrMut,
214 pub dtype: DataType,
215 pub shape: &'a [usize],
216 pub strides: &'a [i64],
218 pub byte_offset: usize,
220 pub device: DeviceId,
221 _marker: PhantomData<&'a mut ()>,
222}
223
224impl<'a> TensorMut<'a> {
225 pub fn new(
228 data: DevicePtrMut,
229 dtype: DataType,
230 shape: &'a [usize],
231 strides: &'a [i64],
232 device: DeviceId,
233 ) -> Self {
234 Self {
235 data,
236 dtype,
237 shape,
238 strides,
239 byte_offset: 0,
240 device,
241 _marker: PhantomData,
242 }
243 }
244
245 pub fn with_byte_offset(mut self, byte_offset: usize) -> Self {
247 self.byte_offset = byte_offset;
248 self
249 }
250
251 pub fn validate(&self) -> Result<()> {
253 validate_view(
254 self.data.is_null(),
255 self.dtype,
256 self.shape,
257 self.strides,
258 self.byte_offset,
259 )
260 }
261
262 pub fn is_contiguous(&self) -> bool {
264 onnx_runtime_ir::is_contiguous(self.shape, self.strides)
265 }
266
267 pub fn numel(&self) -> usize {
269 self.shape.iter().product()
270 }
271
272 pub fn byte_size(&self) -> usize {
275 self.dtype.storage_bytes(self.numel())
276 }
277
278 pub fn data_ptr_mut<T>(&mut self) -> *mut T {
281 (self.data.0 as *mut u8).wrapping_add(self.byte_offset) as *mut T
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use onnx_runtime_ir::compute_contiguous_strides;
289
290 fn ptr(buf: &[u8]) -> DevicePtr {
291 DevicePtr(buf.as_ptr() as *const std::ffi::c_void)
292 }
293
294 #[test]
295 fn contiguous_view_roundtrips_invariants() {
296 let buf = vec![0u8; 6 * 4];
297 let shape = [2usize, 3];
298 let strides = compute_contiguous_strides(&shape);
299 let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu());
300 v.validate().unwrap();
301 assert!(v.is_contiguous());
302 assert_eq!(v.numel(), 6);
303 assert_eq!(v.byte_size(), 24);
304 assert_eq!(v.byte_offset, 0);
305 }
306
307 #[test]
308 fn strided_noncontiguous_view_is_representable() {
309 let buf = vec![0u8; 6 * 4];
311 let shape = [3usize, 2];
312 let strides = [1i64, 3];
313 let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu())
314 .with_byte_offset(4);
315 v.validate().unwrap();
316 assert!(!v.is_contiguous());
317 assert_eq!(v.shape, &[3, 2]);
318 assert_eq!(v.strides, &[1, 3]);
319 assert_eq!(v.byte_offset, 4);
320 let base = buf.as_ptr() as usize;
322 assert_eq!(v.data_ptr::<f32>() as usize, base + 4);
323 }
324
325 #[test]
326 fn negative_strides_are_representable() {
327 let buf = vec![0u8; 4 * 4];
329 let shape = [4usize];
330 let strides = [-1i64];
331 let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu());
332 v.validate().unwrap();
333 assert!(!v.is_contiguous());
334 }
335
336 #[test]
337 fn validate_rejects_rank_mismatch() {
338 let buf = vec![0u8; 8];
339 let shape = [2usize, 2];
340 let strides = [1i64]; let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu());
342 assert!(v.validate().is_err());
343 }
344
345 #[test]
346 fn validate_rejects_misaligned_offset_and_string_and_null() {
347 let buf = vec![0u8; 16];
348 let shape = [2usize];
349 let strides = [1i64];
350 let bad_off =
352 TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu())
353 .with_byte_offset(1);
354 assert!(bad_off.validate().is_err());
355 let bad_dt =
357 TensorView::new(ptr(&buf), DataType::String, &shape, &strides, DeviceId::cpu());
358 assert!(bad_dt.validate().is_err());
359 let bad_null = TensorView::new(
361 DevicePtr(std::ptr::null()),
362 DataType::Float32,
363 &shape,
364 &strides,
365 DeviceId::cpu(),
366 );
367 assert!(bad_null.validate().is_err());
368 }
369
370 #[test]
371 fn mut_view_offset_pointer() {
372 let mut buf = vec![0u8; 4 * 4];
373 let base = buf.as_ptr() as usize;
374 let shape = [2usize];
375 let strides = [1i64];
376 let mut v = TensorMut::new(
377 DevicePtrMut(buf.as_mut_ptr() as *mut std::ffi::c_void),
378 DataType::Float32,
379 &shape,
380 &strides,
381 DeviceId::cpu(),
382 )
383 .with_byte_offset(8);
384 v.validate().unwrap();
385 assert_eq!(v.data_ptr_mut::<f32>() as usize, base + 8);
386 }
387
388 #[test]
389 fn sub_byte_byte_size_uses_packing() {
390 let buf = vec![0u8; 4];
391 let shape = [5usize];
392 let strides = [1i64];
393 let v = TensorView::new(ptr(&buf), DataType::Int4, &shape, &strides, DeviceId::cpu());
394 assert_eq!(v.byte_size(), 3);
396 v.validate().unwrap();
397 }
398}