hermes_simd_core/cow/unary.rs
1//! Unary and ternary CoW transformations: `map_cow`, `fma_cow`.
2//!
3//! # Design
4//!
5//! All functions take `&SimdCow` (borrow) and return a new `SimdCow<'static, T, Arch, Align>`
6//! backed by exactly one `AlignedVec` allocation. The input is never mutated.
7//!
8//! `map_cow` generalizes `UnaryOp` dispatch so callers pass a ZST strategy type
9//! (e.g. `ops::Abs`, `ops::Neg`, `ops::Sqrt`) and the kernel dispatches through
10//! the existing `UnaryOp<T>` trait without any runtime branches.
11//!
12//! `fma_cow` implements `out[i] = a[i] * b[i] + c[i]` using `Arch::fmadd` in the
13//! SIMD region and `T::scalar_fmadd` in the scalar tail.
14//!
15//! # Safety
16//!
17//! Two obligations recur here. Kernel calls are `#[target_feature]`-gated, and
18//! that precondition holds by construction: a `SimdCow` exists only for an
19//! architecture the host can execute, since its borrowed form comes from
20//! [`SimdView::new`](crate::view::SimdView::new) and its owned constructors
21//! assert the same condition. The second is local — these routines build their
22//! output buffer with `with_capacity` and write it through a raw pointer,
23//! raising the length only once every element is initialized. That avoids both
24//! a zero-fill of a buffer about to be overwritten and any `&mut [T]` spanning
25//! uninitialized elements, so each such site carries a `SAFETY` comment showing
26//! the write coverage. `gather` and `prefix_scan` reserve capacity and fill it
27//! through the view's `*_into_uninit` methods over
28//! [`AlignedVec::spare_capacity_mut`](crate::vec::AlignedVec::spare_capacity_mut),
29//! then raise the length once those report success, so those paths never zero
30//! the buffer either.
31
32use super::SimdCow;
33use crate::align::Alignment;
34use crate::arch::SimdArch;
35use crate::kernel::SimdKernel;
36use crate::ops::UnaryOp;
37use crate::scalar::Scalar;
38use crate::vec::AlignedVec;
39use crate::view::SimdError;
40
41// ---------------------------------------------------------------------------
42// map_cow — generic unary op
43// ---------------------------------------------------------------------------
44
45impl<'a, T: 'a, Arch, Align> SimdCow<'a, T, Arch, Align>
46where
47 T: Scalar,
48 Arch: SimdArch + SimdKernel<T>,
49 Align: Alignment,
50{
51 /// Apply a zero-sized `UnaryOp` to every element, returning a new owned `SimdCow`.
52 ///
53 /// One allocation. The input is unchanged.
54 ///
55 /// # Example
56 /// ```rust,ignore
57 /// let abs_cow = cow.map_cow(ops::Abs);
58 /// let neg_cow = cow.map_cow(ops::Neg);
59 /// let sqrt_cow = cow.map_cow(ops::Sqrt);
60 /// ```
61 #[inline]
62 pub fn map_cow<Op: UnaryOp<T>>(&self, op: Op) -> SimdCow<'static, T, Arch, Align> {
63 let data = self.as_ref();
64 let len = data.len();
65 let mut out: AlignedVec<T, Align> = AlignedVec::with_capacity(len);
66
67 let lane_count = Arch::LANE_COUNT;
68 let simd_len = (len / lane_count) * lane_count;
69 let ptr_in = data.as_ptr();
70 let ptr_out = out.as_mut_ptr();
71
72 // SAFETY: `with_capacity(len)` reserved `len` elements, so writes below
73 // `len` stay inside the allocation, and `ptr_in` covers the same `len`
74 // elements. The vector's length is raised only once both loops have
75 // written every element, so no reference ever spans uninitialized
76 // memory and nothing observes the buffer before it is complete.
77 unsafe {
78 let load = |p: *const T| -> Arch::Vector {
79 if crate::align::is_aligned_for_arch::<Arch, Align>() {
80 Arch::load_aligned(p)
81 } else {
82 Arch::load_unaligned(p)
83 }
84 };
85 let store = |p: *mut T, v: Arch::Vector| {
86 if crate::align::is_aligned_for_arch::<Arch, Align>() {
87 Arch::store_aligned(p, v);
88 } else {
89 Arch::store_unaligned(p, v);
90 }
91 };
92 let mut i = 0usize;
93 while i < simd_len {
94 let v = load(ptr_in.add(i));
95 let r = UnaryOp::apply::<Arch>(op, v);
96 store(ptr_out.add(i), r);
97 i += lane_count;
98 }
99 for i in simd_len..len {
100 core::ptr::write(ptr_out.add(i), UnaryOp::apply_scalar(op, *ptr_in.add(i)));
101 }
102 out.set_len(len);
103 }
104
105 SimdCow::Owned(out)
106 }
107
108 /// Fused multiply-add: `out[i] = self[i] * b[i] + c[i]`.
109 ///
110 /// Uses `Arch::fmadd` in the SIMD region and `T::scalar_fmadd` in the tail.
111 /// One allocation. Returns `Err(SimdError::LengthMismatch)` if lengths differ.
112 #[inline]
113 pub fn fma_cow(
114 &self,
115 b: &SimdCow<'_, T, Arch, Align>,
116 c: &SimdCow<'_, T, Arch, Align>,
117 ) -> Result<SimdCow<'static, T, Arch, Align>, SimdError> {
118 let data_a = self.as_ref();
119 let data_b = b.as_ref();
120 let data_c = c.as_ref();
121
122 let len = data_a.len();
123 if len != data_b.len() || len != data_c.len() {
124 return Err(SimdError::LengthMismatch);
125 }
126
127 let mut out: AlignedVec<T, Align> = AlignedVec::with_capacity(len);
128
129 let lane_count = Arch::LANE_COUNT;
130 let simd_len = (len / lane_count) * lane_count;
131 let ptr_a = data_a.as_ptr();
132 let ptr_b = data_b.as_ptr();
133 let ptr_c = data_c.as_ptr();
134 let ptr_o = out.as_mut_ptr();
135
136 // SAFETY: `with_capacity(len)` reserved `len` elements and the three
137 // inputs were length-checked above, so every access below stays inside
138 // its allocation. The vector's length is raised only after both loops
139 // have written every element, so no reference spans uninitialized
140 // memory and nothing observes the buffer before it is complete.
141 unsafe {
142 let load = |p: *const T| -> Arch::Vector {
143 if crate::align::is_aligned_for_arch::<Arch, Align>() {
144 Arch::load_aligned(p)
145 } else {
146 Arch::load_unaligned(p)
147 }
148 };
149 let store = |p: *mut T, v: Arch::Vector| {
150 if crate::align::is_aligned_for_arch::<Arch, Align>() {
151 Arch::store_aligned(p, v);
152 } else {
153 Arch::store_unaligned(p, v);
154 }
155 };
156 let mut i = 0usize;
157 while i < simd_len {
158 let va = load(ptr_a.add(i));
159 let vb = load(ptr_b.add(i));
160 let vc = load(ptr_c.add(i));
161 let vr = Arch::fmadd(va, vb, vc);
162 store(ptr_o.add(i), vr);
163 i += lane_count;
164 }
165 for i in simd_len..len {
166 let value = *ptr_a.add(i) * *ptr_b.add(i) + *ptr_c.add(i);
167 core::ptr::write(ptr_o.add(i), value);
168 }
169 out.set_len(len);
170 }
171
172 Ok(SimdCow::Owned(out))
173 }
174}
175
176// Unit tests moved to integration tests in crates/hermes-simd/tests/select_unary_tests.rs