Skip to main content

hermes_simd_core/view/
scan.rs

1use crate::align::Alignment;
2use crate::arch::SimdArch;
3use crate::execution::ExecutionMode;
4use crate::kernel::SimdKernel;
5use crate::ops::{ScanMode, ScanOp};
6use crate::scalar::Scalar;
7use crate::view::{SimdError, SimdView};
8use core::mem::MaybeUninit;
9
10impl<'a, T: 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode, Ref: 'a>
11    SimdView<'a, T, Arch, Align, Mode, Ref>
12where
13    T: Scalar,
14{
15    /// Perform a prefix scan (inclusive or exclusive) of the view using the specified operation,
16    /// writing results to `out`.
17    ///
18    /// # Errors
19    /// Returns `SimdError::InsufficientOutputLength` if `out.len() < self.len()`.
20    #[inline(always)]
21    pub fn prefix_scan<Op, SMode>(
22        &self,
23        out: &mut [T],
24        op: Op,
25        mode: SMode,
26    ) -> Result<(), SimdError>
27    where
28        Op: ScanOp<T>,
29        SMode: ScanMode,
30    {
31        // SAFETY: an initialized `[T]` is a valid `[MaybeUninit<T>]` — the cast
32        // only widens the permitted state — and `T: Scalar` is `Copy`, so the
33        // `MaybeUninit::write`s in the delegate drop nothing.
34        let out_uninit = unsafe {
35            core::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut MaybeUninit<T>, out.len())
36        };
37        self.prefix_scan_into_uninit(out_uninit, op, mode)?;
38        Ok(())
39    }
40
41    /// Prefix scan into a possibly-uninitialized buffer, returning the initialized prefix.
42    ///
43    /// This is the single scan implementation; [`prefix_scan`](Self::prefix_scan)
44    /// is the initialized-slice wrapper. On `Ok` exactly the first `self.len()`
45    /// elements of `out` are initialized (and returned); on `Err` — only when
46    /// `out` is too short, checked before any store — nothing is written.
47    /// Filling an `AlignedVec`'s
48    /// [`spare_capacity_mut`](crate::vec::AlignedVec::spare_capacity_mut) through
49    /// this method avoids a zero-fill of the output.
50    ///
51    /// # Errors
52    /// Returns `SimdError::InsufficientOutputLength` if `out.len() < self.len()`.
53    #[inline]
54    pub fn prefix_scan_into_uninit<'o, Op, SMode>(
55        &self,
56        out: &'o mut [MaybeUninit<T>],
57        _op: Op,
58        _mode: SMode,
59    ) -> Result<&'o mut [T], SimdError>
60    where
61        Op: ScanOp<T>,
62        SMode: ScanMode,
63    {
64        let len = self.len();
65        if out.len() < len {
66            return Err(SimdError::InsufficientOutputLength);
67        }
68
69        let src = self.as_slice();
70        let lane_count = Arch::LANE_COUNT;
71        let simd_len = (len / lane_count) * lane_count;
72        let ptr_in = src.as_ptr();
73        // Derive the output pointer once and write exclusively through it: mixing
74        // it with `out[i]` slice reborrows would invalidate its provenance under
75        // Stacked Borrows.
76        let ptr_out = out.as_mut_ptr().cast::<T>();
77
78        let mut carry = Op::identity();
79
80        // SAFETY: `out` holds at least `len` slots (checked above) and
81        // `MaybeUninit<T>` shares `T`'s layout, so the vector and scalar stores
82        // below fill `[0, len)` through `ptr_out` without reading any slot first;
83        // `ptr_in` reads the view's own `len` initialized elements.
84        unsafe {
85            let load = |p: *const T| {
86                if crate::align::is_aligned_for_arch::<Arch, Align>() {
87                    Arch::load_aligned(p)
88                } else {
89                    Arch::load_unaligned(p)
90                }
91            };
92            let store = |p: *mut T, v: Arch::Vector| {
93                if crate::align::is_aligned_for_arch::<Arch, Align>() {
94                    Arch::store_aligned(p, v)
95                } else {
96                    Arch::store_unaligned(p, v)
97                }
98            };
99
100            for i in (0..simd_len).step_by(lane_count) {
101                let v = load(ptr_in.add(i));
102                let (r, next_carry) = Arch::scan_vector::<Op, SMode>(v, carry);
103                store(ptr_out.add(i), r);
104                carry = next_carry;
105            }
106
107            if SMode::IS_INCLUSIVE {
108                for i in simd_len..len {
109                    carry = Op::combine(carry, src[i]);
110                    core::ptr::write(ptr_out.add(i), carry);
111                }
112            } else {
113                for i in simd_len..len {
114                    let temp = src[i];
115                    core::ptr::write(ptr_out.add(i), carry);
116                    carry = Op::combine(carry, temp);
117                }
118            }
119
120            // Every element of `[0, len)` is now initialized.
121            Ok(core::slice::from_raw_parts_mut(ptr_out, len))
122        }
123    }
124}
125
126impl<'a, T: 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
127    SimdView<'a, T, Arch, Align, Mode, &'a mut [T]>
128where
129    T: Scalar,
130{
131    /// Perform an in-place prefix scan (inclusive or exclusive) using the
132    /// specified operation.
133    ///
134    /// Vectorized via `Arch::scan_vector` with a scalar carry across chunks;
135    /// the scalar tail uses `Op::combine`. Loads and stores at the same offset
136    /// are sequential, so no intra-chunk aliasing hazard exists.
137    #[inline(always)]
138    pub fn prefix_scan_in_place<Op, SMode>(&mut self, _op: Op, _mode: SMode)
139    where
140        Op: ScanOp<T>,
141        SMode: ScanMode,
142    {
143        let data = self.as_slice_mut();
144        let len = data.len();
145        if len == 0 {
146            return;
147        }
148
149        let lane_count = Arch::LANE_COUNT;
150        let simd_len = (len / lane_count) * lane_count;
151        let ptr = data.as_mut_ptr();
152
153        let mut carry = Op::identity();
154
155        unsafe {
156            let load = |p: *const T| {
157                if crate::align::is_aligned_for_arch::<Arch, Align>() {
158                    Arch::load_aligned(p)
159                } else {
160                    Arch::load_unaligned(p)
161                }
162            };
163            let store = |p: *mut T, v: Arch::Vector| {
164                if crate::align::is_aligned_for_arch::<Arch, Align>() {
165                    Arch::store_aligned(p, v)
166                } else {
167                    Arch::store_unaligned(p, v)
168                }
169            };
170
171            for i in (0..simd_len).step_by(lane_count) {
172                let v = load(ptr.add(i));
173                let (r, next_carry) = Arch::scan_vector::<Op, SMode>(v, carry);
174                store(ptr.add(i), r);
175                carry = next_carry;
176            }
177        }
178
179        if SMode::IS_INCLUSIVE {
180            for x in &mut data[simd_len..] {
181                carry = Op::combine(carry, *x);
182                *x = carry;
183            }
184        } else {
185            for x in &mut data[simd_len..] {
186                let temp = *x;
187                *x = carry;
188                carry = Op::combine(carry, temp);
189            }
190        }
191    }
192}