Skip to main content

strided_view/
raw.rs

1//! Borrowed raw strided layout types.
2//!
3//! These are the prepared-replay counterparts to [`crate::StridedView`] and
4//! [`crate::StridedViewMut`]. They borrow shape/stride metadata instead of
5//! owning it, so compiled kernels can reuse already-validated layout
6//! descriptors without rebuilding dynamic-rank view wrappers.
7
8use crate::element_op::Identity;
9use crate::view::validate_bounds;
10use core::marker::PhantomData;
11use core::mem::MaybeUninit;
12use core::ptr::NonNull;
13use num_complex::{Complex32, Complex64};
14
15mod private {
16    pub trait Sealed {}
17}
18
19/// A scalar type that can safely witness storage for an erased kernel view.
20pub trait KernelStorageElement: private::Sealed + Copy + 'static {
21    const DTYPE: KernelDType;
22}
23
24macro_rules! kernel_storage_element {
25    ($($ty:ty => $dtype:ident),* $(,)?) => {$ (
26        impl private::Sealed for $ty {}
27        impl KernelStorageElement for $ty {
28            const DTYPE: KernelDType = KernelDType::$dtype;
29        }
30    )* };
31}
32
33kernel_storage_element! {
34    f32 => F32,
35    f64 => F64,
36    i32 => I32,
37    i64 => I64,
38    bool => Bool,
39    Complex32 => C32,
40    Complex64 => C64,
41}
42
43use crate::{Result, StridedError, StridedView, StridedViewMut};
44
45/// Dtypes supported by dtype-erased kernel entry points.
46///
47/// The enum is intentionally limited to the scalar set currently used by the
48/// tensor runtime callers. Later FFI layers should map their ABI dtype tags to
49/// this enum before dispatching into prepared kernels.
50#[non_exhaustive]
51#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
52#[repr(u8)]
53pub enum KernelDType {
54    F32 = 1,
55    F64 = 2,
56    I32 = 3,
57    I64 = 4,
58    Bool = 5,
59    C32 = 6,
60    C64 = 7,
61}
62
63impl KernelDType {
64    #[inline]
65    pub const fn label(self) -> &'static str {
66        match self {
67            Self::F32 => "f32",
68            Self::F64 => "f64",
69            Self::I32 => "i32",
70            Self::I64 => "i64",
71            Self::Bool => "bool",
72            Self::C32 => "c32",
73            Self::C64 => "c64",
74        }
75    }
76
77    #[inline]
78    pub const fn size_of(self) -> usize {
79        match self {
80            Self::F32 => core::mem::size_of::<f32>(),
81            Self::F64 => core::mem::size_of::<f64>(),
82            Self::I32 => core::mem::size_of::<i32>(),
83            Self::I64 => core::mem::size_of::<i64>(),
84            Self::Bool => core::mem::size_of::<bool>(),
85            Self::C32 => core::mem::size_of::<Complex32>(),
86            Self::C64 => core::mem::size_of::<Complex64>(),
87        }
88    }
89
90    #[inline]
91    pub const fn alignment(self) -> usize {
92        match self {
93            Self::F32 => core::mem::align_of::<f32>(),
94            Self::F64 => core::mem::align_of::<f64>(),
95            Self::I32 => core::mem::align_of::<i32>(),
96            Self::I64 => core::mem::align_of::<i64>(),
97            Self::Bool => core::mem::align_of::<bool>(),
98            Self::C32 => core::mem::align_of::<Complex32>(),
99            Self::C64 => core::mem::align_of::<Complex64>(),
100        }
101    }
102
103    #[inline]
104    pub const fn requires_valid_byte_values(self) -> bool {
105        matches!(self, Self::Bool)
106    }
107}
108
109fn validate_erased_buffer(dtype: KernelDType, data: &[u8]) -> Result<usize> {
110    let element_count = validate_erased_buffer_extent(dtype, data)?;
111    if element_count != 0 && dtype.requires_valid_byte_values() {
112        validate_bool_bytes(data)?;
113    }
114    Ok(element_count)
115}
116
117/// Validate the length and alignment of erased storage, without reading it.
118///
119/// Typed constructors call this alone: `KernelStorageElement` is sealed, so a
120/// `&[T]` already holds valid values of `T::DTYPE`.
121fn validate_erased_buffer_extent(dtype: KernelDType, data: &[u8]) -> Result<usize> {
122    let element_size = dtype.size_of();
123    if data.len() % element_size != 0 {
124        return Err(StridedError::ByteLengthMismatch {
125            dtype: dtype.label(),
126            byte_len: data.len(),
127            element_size,
128        });
129    }
130
131    let element_count = data.len() / element_size;
132    if element_count == 0 {
133        return Ok(0);
134    }
135
136    let alignment = dtype.alignment();
137    if data.as_ptr() as usize % alignment != 0 {
138        return Err(StridedError::DataAlignmentMismatch {
139            dtype: dtype.label(),
140            alignment,
141        });
142    }
143    Ok(element_count)
144}
145
146/// Reject any byte other than 0 or 1.
147///
148/// The OR fold has no early exit, so it vectorizes and runs at memory
149/// bandwidth; a byte by byte `find` does not, and cost more than the select
150/// kernel it guarded. The slow scan runs only to report the offending byte.
151fn validate_bool_bytes(data: &[u8]) -> Result<()> {
152    if data.iter().fold(0u8, |acc, &value| acc | value) <= 1 {
153        return Ok(());
154    }
155    match data.iter().find(|&&value| value > 1) {
156        Some(&value) => Err(StridedError::InvalidBoolByte { value }),
157        None => Ok(()),
158    }
159}
160
161fn validate_erased_buffer_layout(
162    dtype: KernelDType,
163    data: NonNull<u8>,
164    byte_len: usize,
165) -> Result<usize> {
166    let element_size = dtype.size_of();
167    if byte_len % element_size != 0 {
168        return Err(StridedError::ByteLengthMismatch {
169            dtype: dtype.label(),
170            byte_len,
171            element_size,
172        });
173    }
174    if byte_len != 0 && data.as_ptr() as usize % dtype.alignment() != 0 {
175        return Err(StridedError::DataAlignmentMismatch {
176            dtype: dtype.label(),
177            alignment: dtype.alignment(),
178        });
179    }
180    Ok(byte_len / element_size)
181}
182
183/// Pointer-backed dtype-erased input used by one-shot write entry points.
184///
185/// Unlike [`ErasedRawStridedRef`], this descriptor does not create a shared
186/// Rust reference at construction time. That lets the entry point reject an
187/// input/output overlap before forming references to either side.
188#[derive(Clone, Copy, Debug)]
189pub struct ErasedRawStridedPtr<'a> {
190    dtype: KernelDType,
191    data: NonNull<u8>,
192    byte_len: usize,
193    dims: &'a [usize],
194    strides: &'a [isize],
195    offset: isize,
196    marker: PhantomData<&'a [MaybeUninit<u8>]>,
197}
198
199impl<'a> ErasedRawStridedPtr<'a> {
200    /// Create a pointer-backed descriptor from erased storage.
201    ///
202    /// # Safety
203    ///
204    /// `data` must point into an allocation whose alignment is suitable for
205    /// `dtype`, independently of the observed address. The allocation must
206    /// provide `byte_len` initialized, readable bytes with valid provenance
207    /// for `'a`, and remain alive for `'a`. For `bool`, the bytes may contain
208    /// invalid values temporarily. They must not be read or used to form a
209    /// typed reference until a caller has rejected overlap and
210    /// [`Self::try_as_ref_after_no_overlap`] has validated the complete extent.
211    /// The allocation may overlap a destination.
212    /// The original owner may perform synchronized sequential mutation through
213    /// the same-provenance raw pointer before conversion. No concurrent
214    /// mutation or conflicting access is permitted during overlap checking,
215    /// conversion, or consumer access.
216    ///
217    /// # Errors
218    ///
219    /// Returns [`StridedError::ByteLengthMismatch`] when `byte_len` is not
220    /// a whole number of `dtype` elements, and
221    /// [`StridedError::DataAlignmentMismatch`] when a non-empty `data` is
222    /// not aligned for `dtype`. Returns
223    /// [`StridedError::StrideLengthMismatch`] when `dims` and `strides`
224    /// have different lengths, and [`StridedError::OffsetOverflow`] when
225    /// a reachable offset is not representable: `dim - 1` of a non-unit
226    /// extent does not fit `isize`, a stride times that last index or the
227    /// accumulated offsets overflow `isize` or go negative, or the highest
228    /// reachable offset is not below the storage length. A layout with a zero
229    /// extent skips this check.
230    /// Bool bytes are not checked here;
231    /// [`Self::try_as_ref_after_no_overlap`] does that.
232    pub unsafe fn from_raw_parts(
233        dtype: KernelDType,
234        data: NonNull<u8>,
235        byte_len: usize,
236        dims: &'a [usize],
237        strides: &'a [isize],
238        offset: isize,
239    ) -> Result<Self> {
240        let element_count = validate_erased_buffer_layout(dtype, data, byte_len)?;
241        validate_bounds(element_count, dims, strides, offset)?;
242        Ok(Self {
243            dtype,
244            data,
245            byte_len,
246            dims,
247            strides,
248            offset,
249            marker: PhantomData,
250        })
251    }
252
253    /// Borrow a safe erased input as a pointer descriptor.
254    pub fn from_ref(input: &ErasedRawStridedRef<'a>) -> Self {
255        Self {
256            dtype: input.dtype,
257            data: NonNull::new(input.data.as_ptr().cast_mut()).unwrap_or_else(NonNull::dangling),
258            byte_len: input.data.len(),
259            dims: input.dims,
260            strides: input.strides,
261            offset: input.offset,
262            marker: PhantomData,
263        }
264    }
265
266    /// Convert this pointer to an initialized descriptor after overlap checks.
267    ///
268    /// # Safety
269    ///
270    /// The caller must have proved that no mutable allocation overlaps this
271    /// pointer's complete byte extent.
272    /// The allocation contract from [`Self::from_raw_parts`] must still hold.
273    /// No mutation or other conflicting access may occur during this conversion
274    /// or for the duration of the returned descriptor's consumer access.
275    /// Bool bytes are validated here, immediately before typed access.
276    ///
277    /// # Errors
278    ///
279    /// Returns [`StridedError::InvalidBoolByte`] when `dtype` is `Bool` and
280    /// a byte of the extent is neither 0 nor 1. It repeats the extent
281    /// length, alignment, stride length and bounds checks
282    /// ([`StridedError::ByteLengthMismatch`],
283    /// [`StridedError::DataAlignmentMismatch`],
284    /// [`StridedError::StrideLengthMismatch`],
285    /// [`StridedError::OffsetOverflow`]) that [`Self::from_raw_parts`] and
286    /// [`Self::from_ref`] already applied to the same values, so those
287    /// cannot fire again here.
288    pub unsafe fn try_as_ref_after_no_overlap(&self) -> Result<ErasedRawStridedRef<'_>> {
289        let bytes = core::slice::from_raw_parts(self.data.as_ptr(), self.byte_len);
290        validate_erased_buffer(self.dtype, bytes)?;
291        ErasedRawStridedRef::from_raw_parts(
292            self.dtype,
293            self.data,
294            self.byte_len,
295            self.dims,
296            self.strides,
297            self.offset,
298        )
299    }
300
301    #[inline]
302    pub fn dtype(&self) -> KernelDType {
303        self.dtype
304    }
305
306    /// Check overlap with initialized mutable storage without reading it.
307    ///
308    /// # Errors
309    ///
310    /// Never returns `Err` in practice: both byte extents come from valid
311    /// descriptors, whose contracts already guarantee an allocation of that
312    /// size, so the end-address arithmetic cannot wrap. The `Result` only
313    /// carries that arithmetic's [`StridedError::OffsetOverflow`].
314    pub fn overlaps_mut(&self, dest: &ErasedRawStridedMut<'_>) -> Result<bool> {
315        ranges_overlap(
316            self.data.as_ptr() as usize,
317            self.byte_len,
318            dest.data.as_ptr() as usize,
319            dest.data.len(),
320        )
321    }
322
323    /// Check overlap with uninitialized mutable storage without reading it.
324    ///
325    /// # Errors
326    ///
327    /// Never returns `Err` in practice: both byte extents come from valid
328    /// descriptors, whose contracts already guarantee an allocation of that
329    /// size, so the end-address arithmetic cannot wrap. The `Result` only
330    /// carries that arithmetic's [`StridedError::OffsetOverflow`].
331    pub fn overlaps_uninit_mut(&self, dest: &ErasedRawStridedUninitMut<'_>) -> Result<bool> {
332        ranges_overlap(
333            self.data.as_ptr() as usize,
334            self.byte_len,
335            dest.data.as_ptr() as usize,
336            dest.data.len(),
337        )
338    }
339
340    #[inline]
341    pub fn dims(&self) -> &'a [usize] {
342        self.dims
343    }
344
345    #[inline]
346    pub fn strides(&self) -> &'a [isize] {
347        self.strides
348    }
349
350    #[inline]
351    pub fn offset(&self) -> isize {
352        self.offset
353    }
354}
355
356/// Borrowed dtype-erased raw strided input layout.
357///
358/// `dims`, `strides`, and `offset` are expressed in dtype elements, not bytes.
359#[derive(Clone, Copy)]
360pub struct ErasedRawStridedRef<'a> {
361    dtype: KernelDType,
362    data: &'a [u8],
363    dims: &'a [usize],
364    strides: &'a [isize],
365    offset: isize,
366}
367
368// A derived `Debug` would print every byte of the backing buffer; the layout
369// metadata is what a reader wants.
370impl core::fmt::Debug for ErasedRawStridedRef<'_> {
371    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
372        f.debug_struct("ErasedRawStridedRef")
373            .field("dtype", &self.dtype)
374            .field("byte_len", &self.data.len())
375            .field("dims", &self.dims)
376            .field("strides", &self.strides)
377            .field("offset", &self.offset)
378            .finish()
379    }
380}
381
382impl<'a> ErasedRawStridedRef<'a> {
383    /// Create an erased input descriptor from typed, initialized storage.
384    ///
385    /// # Errors
386    ///
387    /// Returns [`StridedError::StrideLengthMismatch`] when `dims` and
388    /// `strides` have different lengths, and
389    /// [`StridedError::OffsetOverflow`] when a reachable offset is not
390    /// representable: `dim - 1` of a non-unit extent does not fit `isize`, a
391    /// stride times that last index or the accumulated offsets overflow
392    /// `isize` or go negative, or the highest reachable offset is not below
393    /// the buffer length. A layout with a zero extent skips this check. The
394    /// byte-length and alignment checks
395    /// cannot fail for a typed `&[T]`.
396    pub fn from_slice<T: KernelStorageElement>(
397        data: &'a [T],
398        dims: &'a [usize],
399        strides: &'a [isize],
400        offset: isize,
401    ) -> Result<Self> {
402        let dtype = T::DTYPE;
403        let byte_len = data
404            .len()
405            .checked_mul(core::mem::size_of::<T>())
406            .ok_or(StridedError::OffsetOverflow)?;
407        // SAFETY: `data` is a live `&[T]` and `byte_len` is
408        // `data.len() * size_of::<T>()` from the `checked_mul` above, so the
409        // `u8` view spans exactly that initialized allocation for `data`'s
410        // lifetime. `u8` has no invalid bit patterns, and the resulting length
411        // is re-checked by `validate_erased_buffer_extent` (raw.rs:121).
412        let bytes = unsafe { core::slice::from_raw_parts(data.as_ptr().cast::<u8>(), byte_len) };
413        let element_count = validate_erased_buffer_extent(dtype, bytes)?;
414        validate_bounds(element_count, dims, strides, offset)?;
415        Ok(Self {
416            dtype,
417            data: bytes,
418            dims,
419            strides,
420            offset,
421        })
422    }
423
424    /// Construct from erased storage.
425    ///
426    /// # Safety
427    /// `data` must be the start of an allocation aligned for `dtype`; the
428    /// allocation must contain `byte_len` initialized, readable bytes for
429    /// `'a`, and every byte extent must represent valid values for `dtype`.
430    /// The allocation and metadata must outlive `'a`; no mutable alias may
431    /// exist while this descriptor is used. Alignment is an allocation
432    /// property and must not be inferred from the observed address alone.
433    ///
434    /// # Errors
435    ///
436    /// Returns [`StridedError::ByteLengthMismatch`] when `byte_len` is not
437    /// a whole number of `dtype` elements, and
438    /// [`StridedError::DataAlignmentMismatch`] when a non-empty `data` is
439    /// not aligned for `dtype`. Returns
440    /// [`StridedError::StrideLengthMismatch`] when `dims` and `strides`
441    /// have different lengths, and [`StridedError::OffsetOverflow`] when
442    /// a reachable offset is not representable: `dim - 1` of a non-unit
443    /// extent does not fit `isize`, a stride times that last index or the
444    /// accumulated offsets overflow `isize` or go negative, or the highest
445    /// reachable offset is not below the storage length. A layout with a zero
446    /// extent skips this check.
447    /// Bool bytes are the caller's responsibility here, per the safety
448    /// contract.
449    pub unsafe fn from_raw_parts(
450        dtype: KernelDType,
451        data: NonNull<u8>,
452        byte_len: usize,
453        dims: &'a [usize],
454        strides: &'a [isize],
455        offset: isize,
456    ) -> Result<Self> {
457        let count = validate_erased_buffer_layout(dtype, data, byte_len)?;
458        let bytes = core::slice::from_raw_parts(data.as_ptr(), byte_len);
459        validate_bounds(count, dims, strides, offset)?;
460        Ok(Self {
461            dtype,
462            data: bytes,
463            dims,
464            strides,
465            offset,
466        })
467    }
468
469    /// Borrow the initialized storage as its concrete scalar type.
470    ///
471    /// # Errors
472    ///
473    /// Returns [`StridedError::DTypeMismatch`] when `T` is not the
474    /// descriptor's dtype.
475    pub fn data_as<T: KernelStorageElement>(&self) -> Result<&[T]> {
476        if self.dtype != T::DTYPE {
477            return Err(StridedError::DTypeMismatch {
478                expected: T::DTYPE.label(),
479                actual: self.dtype.label(),
480            });
481        }
482        // SAFETY: the dtype check above proves the stored dtype is `T`, and the
483        // constructor ran `validate_erased_buffer_extent` (raw.rs:121) /
484        // `validate_erased_buffer_layout` (raw.rs:161): the byte length is a
485        // whole multiple of `size_of::<T>()` and the base is `T`-aligned, so
486        // `len / size_of::<T>()` initialized `T` values are in bounds.
487        Ok(unsafe {
488            core::slice::from_raw_parts(
489                self.data.as_ptr().cast::<T>(),
490                self.data.len() / core::mem::size_of::<T>(),
491            )
492        })
493    }
494
495    #[inline]
496    pub fn dtype(&self) -> KernelDType {
497        self.dtype
498    }
499
500    #[inline]
501    pub fn dims(&self) -> &'a [usize] {
502        self.dims
503    }
504
505    #[inline]
506    pub fn strides(&self) -> &'a [isize] {
507        self.strides
508    }
509
510    #[inline]
511    pub fn offset(&self) -> isize {
512        self.offset
513    }
514}
515
516/// Borrowed dtype-erased raw strided output layout.
517///
518/// `dims`, `strides`, and `offset` are expressed in dtype elements, not bytes.
519pub struct ErasedRawStridedMut<'a> {
520    dtype: KernelDType,
521    data: &'a mut [u8],
522    dims: &'a [usize],
523    strides: &'a [isize],
524    offset: isize,
525}
526
527// A derived `Debug` would print every byte of the backing buffer; the layout
528// metadata is what a reader wants.
529impl core::fmt::Debug for ErasedRawStridedMut<'_> {
530    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
531        f.debug_struct("ErasedRawStridedMut")
532            .field("dtype", &self.dtype)
533            .field("byte_len", &self.data.len())
534            .field("dims", &self.dims)
535            .field("strides", &self.strides)
536            .field("offset", &self.offset)
537            .finish()
538    }
539}
540
541impl<'a> ErasedRawStridedMut<'a> {
542    /// Create an erased output descriptor from typed, initialized storage.
543    ///
544    /// # Errors
545    ///
546    /// Returns [`StridedError::StrideLengthMismatch`] when `dims` and
547    /// `strides` have different lengths, and
548    /// [`StridedError::OffsetOverflow`] when a reachable offset is not
549    /// representable: `dim - 1` of a non-unit extent does not fit `isize`, a
550    /// stride times that last index or the accumulated offsets overflow
551    /// `isize` or go negative, or the highest reachable offset is not below
552    /// the buffer length. A layout with a zero extent skips this check. The
553    /// byte-length and alignment checks
554    /// cannot fail for a typed `&mut [T]`.
555    pub fn from_slice_mut<T: KernelStorageElement>(
556        data: &'a mut [T],
557        dims: &'a [usize],
558        strides: &'a [isize],
559        offset: isize,
560    ) -> Result<Self> {
561        let dtype = T::DTYPE;
562        let byte_len = data
563            .len()
564            .checked_mul(core::mem::size_of::<T>())
565            .ok_or(StridedError::OffsetOverflow)?;
566        // SAFETY: `data` is a live, exclusively borrowed `&mut [T]` and
567        // `byte_len` is `data.len() * size_of::<T>()` from the `checked_mul`
568        // above, so the `u8` view spans exactly that initialized allocation for
569        // `'a`. `u8` has no invalid bit patterns, and the resulting length is
570        // re-checked by `validate_erased_buffer_extent` (raw.rs:121).
571        let data =
572            unsafe { core::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<u8>(), byte_len) };
573        let element_count = validate_erased_buffer_extent(dtype, data)?;
574        validate_bounds(element_count, dims, strides, offset)?;
575        Ok(Self {
576            dtype,
577            data,
578            dims,
579            strides,
580            offset,
581        })
582    }
583
584    /// Construct from erased storage.
585    ///
586    /// # Safety
587    /// `data..data + byte_len` must be an aligned, writable allocation valid
588    /// for `'a`, with valid initialized values for `dtype` throughout the
589    /// complete byte extent; its provenance, extent, lifetime, and exclusive
590    /// aliasing must be upheld by the caller.
591    /// The alignment requirement is on the allocation, not merely the observed
592    /// address. The caller must retain exclusive access: no mutable alias or
593    /// concurrent mutation may exist while this descriptor is used.
594    ///
595    /// # Errors
596    ///
597    /// Returns [`StridedError::ByteLengthMismatch`] when `byte_len` is not
598    /// a whole number of `dtype` elements, and
599    /// [`StridedError::DataAlignmentMismatch`] when a non-empty `data` is
600    /// not aligned for `dtype`. Returns
601    /// [`StridedError::InvalidBoolByte`] when `dtype` is `Bool` and a byte
602    /// of the extent is neither 0 nor 1. Returns
603    /// [`StridedError::StrideLengthMismatch`] when `dims` and `strides`
604    /// have different lengths, and [`StridedError::OffsetOverflow`] when
605    /// a reachable offset is not representable: `dim - 1` of a non-unit
606    /// extent does not fit `isize`, a stride times that last index or the
607    /// accumulated offsets overflow `isize` or go negative, or the highest
608    /// reachable offset is not below the storage length. A layout with a zero
609    /// extent skips this check.
610    pub unsafe fn from_raw_parts(
611        dtype: KernelDType,
612        data: NonNull<u8>,
613        byte_len: usize,
614        dims: &'a [usize],
615        strides: &'a [isize],
616        offset: isize,
617    ) -> Result<Self> {
618        let count = validate_erased_buffer_layout(dtype, data, byte_len)?;
619        let data = core::slice::from_raw_parts_mut(data.as_ptr(), byte_len);
620        validate_erased_buffer(dtype, data)?;
621        validate_bounds(count, dims, strides, offset)?;
622        Ok(Self {
623            dtype,
624            data,
625            dims,
626            strides,
627            offset,
628        })
629    }
630
631    /// Borrow initialized storage as its concrete scalar type.
632    ///
633    /// # Errors
634    ///
635    /// Returns [`StridedError::DTypeMismatch`] when `T` is not the
636    /// descriptor's dtype.
637    pub fn data_as<T: KernelStorageElement>(&self) -> Result<&[T]> {
638        if self.dtype != T::DTYPE {
639            return Err(StridedError::DTypeMismatch {
640                expected: T::DTYPE.label(),
641                actual: self.dtype.label(),
642            });
643        }
644        // SAFETY: the dtype check above proves the stored dtype is `T`, and the
645        // constructor ran `validate_erased_buffer_extent` (raw.rs:121) /
646        // `validate_erased_buffer_layout` (raw.rs:161): the byte length is a
647        // whole multiple of `size_of::<T>()` and the base is `T`-aligned, so
648        // `len / size_of::<T>()` initialized `T` values are in bounds.
649        Ok(unsafe {
650            core::slice::from_raw_parts(
651                self.data.as_ptr().cast::<T>(),
652                self.data.len() / core::mem::size_of::<T>(),
653            )
654        })
655    }
656
657    /// Borrow initialized storage mutably as its concrete scalar type.
658    ///
659    /// # Errors
660    ///
661    /// Returns [`StridedError::DTypeMismatch`] when `T` is not the
662    /// descriptor's dtype.
663    pub fn data_as_mut<T: KernelStorageElement>(&mut self) -> Result<&mut [T]> {
664        if self.dtype != T::DTYPE {
665            return Err(StridedError::DTypeMismatch {
666                expected: T::DTYPE.label(),
667                actual: self.dtype.label(),
668            });
669        }
670        // SAFETY: as `data_as` (raw.rs:637) — the dtype check proves `T`, and
671        // `validate_erased_buffer_layout` (raw.rs:161) proved the base is
672        // `T`-aligned with a whole multiple of `size_of::<T>()` bytes. `&mut
673        // self` keeps this reborrow exclusive for its own lifetime.
674        Ok(unsafe {
675            core::slice::from_raw_parts_mut(
676                self.data.as_mut_ptr().cast::<T>(),
677                self.data.len() / core::mem::size_of::<T>(),
678            )
679        })
680    }
681
682    #[inline]
683    pub fn dtype(&self) -> KernelDType {
684        self.dtype
685    }
686
687    #[inline]
688    pub fn dims(&self) -> &'a [usize] {
689        self.dims
690    }
691
692    #[inline]
693    pub fn strides(&self) -> &'a [isize] {
694        self.strides
695    }
696
697    #[inline]
698    pub fn offset(&self) -> isize {
699        self.offset
700    }
701}
702
703/// Borrowed dtype-erased raw strided output whose reachable elements may be
704/// uninitialized.
705///
706/// This descriptor is only accepted by operations that prove and perform a
707/// full overwrite of every reachable logical destination element. The backing
708/// allocation may contain non-reachable holes, which remain uninitialized.
709pub struct ErasedRawStridedUninitMut<'a> {
710    dtype: KernelDType,
711    data: &'a mut [MaybeUninit<u8>],
712    dims: &'a [usize],
713    strides: &'a [isize],
714    offset: isize,
715}
716
717// A derived `Debug` would print one entry per byte of the backing buffer; the
718// layout metadata is what a reader wants.
719impl core::fmt::Debug for ErasedRawStridedUninitMut<'_> {
720    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
721        f.debug_struct("ErasedRawStridedUninitMut")
722            .field("dtype", &self.dtype)
723            .field("byte_len", &self.data.len())
724            .field("dims", &self.dims)
725            .field("strides", &self.strides)
726            .field("offset", &self.offset)
727            .finish()
728    }
729}
730
731impl<'a> ErasedRawStridedUninitMut<'a> {
732    /// Create an uninitialized erased output descriptor from typed storage.
733    ///
734    /// # Errors
735    ///
736    /// Returns [`StridedError::StrideLengthMismatch`] when `dims` and
737    /// `strides` have different lengths, and
738    /// [`StridedError::OffsetOverflow`] when a reachable offset is not
739    /// representable: `dim - 1` of a non-unit extent does not fit `isize`, a
740    /// stride times that last index or the accumulated offsets overflow
741    /// `isize` or go negative, or the highest reachable offset is not below
742    /// the buffer length. A layout with a zero extent skips this check. The
743    /// byte-length and alignment checks
744    /// cannot fail for a typed `&mut [MaybeUninit<T>]`.
745    pub fn from_uninit_slice<T: KernelStorageElement>(
746        data: &'a mut [MaybeUninit<T>],
747        dims: &'a [usize],
748        strides: &'a [isize],
749        offset: isize,
750    ) -> Result<Self> {
751        let dtype = T::DTYPE;
752        let byte_len = data
753            .len()
754            .checked_mul(core::mem::size_of::<T>())
755            .ok_or(StridedError::OffsetOverflow)?;
756        // SAFETY: `data` is a live, exclusively borrowed `&mut
757        // [MaybeUninit<T>]` and `byte_len` is `data.len() * size_of::<T>()` from
758        // the `checked_mul` above, so the view spans exactly that allocation for
759        // `'a`. `MaybeUninit<u8>` accepts every bit pattern, so no
760        // initialization is asserted here.
761        let data = unsafe {
762            core::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<MaybeUninit<u8>>(), byte_len)
763        };
764        let data_ptr =
765            NonNull::new(data.as_mut_ptr().cast::<u8>()).unwrap_or_else(NonNull::dangling);
766        let element_count = validate_erased_buffer_layout(dtype, data_ptr, byte_len)?;
767        validate_bounds(element_count, dims, strides, offset)?;
768        Ok(Self {
769            dtype,
770            data,
771            dims,
772            strides,
773            offset,
774        })
775    }
776
777    /// Construct from erased uninitialized storage.
778    ///
779    /// # Safety
780    /// `data..data + byte_len` must be an aligned, writable allocation valid
781    /// for `'a`, with provenance and extent sufficient for all reachable
782    /// elements. The caller must ensure every reachable element is completely
783    /// overwritten before it is read or exposed as initialized storage. The
784    /// allocation's alignment is an allocation property independent of the
785    /// observed address, and the descriptor must have exclusive access with no
786    /// mutable alias or concurrent mutation during use.
787    ///
788    /// # Errors
789    ///
790    /// Returns [`StridedError::ByteLengthMismatch`] when `byte_len` is not
791    /// a whole number of `dtype` elements, and
792    /// [`StridedError::DataAlignmentMismatch`] when a non-empty `data` is
793    /// not aligned for `dtype`. Returns
794    /// [`StridedError::StrideLengthMismatch`] when `dims` and `strides`
795    /// have different lengths, and [`StridedError::OffsetOverflow`] when
796    /// a reachable offset is not representable: `dim - 1` of a non-unit
797    /// extent does not fit `isize`, a stride times that last index or the
798    /// accumulated offsets overflow `isize` or go negative, or the highest
799    /// reachable offset is not below the storage length. A layout with a zero
800    /// extent skips this check.
801    pub unsafe fn from_raw_parts(
802        dtype: KernelDType,
803        data: NonNull<u8>,
804        byte_len: usize,
805        dims: &'a [usize],
806        strides: &'a [isize],
807        offset: isize,
808    ) -> Result<Self> {
809        let count = validate_erased_buffer_layout(dtype, data, byte_len)?;
810        let data =
811            core::slice::from_raw_parts_mut(data.as_ptr().cast::<MaybeUninit<u8>>(), byte_len);
812        validate_bounds(count, dims, strides, offset)?;
813        Ok(Self {
814            dtype,
815            data,
816            dims,
817            strides,
818            offset,
819        })
820    }
821
822    /// Borrow the uninitialized storage as its concrete scalar type.
823    ///
824    /// # Errors
825    ///
826    /// Returns [`StridedError::DTypeMismatch`] when `T` is not the
827    /// descriptor's dtype.
828    pub fn data_as_uninit_mut<T: KernelStorageElement>(&mut self) -> Result<&mut [MaybeUninit<T>]> {
829        if self.dtype != T::DTYPE {
830            return Err(StridedError::DTypeMismatch {
831                expected: T::DTYPE.label(),
832                actual: self.dtype.label(),
833            });
834        }
835        // SAFETY: the dtype check proves `T`, and
836        // `validate_erased_buffer_layout` (raw.rs:161) proved the base is
837        // `T`-aligned with a whole multiple of `size_of::<T>()` bytes.
838        // `MaybeUninit<T>` accepts every bit pattern, so the partially
839        // initialized extent is not read as `T` here.
840        Ok(unsafe {
841            core::slice::from_raw_parts_mut(
842                self.data.as_mut_ptr().cast::<MaybeUninit<T>>(),
843                self.data.len() / core::mem::size_of::<T>(),
844            )
845        })
846    }
847
848    #[inline]
849    pub fn dtype(&self) -> KernelDType {
850        self.dtype
851    }
852
853    #[inline]
854    pub fn dims(&self) -> &'a [usize] {
855        self.dims
856    }
857
858    #[inline]
859    pub fn strides(&self) -> &'a [isize] {
860        self.strides
861    }
862
863    #[inline]
864    pub fn offset(&self) -> isize {
865        self.offset
866    }
867}
868
869fn ranges_overlap(a_start: usize, a_len: usize, b_start: usize, b_len: usize) -> Result<bool> {
870    let a_end = a_start
871        .checked_add(a_len)
872        .ok_or(StridedError::OffsetOverflow)?;
873    let b_end = b_start
874        .checked_add(b_len)
875        .ok_or(StridedError::OffsetOverflow)?;
876    Ok(a_start < b_end && b_start < a_end)
877}
878
879/// Borrowed raw strided input layout.
880///
881/// Use [`RawStridedRef::new`] for checked construction, or
882/// [`RawStridedRef::new_unchecked`] when a higher-level compiled plan has
883/// already validated bounds.
884#[derive(Clone, Copy)]
885pub struct RawStridedRef<'a, T> {
886    data: &'a [T],
887    dims: &'a [usize],
888    strides: &'a [isize],
889    offset: isize,
890}
891
892// A derived `Debug` would print every element of the backing buffer; the layout
893// metadata is what a reader wants.
894impl<T: core::fmt::Debug> core::fmt::Debug for RawStridedRef<'_, T> {
895    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
896        f.debug_struct("RawStridedRef")
897            .field("data_len", &self.data.len())
898            .field("dims", &self.dims)
899            .field("strides", &self.strides)
900            .field("offset", &self.offset)
901            .finish()
902    }
903}
904
905impl<'a, T> RawStridedRef<'a, T> {
906    /// Create a raw strided input after validating reachable offsets.
907    ///
908    /// # Errors
909    ///
910    /// Returns [`StridedError::StrideLengthMismatch`] when `dims` and
911    /// `strides` have different lengths, and
912    /// [`StridedError::OffsetOverflow`] when a reachable offset is not
913    /// representable: `dim - 1` of a non-unit extent does not fit `isize`, a
914    /// stride times that last index or the accumulated offsets overflow
915    /// `isize` or go negative, or the highest reachable offset is not below
916    /// the buffer length. A layout with a zero extent skips this check.
917    pub fn new(
918        data: &'a [T],
919        dims: &'a [usize],
920        strides: &'a [isize],
921        offset: isize,
922    ) -> Result<Self> {
923        validate_bounds(data.len(), dims, strides, offset)?;
924        Ok(Self {
925            data,
926            dims,
927            strides,
928            offset,
929        })
930    }
931
932    /// Create a raw strided input without bounds checking.
933    ///
934    /// # Safety
935    /// The caller must ensure every index reachable by `dims`/`strides` from
936    /// `offset` lies inside `data`.
937    pub unsafe fn new_unchecked(
938        data: &'a [T],
939        dims: &'a [usize],
940        strides: &'a [isize],
941        offset: isize,
942    ) -> Self {
943        Self {
944            data,
945            dims,
946            strides,
947            offset,
948        }
949    }
950
951    #[inline]
952    pub fn data(&self) -> &'a [T] {
953        self.data
954    }
955
956    #[inline]
957    pub fn dims(&self) -> &'a [usize] {
958        self.dims
959    }
960
961    #[inline]
962    pub fn strides(&self) -> &'a [isize] {
963        self.strides
964    }
965
966    #[inline]
967    pub fn offset(&self) -> isize {
968        self.offset
969    }
970
971    #[inline]
972    pub fn ptr(&self) -> *const T {
973        if self.dims.iter().any(|&dim| dim == 0) {
974            NonNull::<T>::dangling().as_ptr()
975        } else {
976            // SAFETY: `offset` was accepted by `validate_bounds`
977            // (strided-view/src/view.rs:186) in `RawStridedRef::new`, or by the
978            // caller contract of `new_unchecked`, so `data.as_ptr() + offset`
979            // stays inside `data`. The empty-layout branch above returns a
980            // dangling pointer instead of offsetting a pointer no element
981            // reaches.
982            unsafe { self.data.as_ptr().offset(self.offset) }
983        }
984    }
985
986    /// Convert to an immutable view, borrowing this descriptor's metadata.
987    ///
988    /// The conversion allocates nothing; hot prepared paths use the raw
989    /// accessors directly all the same.
990    #[inline]
991    pub fn as_view(&self) -> StridedView<'a, T, Identity> {
992        // SAFETY: `new_unchecked` requires every reachable index to stay inside
993        // `data`; this descriptor's `offset`/`dims`/`strides` came from
994        // `validate_bounds` (strided-view/src/view.rs:186) in `RawStridedRef::new`,
995        // or from the same contract on `new_unchecked`.
996        unsafe { StridedView::new_unchecked(self.data, self.dims, self.strides, self.offset) }
997    }
998}
999
1000/// Borrowed raw strided output layout.
1001///
1002/// This is the mutable counterpart to [`RawStridedRef`]. It avoids allocating
1003/// owned shape/stride metadata in prepared replay paths.
1004pub struct RawStridedMut<'a, T> {
1005    data: &'a mut [T],
1006    dims: &'a [usize],
1007    strides: &'a [isize],
1008    offset: isize,
1009}
1010
1011// A derived `Debug` would print every element of the backing buffer; the layout
1012// metadata is what a reader wants.
1013impl<T: core::fmt::Debug> core::fmt::Debug for RawStridedMut<'_, T> {
1014    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1015        f.debug_struct("RawStridedMut")
1016            .field("data_len", &self.data.len())
1017            .field("dims", &self.dims)
1018            .field("strides", &self.strides)
1019            .field("offset", &self.offset)
1020            .finish()
1021    }
1022}
1023
1024impl<'a, T> RawStridedMut<'a, T> {
1025    /// Create a raw strided output after validating reachable offsets.
1026    ///
1027    /// # Errors
1028    ///
1029    /// Returns [`StridedError::StrideLengthMismatch`] when `dims` and
1030    /// `strides` have different lengths, and
1031    /// [`StridedError::OffsetOverflow`] when a reachable offset is not
1032    /// representable: `dim - 1` of a non-unit extent does not fit `isize`, a
1033    /// stride times that last index or the accumulated offsets overflow
1034    /// `isize` or go negative, or the highest reachable offset is not below
1035    /// the buffer length. A layout with a zero extent skips this check.
1036    pub fn new(
1037        data: &'a mut [T],
1038        dims: &'a [usize],
1039        strides: &'a [isize],
1040        offset: isize,
1041    ) -> Result<Self> {
1042        validate_bounds(data.len(), dims, strides, offset)?;
1043        Ok(Self {
1044            data,
1045            dims,
1046            strides,
1047            offset,
1048        })
1049    }
1050
1051    /// Create a raw strided output without bounds checking.
1052    ///
1053    /// # Safety
1054    /// The caller must ensure every index reachable by `dims`/`strides` from
1055    /// `offset` lies inside `data`, and no aliases violate mutable access.
1056    pub unsafe fn new_unchecked(
1057        data: &'a mut [T],
1058        dims: &'a [usize],
1059        strides: &'a [isize],
1060        offset: isize,
1061    ) -> Self {
1062        Self {
1063            data,
1064            dims,
1065            strides,
1066            offset,
1067        }
1068    }
1069
1070    #[inline]
1071    pub fn data(&self) -> &[T] {
1072        self.data
1073    }
1074
1075    #[inline]
1076    pub fn data_mut(&mut self) -> &mut [T] {
1077        self.data
1078    }
1079
1080    #[inline]
1081    pub fn dims(&self) -> &'a [usize] {
1082        self.dims
1083    }
1084
1085    #[inline]
1086    pub fn strides(&self) -> &'a [isize] {
1087        self.strides
1088    }
1089
1090    #[inline]
1091    pub fn offset(&self) -> isize {
1092        self.offset
1093    }
1094
1095    #[inline]
1096    pub fn ptr(&self) -> *const T {
1097        if self.dims.iter().any(|&dim| dim == 0) {
1098            NonNull::<T>::dangling().as_ptr()
1099        } else {
1100            // SAFETY: `offset` was accepted by `validate_bounds`
1101            // (strided-view/src/view.rs:186) in `RawStridedMut::new`, or by the
1102            // caller contract of `new_unchecked`, so `data.as_ptr() + offset`
1103            // stays inside `data`. The empty-layout branch above returns a
1104            // dangling pointer instead of offsetting a pointer no element
1105            // reaches.
1106            unsafe { self.data.as_ptr().offset(self.offset) }
1107        }
1108    }
1109
1110    #[inline]
1111    pub fn as_mut_ptr(&mut self) -> *mut T {
1112        if self.dims.iter().any(|&dim| dim == 0) {
1113            NonNull::<T>::dangling().as_ptr()
1114        } else {
1115            // SAFETY: as `ptr` above — `offset` was accepted by
1116            // `validate_bounds` (strided-view/src/view.rs:186) in
1117            // `RawStridedMut::new` or by the `new_unchecked` contract, and
1118            // `&mut self` keeps the write provenance exclusive.
1119            unsafe { self.data.as_mut_ptr().offset(self.offset) }
1120        }
1121    }
1122
1123    /// Convert to an immutable view, borrowing this descriptor's metadata.
1124    ///
1125    /// The conversion allocates nothing; hot prepared paths use the raw
1126    /// accessors directly all the same.
1127    #[inline]
1128    pub fn as_view(&self) -> StridedView<'_, T, Identity> {
1129        // SAFETY: `new_unchecked` requires every reachable index to stay inside
1130        // `data`; this descriptor's metadata came from `validate_bounds`
1131        // (strided-view/src/view.rs:186) in `RawStridedMut::new`, or from the
1132        // same contract on `new_unchecked`.
1133        unsafe { StridedView::new_unchecked(self.data, self.dims, self.strides, self.offset) }
1134    }
1135
1136    /// Convert to a mutable view, borrowing this descriptor's metadata.
1137    ///
1138    /// The conversion allocates nothing; hot prepared paths use the raw
1139    /// accessors directly all the same.
1140    #[inline]
1141    pub fn as_view_mut(&mut self) -> StridedViewMut<'_, T> {
1142        // SAFETY: as `as_view` above, and `&mut self` keeps the mutable borrow
1143        // exclusive to the returned view, which `new_unchecked` requires.
1144        unsafe { StridedViewMut::new_unchecked(self.data, self.dims, self.strides, self.offset) }
1145    }
1146}
1147
1148#[cfg(test)]
1149mod tests;