Skip to main content

hermes_simd_core/iter/
chunks.rs

1//! `SimdChunks` and `SimdChunksMut` — immutable and mutable SIMD chunk iterators.
2//!
3//! These iterate non-overlapping sub-views of exactly `LANE_COUNT` elements from a
4//! `SimdView`, leaving the remainder (the scalar tail) accessible via
5//! [`SimdChunks::remainder`] / [`SimdChunksMut::into_remainder`].
6//!
7//! # Safety
8//!
9//! Every kernel call below is `#[target_feature]`-gated and is therefore sound
10//! only on a host implementing `Arch`. That holds by construction rather than by
11//! inspection: [`SimdView::new`](crate::view::SimdView::new) returns `None` for
12//! an architecture the host cannot execute, and the sparse and copy-on-write
13//! constructors assert the same condition, so possessing one of these
14//! arch-parameterized values *is* the proof. Per-site `SAFETY` comments record
15//! only the obligations that go beyond it — pointer provenance, bounds, and
16//! alignment.
17
18use crate::align::Alignment;
19use crate::arch::SimdArch;
20use crate::execution::ExecutionMode;
21use crate::kernel::SimdKernel;
22use crate::scalar::Scalar;
23use crate::view::SimdView;
24use core::marker::PhantomData;
25
26/// Iterator over non-overlapping `LANE_COUNT`-wide sub-views of a `SimdView`.
27///
28/// Created by [`SimdView::simd_chunks`]. The final partial chunk (length `< LANE_COUNT`)
29/// is NOT yielded as an `Item`; access it via [`SimdChunks::remainder`] after the loop.
30///
31/// # Type Parameters
32/// Mirrors the parent [`SimdView`] — `T`, `Arch`, `Align`, `Mode` are all preserved so
33/// the yielded sub-views carry identical type-level guarantees.
34pub struct SimdChunks<'a, T: 'a, Arch: SimdArch, Align: Alignment, Mode: ExecutionMode> {
35    /// Base pointer of the original slice.
36    base: *const T,
37    /// Current element offset (advances by `LANE_COUNT` per step).
38    pos: usize,
39    /// Total number of elements in the original slice.
40    total: usize,
41    /// `floor(total / LANE_COUNT) * LANE_COUNT` — the SIMD-processable prefix.
42    simd_end: usize,
43    _marker: PhantomData<(&'a T, Arch, Align, Mode)>,
44}
45
46// SAFETY: SimdChunks borrows `'a` data immutably; forwarding Send/Sync is sound
47// when `T: Send` / `T: Sync`.
48unsafe impl<
49        'a,
50        T: Send,
51        Arch: SimdArch + crate::kernel::SimdKernel<T>,
52        Align: Alignment,
53        Mode: ExecutionMode,
54    > Send for SimdChunks<'a, T, Arch, Align, Mode>
55where
56    T: crate::scalar::Scalar,
57{
58}
59unsafe impl<
60        'a,
61        T: Sync,
62        Arch: SimdArch + crate::kernel::SimdKernel<T>,
63        Align: Alignment,
64        Mode: ExecutionMode,
65    > Sync for SimdChunks<'a, T, Arch, Align, Mode>
66where
67    T: crate::scalar::Scalar,
68{
69}
70
71impl<
72        'a,
73        T: 'a,
74        Arch: SimdArch + crate::kernel::SimdKernel<T>,
75        Align: Alignment,
76        Mode: ExecutionMode,
77    > SimdChunks<'a, T, Arch, Align, Mode>
78where
79    T: crate::scalar::Scalar,
80{
81    /// Create a new `SimdChunks` iterator from raw parts.
82    ///
83    /// # Safety
84    /// `base` must be valid for reads of `total` elements for the lifetime `'a`.
85    #[inline]
86    pub(crate) unsafe fn from_raw_parts(base: *const T, total: usize, lane_count: usize) -> Self {
87        let simd_end = (total / lane_count) * lane_count;
88        Self {
89            base,
90            pos: 0,
91            total,
92            simd_end,
93            _marker: PhantomData,
94        }
95    }
96
97    /// Returns the scalar tail — elements that did not fill a complete SIMD vector.
98    ///
99    /// Equivalent to `&original_slice[simd_end..]`. Length is `total % LANE_COUNT`.
100    /// May be empty if `total` is a multiple of `LANE_COUNT`.
101    ///
102    /// # Usage
103    /// Call this after exhausting the iterator (or at any time) to access the tail:
104    /// ```rust,ignore
105    /// let mut chunks = view.simd_chunks();
106    /// for chunk in &mut chunks { /* SIMD body */ }
107    /// for &x in chunks.remainder() { /* scalar tail */ }
108    /// ```
109    #[inline(always)]
110    pub fn remainder(&self) -> &'a [T] {
111        // SAFETY: base + simd_end is within the original slice of length `total`.
112        // simd_end <= total by construction.
113        unsafe {
114            core::slice::from_raw_parts(self.base.add(self.simd_end), self.total - self.simd_end)
115        }
116    }
117}
118
119impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
120    SimdChunks<'a, T, Arch, Align, Mode>
121{
122    /// Returns the number of complete SIMD chunks remaining.
123    ///
124    /// After `n` calls to `next`, this returns `original_chunks - n`.
125    #[inline(always)]
126    pub fn chunks_remaining(&self) -> usize {
127        if self.simd_end > self.pos {
128            (self.simd_end - self.pos) / Arch::LANE_COUNT
129        } else {
130            0
131        }
132    }
133}
134
135impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
136    Iterator for SimdChunks<'a, T, Arch, Align, Mode>
137{
138    type Item = SimdView<'a, T, Arch, Align, Mode, &'a [T]>;
139
140    #[inline(always)]
141    fn next(&mut self) -> Option<Self::Item> {
142        if self.pos >= self.simd_end {
143            return None;
144        }
145        // SAFETY:
146        // - `base + pos` is within the original slice (pos < simd_end <= total).
147        // - `pos + LANE_COUNT <= simd_end <= total`, so the sub-slice is within bounds.
148        // - `Align::ALIGNMENT` contract is preserved: the base pointer satisfies it,
149        //    and `pos * size_of::<T>()` is a multiple of LANE_COUNT*size_of::<T>(),
150        //    which is >= ALIGNMENT for all known backends.
151        let chunk_slice =
152            unsafe { core::slice::from_raw_parts(self.base.add(self.pos), Arch::LANE_COUNT) };
153        self.pos += Arch::LANE_COUNT;
154        // SAFETY: alignment is guaranteed by AlignedVec contract on the parent.
155        // For Unaligned parents, `SimdView::new` with `Unaligned` never fails.
156        Some(SimdView::new(chunk_slice).expect("chunk alignment invariant violated"))
157    }
158
159    #[inline(always)]
160    fn size_hint(&self) -> (usize, Option<usize>) {
161        let remaining = self.chunks_remaining();
162        (remaining, Some(remaining))
163    }
164}
165
166impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
167    ExactSizeIterator for SimdChunks<'a, T, Arch, Align, Mode>
168{
169}
170
171impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
172    DoubleEndedIterator for SimdChunks<'a, T, Arch, Align, Mode>
173{
174    #[inline(always)]
175    fn next_back(&mut self) -> Option<Self::Item> {
176        if self.pos >= self.simd_end {
177            return None;
178        }
179        self.simd_end -= Arch::LANE_COUNT;
180        // SAFETY: same as `next` — `simd_end` is still within original slice bounds.
181        let chunk_slice =
182            unsafe { core::slice::from_raw_parts(self.base.add(self.simd_end), Arch::LANE_COUNT) };
183        Some(SimdView::new(chunk_slice).expect("chunk alignment invariant violated"))
184    }
185}
186
187impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
188    core::iter::FusedIterator for SimdChunks<'a, T, Arch, Align, Mode>
189{
190}
191
192// ---------------------------------------------------------------------------
193// SimdChunksMut
194// ---------------------------------------------------------------------------
195
196/// Iterator over non-overlapping mutable `LANE_COUNT`-wide sub-views of a `SimdView`.
197///
198/// Created by [`SimdView::simd_chunks_mut`]. The final partial chunk (length `< LANE_COUNT`)
199/// is NOT yielded as an `Item`; access it via [`SimdChunksMut::into_remainder`] after the loop.
200pub struct SimdChunksMut<'a, T: 'a, Arch: SimdArch, Align: Alignment, Mode: ExecutionMode> {
201    /// Base pointer of the original slice.
202    base: *mut T,
203    /// Current element offset (advances by `LANE_COUNT` per step).
204    pos: usize,
205    /// Total number of elements in the original slice.
206    total: usize,
207    /// `floor(total / LANE_COUNT) * LANE_COUNT` — the SIMD-processable prefix.
208    simd_end: usize,
209    _marker: PhantomData<(&'a mut T, Arch, Align, Mode)>,
210}
211
212// SAFETY: SimdChunksMut borrows `'a` data mutably; forwarding Send/Sync is sound
213// when `T: Send` / `T: Sync`.
214unsafe impl<
215        'a,
216        T: Send,
217        Arch: SimdArch + crate::kernel::SimdKernel<T>,
218        Align: Alignment,
219        Mode: ExecutionMode,
220    > Send for SimdChunksMut<'a, T, Arch, Align, Mode>
221where
222    T: crate::scalar::Scalar,
223{
224}
225unsafe impl<
226        'a,
227        T: Sync,
228        Arch: SimdArch + crate::kernel::SimdKernel<T>,
229        Align: Alignment,
230        Mode: ExecutionMode,
231    > Sync for SimdChunksMut<'a, T, Arch, Align, Mode>
232where
233    T: crate::scalar::Scalar,
234{
235}
236
237impl<
238        'a,
239        T: 'a,
240        Arch: SimdArch + crate::kernel::SimdKernel<T>,
241        Align: Alignment,
242        Mode: ExecutionMode,
243    > SimdChunksMut<'a, T, Arch, Align, Mode>
244where
245    T: crate::scalar::Scalar,
246{
247    /// Create a new `SimdChunksMut` iterator from raw parts.
248    ///
249    /// # Safety
250    /// `base` must be valid for reads and writes of `total` elements for the lifetime `'a`.
251    #[inline]
252    pub(crate) unsafe fn from_raw_parts(base: *mut T, total: usize, lane_count: usize) -> Self {
253        let simd_end = (total / lane_count) * lane_count;
254        Self {
255            base,
256            pos: 0,
257            total,
258            simd_end,
259            _marker: PhantomData,
260        }
261    }
262
263    /// Returns the mutable scalar tail — elements that did not fill a complete SIMD vector.
264    ///
265    /// Consumes the iterator to return a mutable slice with lifetime `'a`.
266    #[inline(always)]
267    pub fn into_remainder(self) -> &'a mut [T] {
268        // SAFETY: base + simd_end is within the original slice of length `total`.
269        // simd_end <= total by construction.
270        unsafe {
271            core::slice::from_raw_parts_mut(
272                self.base.add(self.simd_end),
273                self.total - self.simd_end,
274            )
275        }
276    }
277}
278
279impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
280    SimdChunksMut<'a, T, Arch, Align, Mode>
281{
282    /// Returns the number of complete SIMD chunks remaining.
283    #[inline(always)]
284    pub fn chunks_remaining(&self) -> usize {
285        if self.simd_end > self.pos {
286            (self.simd_end - self.pos) / Arch::LANE_COUNT
287        } else {
288            0
289        }
290    }
291}
292
293impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
294    Iterator for SimdChunksMut<'a, T, Arch, Align, Mode>
295{
296    type Item = SimdView<'a, T, Arch, Align, Mode, &'a mut [T]>;
297
298    #[inline(always)]
299    fn next(&mut self) -> Option<Self::Item> {
300        if self.pos >= self.simd_end {
301            return None;
302        }
303        // SAFETY:
304        // - `base + pos` is within the original slice (pos < simd_end <= total).
305        // - `pos + LANE_COUNT <= simd_end <= total`, so the sub-slice is within bounds.
306        // - `Align::ALIGNMENT` contract is preserved: the base pointer satisfies it.
307        let chunk_slice =
308            unsafe { core::slice::from_raw_parts_mut(self.base.add(self.pos), Arch::LANE_COUNT) };
309        self.pos += Arch::LANE_COUNT;
310        // SAFETY: alignment is guaranteed by AlignedVec contract on the parent.
311        // For Unaligned parents, `SimdView::new_mut` with `Unaligned` never fails.
312        Some(SimdView::new_mut(chunk_slice).expect("chunk alignment invariant violated"))
313    }
314
315    #[inline(always)]
316    fn size_hint(&self) -> (usize, Option<usize>) {
317        let remaining = self.chunks_remaining();
318        (remaining, Some(remaining))
319    }
320}
321
322impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
323    ExactSizeIterator for SimdChunksMut<'a, T, Arch, Align, Mode>
324{
325}
326
327impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
328    DoubleEndedIterator for SimdChunksMut<'a, T, Arch, Align, Mode>
329{
330    #[inline(always)]
331    fn next_back(&mut self) -> Option<Self::Item> {
332        if self.pos >= self.simd_end {
333            return None;
334        }
335        self.simd_end -= Arch::LANE_COUNT;
336        // SAFETY: same as `next` — `simd_end` is still within original slice bounds.
337        let chunk_slice = unsafe {
338            core::slice::from_raw_parts_mut(self.base.add(self.simd_end), Arch::LANE_COUNT)
339        };
340        Some(SimdView::new_mut(chunk_slice).expect("chunk alignment invariant violated"))
341    }
342}
343
344impl<'a, T: Scalar + 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
345    core::iter::FusedIterator for SimdChunksMut<'a, T, Arch, Align, Mode>
346{
347}