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}