Skip to main content

hermes_simd_core/cow/
combinators.rs

1//! CoW combinators: `zip_cow`, `transform_in_place`, `reduce`, arithmetic shorthands,
2//! and ergonomic `From`/`Extend` conversions for `SimdCow`.
3//!
4//! # Safety
5//!
6//! Two obligations recur here. Kernel calls are `#[target_feature]`-gated, and
7//! that precondition holds by construction: a `SimdCow` exists only for an
8//! architecture the host can execute, since its borrowed form comes from
9//! [`SimdView::new`](crate::view::SimdView::new) and its owned constructors
10//! assert the same condition. The second is local — these routines build their
11//! output buffer with `with_capacity` and write it through a raw pointer,
12//! raising the length only once every element is initialized. That avoids both
13//! a zero-fill of a buffer about to be overwritten and any `&mut [T]` spanning
14//! uninitialized elements, so each such site carries a `SAFETY` comment showing
15//! the write coverage. `gather` and `prefix_scan` reserve capacity and fill it
16//! through the view's `*_into_uninit` methods over
17//! [`AlignedVec::spare_capacity_mut`](crate::vec::AlignedVec::spare_capacity_mut),
18//! then raise the length once those report success, so those paths never zero
19//! the buffer either.
20
21use super::types::SimdCow;
22use crate::align::Alignment;
23use crate::arch::SimdArch;
24use crate::execution::Unmasked;
25use crate::kernel::SimdKernel;
26use crate::ops::{ElementOp, ReductionOp};
27use crate::scalar::Scalar;
28use crate::vec::AlignedVec;
29use crate::view::{SimdError, SimdView};
30
31impl<'a, T: 'a, Arch, Align> SimdCow<'a, T, Arch, Align>
32where
33    T: Scalar,
34    Arch: SimdArch + SimdKernel<T>,
35    Align: Alignment,
36{
37    /// Sum all elements using `SimdView::sum`.
38    #[inline(always)]
39    pub fn sum(&self) -> T {
40        self.view().sum()
41    }
42
43    /// Compute the dot product with another `SimdCow`.
44    ///
45    /// # Errors
46    /// Returns `SimdError::LengthMismatch` if lengths differ.
47    #[inline(always)]
48    pub fn dot(&self, other: &Self) -> Result<T, SimdError> {
49        self.view().dot(&other.view())
50    }
51
52    /// Apply an `ElementOp` pairwise to `self` and `other`, returning a fully-owned
53    /// `SimdCow<'static, T, Arch, Align>` backed by a single `AlignedVec` allocation.
54    ///
55    /// The SIMD vectorized loop processes `floor(len / LANE_COUNT) * LANE_COUNT` elements.
56    /// The scalar tail uses `Op::apply_scalar` directly on individual elements — no
57    /// vector loads or stores are performed in the tail, avoiding all boundary-condition UB.
58    ///
59    /// # Errors
60    /// Returns `SimdError::LengthMismatch` if `self.len() != other.len()`.
61    pub fn zip_cow<Op: ElementOp<T>>(
62        &self,
63        other: &SimdCow<'_, T, Arch, Align>,
64        _op: Op,
65    ) -> Result<SimdCow<'static, T, Arch, Align>, SimdError> {
66        if self.len() != other.len() {
67            return Err(SimdError::LengthMismatch);
68        }
69        let len = self.len();
70        if len == 0 {
71            return Ok(SimdCow::Owned(AlignedVec::new()));
72        }
73
74        let mut out = AlignedVec::with_capacity(len);
75        let out_ptr: *mut T = out.as_mut_ptr();
76
77        let view_self = self.view();
78        let view_other = other.view();
79
80        let mut chunks_self = view_self.simd_chunks();
81        let mut chunks_other = view_other.simd_chunks();
82
83        let mut i = 0usize;
84        for (chunk_self, chunk_other) in (&mut chunks_self).zip(&mut chunks_other) {
85            unsafe {
86                let va = if crate::align::is_aligned_for_arch::<Arch, Align>() {
87                    Arch::load_aligned(chunk_self.as_ptr())
88                } else {
89                    Arch::load_unaligned(chunk_self.as_ptr())
90                };
91                let vb = if crate::align::is_aligned_for_arch::<Arch, Align>() {
92                    Arch::load_aligned(chunk_other.as_ptr())
93                } else {
94                    Arch::load_unaligned(chunk_other.as_ptr())
95                };
96                let vr = _op.apply::<Arch>(va, vb);
97                if crate::align::is_aligned_for_arch::<Arch, Align>() {
98                    Arch::store_aligned(out_ptr.add(i), vr);
99                } else {
100                    Arch::store_unaligned(out_ptr.add(i), vr);
101                }
102            }
103            i += Arch::LANE_COUNT;
104        }
105
106        let remainder_self = chunks_self.remainder();
107        let remainder_other = chunks_other.remainder();
108
109        for (&a, &b) in remainder_self.iter().zip(remainder_other.iter()) {
110            unsafe {
111                core::ptr::write(out_ptr.add(i), _op.apply_scalar(a, b));
112            }
113            i += 1;
114        }
115
116        // SAFETY: `with_capacity(len)` reserved `len` elements, and the vector
117        // and remainder loops above have together written every one of them
118        // through `out_ptr`. The length is raised only now, so no reference to
119        // this buffer ever spanned uninitialized memory.
120        unsafe {
121            out.set_len(len);
122        }
123
124        Ok(SimdCow::Owned(out))
125    }
126
127    /// Apply an `ElementOp` in-place: `self[i] = op(self[i], other[i])`.
128    ///
129    /// If `self` is `Borrowed`, promotes to `Owned` first (one allocation).
130    /// Subsequent calls on the same already-owned `self` are allocation-free.
131    ///
132    /// The scalar tail uses `Op::apply_scalar` to avoid vector boundary UB.
133    ///
134    /// # Errors
135    /// Returns `SimdError::LengthMismatch` if `self.len() != other.len()`.
136    pub fn transform_in_place<Op: ElementOp<T>>(
137        &mut self,
138        other: &SimdCow<'_, T, Arch, Align>,
139        _op: Op,
140    ) -> Result<(), SimdError> {
141        if self.len() != other.len() {
142            return Err(SimdError::LengthMismatch);
143        }
144        // `to_mut` promotes borrowed → owned (one allocation if borrowed, free if owned).
145        // Use the returned reference directly — no secondary match required.
146        let out_slice = self.to_mut().as_mut_slice();
147
148        let other_view = other.view();
149
150        let self_view: SimdView<'_, T, Arch, Align, Unmasked, &mut [T]> =
151            SimdView::new_mut(out_slice).expect("alignment invariant violated");
152
153        let mut chunks_self = self_view.simd_chunks_mut();
154        let mut chunks_other = other_view.simd_chunks();
155
156        for (mut chunk_self, chunk_other) in (&mut chunks_self).zip(&mut chunks_other) {
157            unsafe {
158                let va = if crate::align::is_aligned_for_arch::<Arch, Align>() {
159                    Arch::load_aligned(chunk_self.as_ptr())
160                } else {
161                    Arch::load_unaligned(chunk_self.as_ptr())
162                };
163                let vb = if crate::align::is_aligned_for_arch::<Arch, Align>() {
164                    Arch::load_aligned(chunk_other.as_ptr())
165                } else {
166                    Arch::load_unaligned(chunk_other.as_ptr())
167                };
168                let vr = _op.apply::<Arch>(va, vb);
169                if crate::align::is_aligned_for_arch::<Arch, Align>() {
170                    Arch::store_aligned(chunk_self.as_mut_ptr(), vr);
171                } else {
172                    Arch::store_unaligned(chunk_self.as_mut_ptr(), vr);
173                }
174            }
175        }
176
177        let tail_self = chunks_self.into_remainder();
178        let tail_other = chunks_other.remainder();
179        for (a, &b) in tail_self.iter_mut().zip(tail_other.iter()) {
180            *a = _op.apply_scalar(*a, b);
181        }
182
183        Ok(())
184    }
185
186    /// Apply a `ReductionOp` to this `SimdCow`, delegating to `SimdView::reduce`.
187    ///
188    /// Monomorphization is shared with the view path — no duplicate code.
189    #[inline(always)]
190    pub fn reduce<Op: ReductionOp<T>>(&self, op: Op) -> T {
191        self.view().reduce(op)
192    }
193
194    // -----------------------------------------------------------------------
195    // Arithmetic combinators — each returns `SimdCow<'static, ...>` (owned)
196    // -----------------------------------------------------------------------
197
198    /// Elementwise addition: `self[i] + other[i]`.
199    ///
200    /// Allocates one `AlignedVec` output. Zero-copy on both operands.
201    ///
202    /// # Errors
203    /// Returns `SimdError::LengthMismatch` if lengths differ.
204    #[inline(always)]
205    pub fn add_cow(
206        &self,
207        other: &SimdCow<'_, T, Arch, Align>,
208    ) -> Result<SimdCow<'static, T, Arch, Align>, SimdError> {
209        self.zip_cow(other, crate::ops::Add)
210    }
211
212    /// Elementwise subtraction: `self[i] - other[i]`.
213    ///
214    /// # Errors
215    /// Returns `SimdError::LengthMismatch` if lengths differ.
216    #[inline(always)]
217    pub fn sub_cow(
218        &self,
219        other: &SimdCow<'_, T, Arch, Align>,
220    ) -> Result<SimdCow<'static, T, Arch, Align>, SimdError> {
221        self.zip_cow(other, crate::ops::Sub)
222    }
223
224    /// Elementwise multiplication: `self[i] * other[i]`.
225    ///
226    /// # Errors
227    /// Returns `SimdError::LengthMismatch` if lengths differ.
228    #[inline(always)]
229    pub fn mul_cow(
230        &self,
231        other: &SimdCow<'_, T, Arch, Align>,
232    ) -> Result<SimdCow<'static, T, Arch, Align>, SimdError> {
233        self.zip_cow(other, crate::ops::Mul)
234    }
235}
236
237// ---------------------------------------------------------------------------
238// Ergonomic conversions
239// ---------------------------------------------------------------------------
240
241/// Adopt an `AlignedVec` as an owned `SimdCow` — zero-cost, no allocation.
242impl<'a, T, Arch, Align> From<AlignedVec<T, Align>> for SimdCow<'a, T, Arch, Align>
243where
244    Arch: SimdArch,
245    Align: Alignment,
246{
247    #[inline]
248    fn from(vec: AlignedVec<T, Align>) -> Self {
249        Self::Owned(vec)
250    }
251}
252
253/// Copy a standard `Vec<T>` into a new owned `SimdCow`, allocating one aligned buffer.
254impl<'a, T: Copy, Arch, Align> From<alloc::vec::Vec<T>> for SimdCow<'a, T, Arch, Align>
255where
256    Arch: SimdArch,
257    Align: Alignment,
258{
259    #[inline]
260    fn from(v: alloc::vec::Vec<T>) -> Self {
261        Self::Owned(AlignedVec::from_slice(&v))
262    }
263}
264
265impl<'a, T: Copy + 'a, Arch, Align> Extend<T> for SimdCow<'a, T, Arch, Align>
266where
267    Arch: SimdArch + SimdKernel<T>,
268    Align: Alignment,
269    T: Scalar,
270{
271    /// Extend the `SimdCow`, promoting to owned if currently borrowed.
272    ///
273    /// After promotion, subsequent `extend` calls are allocation-free as long as the
274    /// `AlignedVec` has sufficient capacity.
275    #[inline]
276    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
277        let iter = iter.into_iter();
278        let vec = self.to_mut();
279        // Reserve the iterator's lower-bound size up front so a bulk extend does
280        // one reallocation rather than the ⌈log₂ n⌉ a push loop would incur
281        // (`size_hint().0` is exact for the common sized-iterator sources).
282        vec.reserve(iter.size_hint().0);
283        for item in iter {
284            vec.push(item);
285        }
286    }
287}