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 with_byte_offset(mut self, byte_offset: usize) -> Self {
145 self.byte_offset = byte_offset;
146 self
147 }
148
149 pub fn validate(&self) -> Result<()> {
152 validate_view(
153 self.data.is_null(),
154 self.dtype,
155 self.shape,
156 self.strides,
157 self.byte_offset,
158 )
159 }
160
161 pub fn is_contiguous(&self) -> bool {
163 onnx_runtime_ir::is_contiguous(self.shape, self.strides)
164 }
165
166 pub fn numel(&self) -> usize {
168 self.shape.iter().product()
169 }
170
171 pub fn byte_size(&self) -> usize {
174 self.dtype.storage_bytes(self.numel())
175 }
176
177 pub fn data_ptr<T>(&self) -> *const T {
180 (self.data.0 as *const u8).wrapping_add(self.byte_offset) as *const T
181 }
182}
183
184pub struct TensorMut<'a> {
186 pub data: DevicePtrMut,
187 pub dtype: DataType,
188 pub shape: &'a [usize],
189 pub strides: &'a [i64],
191 pub byte_offset: usize,
193 pub device: DeviceId,
194 _marker: PhantomData<&'a mut ()>,
195}
196
197impl<'a> TensorMut<'a> {
198 pub fn new(
201 data: DevicePtrMut,
202 dtype: DataType,
203 shape: &'a [usize],
204 strides: &'a [i64],
205 device: DeviceId,
206 ) -> Self {
207 Self {
208 data,
209 dtype,
210 shape,
211 strides,
212 byte_offset: 0,
213 device,
214 _marker: PhantomData,
215 }
216 }
217
218 pub fn with_byte_offset(mut self, byte_offset: usize) -> Self {
220 self.byte_offset = byte_offset;
221 self
222 }
223
224 pub fn validate(&self) -> Result<()> {
226 validate_view(
227 self.data.is_null(),
228 self.dtype,
229 self.shape,
230 self.strides,
231 self.byte_offset,
232 )
233 }
234
235 pub fn is_contiguous(&self) -> bool {
237 onnx_runtime_ir::is_contiguous(self.shape, self.strides)
238 }
239
240 pub fn numel(&self) -> usize {
242 self.shape.iter().product()
243 }
244
245 pub fn byte_size(&self) -> usize {
248 self.dtype.storage_bytes(self.numel())
249 }
250
251 pub fn data_ptr_mut<T>(&mut self) -> *mut T {
254 (self.data.0 as *mut u8).wrapping_add(self.byte_offset) as *mut T
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use onnx_runtime_ir::compute_contiguous_strides;
262
263 fn ptr(buf: &[u8]) -> DevicePtr {
264 DevicePtr(buf.as_ptr() as *const std::ffi::c_void)
265 }
266
267 #[test]
268 fn contiguous_view_roundtrips_invariants() {
269 let buf = vec![0u8; 6 * 4];
270 let shape = [2usize, 3];
271 let strides = compute_contiguous_strides(&shape);
272 let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu());
273 v.validate().unwrap();
274 assert!(v.is_contiguous());
275 assert_eq!(v.numel(), 6);
276 assert_eq!(v.byte_size(), 24);
277 assert_eq!(v.byte_offset, 0);
278 }
279
280 #[test]
281 fn strided_noncontiguous_view_is_representable() {
282 let buf = vec![0u8; 6 * 4];
284 let shape = [3usize, 2];
285 let strides = [1i64, 3];
286 let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu())
287 .with_byte_offset(4);
288 v.validate().unwrap();
289 assert!(!v.is_contiguous());
290 assert_eq!(v.shape, &[3, 2]);
291 assert_eq!(v.strides, &[1, 3]);
292 assert_eq!(v.byte_offset, 4);
293 let base = buf.as_ptr() as usize;
295 assert_eq!(v.data_ptr::<f32>() as usize, base + 4);
296 }
297
298 #[test]
299 fn negative_strides_are_representable() {
300 let buf = vec![0u8; 4 * 4];
302 let shape = [4usize];
303 let strides = [-1i64];
304 let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu());
305 v.validate().unwrap();
306 assert!(!v.is_contiguous());
307 }
308
309 #[test]
310 fn validate_rejects_rank_mismatch() {
311 let buf = vec![0u8; 8];
312 let shape = [2usize, 2];
313 let strides = [1i64]; let v = TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu());
315 assert!(v.validate().is_err());
316 }
317
318 #[test]
319 fn validate_rejects_misaligned_offset_and_string_and_null() {
320 let buf = vec![0u8; 16];
321 let shape = [2usize];
322 let strides = [1i64];
323 let bad_off =
325 TensorView::new(ptr(&buf), DataType::Float32, &shape, &strides, DeviceId::cpu())
326 .with_byte_offset(1);
327 assert!(bad_off.validate().is_err());
328 let bad_dt =
330 TensorView::new(ptr(&buf), DataType::String, &shape, &strides, DeviceId::cpu());
331 assert!(bad_dt.validate().is_err());
332 let bad_null = TensorView::new(
334 DevicePtr(std::ptr::null()),
335 DataType::Float32,
336 &shape,
337 &strides,
338 DeviceId::cpu(),
339 );
340 assert!(bad_null.validate().is_err());
341 }
342
343 #[test]
344 fn mut_view_offset_pointer() {
345 let mut buf = vec![0u8; 4 * 4];
346 let base = buf.as_ptr() as usize;
347 let shape = [2usize];
348 let strides = [1i64];
349 let mut v = TensorMut::new(
350 DevicePtrMut(buf.as_mut_ptr() as *mut std::ffi::c_void),
351 DataType::Float32,
352 &shape,
353 &strides,
354 DeviceId::cpu(),
355 )
356 .with_byte_offset(8);
357 v.validate().unwrap();
358 assert_eq!(v.data_ptr_mut::<f32>() as usize, base + 8);
359 }
360
361 #[test]
362 fn sub_byte_byte_size_uses_packing() {
363 let buf = vec![0u8; 4];
364 let shape = [5usize];
365 let strides = [1i64];
366 let v = TensorView::new(ptr(&buf), DataType::Int4, &shape, &strides, DeviceId::cpu());
367 assert_eq!(v.byte_size(), 3);
369 v.validate().unwrap();
370 }
371}