Skip to main content

hermes_simd_core/view/
ops_mut.rs

1//! Mutable elementwise SIMD operations on views backed by `&'a mut [T]`.
2//!
3//! All methods on `SimdView<'a, T, Arch, Align, Mode, &'a mut [T]>` that modify the
4//! underlying slice in-place live here, keeping `ops.rs` (read-only) and `ops_mut.rs`
5//! (write) as two separate bounded-context files.
6//!
7//! # DRY Note
8//!
9//! Concrete `add_assign` and `mul_assign` delegate to the generic `transform_in_place`
10//! kernel. This yields a single authoritative SIMD loop body that is monomorphized per
11//! `(T, Arch, Align, Op)` — not duplicated for each binary operation.
12
13use crate::align::Alignment;
14use crate::arch::SimdArch;
15use crate::execution::ExecutionMode;
16use crate::kernel::SimdKernel;
17use crate::ops::{Add, ElementOp, Mul};
18use crate::scalar::Scalar;
19use crate::view::{SimdError, SimdView};
20
21impl<'a, T: 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode>
22    SimdView<'a, T, Arch, Align, Mode, &'a mut [T]>
23where
24    T: Scalar,
25{
26    /// Apply an elementwise `ElementOp<T>` on `self` and `other` in-place:
27    /// `self[i] = op(self[i], other[i])`.
28    ///
29    /// This is the canonical generic in-place kernel. `add_assign` and `mul_assign`
30    /// delegate here. The operation ZST is erased at every monomorphization site.
31    ///
32    /// # Zero-Cost Contract
33    ///
34    /// `Op` is a ZST (`size_of::<Op>() == 0` for `Add`, `Mul`, etc.). The compiler
35    /// inlines the `op.apply::<Arch>` call and the alignment branch is eliminated by DCE
36    /// at each `(T, Arch, Align, Op)` monomorphization.
37    ///
38    /// # Errors
39    ///
40    /// Returns [`SimdError::LengthMismatch`] if operand lengths differ.
41    #[inline(always)]
42    pub fn transform_in_place<ORef, Op>(
43        &mut self,
44        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
45        op: Op,
46    ) -> Result<(), SimdError>
47    where
48        ORef: 'a,
49        Op: ElementOp<T>,
50    {
51        super::check_lengths_equal(self.len(), other.len())?;
52
53        let len = self.len();
54        let lane_count = Arch::LANE_COUNT;
55        let simd_len = (len / lane_count) * lane_count;
56
57        let ptr_self = self.as_slice_mut().as_mut_ptr();
58        let ptr_other = other.as_slice().as_ptr();
59
60        unsafe {
61            // Alignment-dependent load/store closures. The `Align::IS_ALIGNED` branch is
62            // a compile-time constant: DCE removes the unused arm at every monomorphization.
63            let load_self = |p: *const T| {
64                if crate::align::is_aligned_for_arch::<Arch, Align>() {
65                    Arch::load_aligned(p)
66                } else {
67                    Arch::load_unaligned(p)
68                }
69            };
70            let load_other = |p: *const T| {
71                if crate::align::is_aligned_for_arch::<Arch, Align>() {
72                    Arch::load_aligned(p)
73                } else {
74                    Arch::load_unaligned(p)
75                }
76            };
77            let store = |p: *mut T, v: Arch::Vector| {
78                if crate::align::is_aligned_for_arch::<Arch, Align>() {
79                    Arch::store_aligned(p, v);
80                } else {
81                    Arch::store_unaligned(p, v);
82                }
83            };
84
85            for i in (0..simd_len).step_by(lane_count) {
86                let va = load_self(ptr_self.add(i) as *const T);
87                let vb = load_other(ptr_other.add(i));
88                let vr = op.apply::<Arch>(va, vb);
89                store(ptr_self.add(i), vr);
90            }
91        }
92
93        // Scalar tail — elements that do not fill a complete SIMD vector.
94        let s_mut_slice = self.as_slice_mut();
95        let o_slice = other.as_slice();
96        for i in simd_len..len {
97            s_mut_slice[i] = op.apply_scalar(s_mut_slice[i], o_slice[i]);
98        }
99
100        Ok(())
101    }
102
103    /// Add another view elementwise to this mutable view in-place.
104    ///
105    /// Delegates to [`Self::transform_in_place`] with the [`Add`] strategy.
106    ///
107    /// # Errors
108    ///
109    /// Returns [`SimdError::LengthMismatch`] if operand lengths do not match.
110    #[inline(always)]
111    pub fn add_assign<ORef>(
112        &mut self,
113        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
114    ) -> Result<(), SimdError>
115    where
116        ORef: 'a,
117    {
118        self.transform_in_place(other, Add)
119    }
120
121    /// Multiply another view elementwise with this mutable view in-place.
122    ///
123    /// Delegates to [`Self::transform_in_place`] with the [`Mul`] strategy.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`SimdError::LengthMismatch`] if operand lengths do not match.
128    #[inline(always)]
129    pub fn mul_assign<ORef>(
130        &mut self,
131        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
132    ) -> Result<(), SimdError>
133    where
134        ORef: 'a,
135    {
136        self.transform_in_place(other, Mul)
137    }
138}