Skip to main content

lightweight_simd/
ops.rs

1// Most or all contents generated by AI (model glm-5.3)
2
3//! Lane-wise operators, float functions, comparisons, and masks for the vector
4//! types ([`Simd`] and its [`aligned`](crate::aligned) siblings).
5
6use core::ops::*;
7#[cfg(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma"))]
8use num_traits::MulAdd;
9use num_traits::{Float, Num, NumAssignOps};
10
11use crate::{Aligned4, Aligned8, Aligned16, Aligned32, Aligned64, Simd};
12
13impl<const N: usize> Simd<bool, N> {
14    /// Lane-wise select: picks lanes of `if_true` where the mask is `true` and
15    /// lanes of `if_false` where it is `false`.
16    #[inline(always)]
17    pub fn select<T: Copy>(self, if_true: Simd<T, N>, if_false: Simd<T, N>) -> Simd<T, N> {
18        let mut out = if_true.0;
19        for ((o, f), m) in out.iter_mut().zip(if_false.0).zip(self.0) {
20            if !m {
21                *o = f;
22            }
23        }
24        Simd(out)
25    }
26
27    /// Returns `true` if any lane is `true`.
28    #[inline(always)]
29    pub fn any_true(self) -> bool {
30        self.0.iter().any(|b| *b)
31    }
32
33    /// Returns `true` if all lanes are `true`.
34    #[inline(always)]
35    pub fn all_true(self) -> bool {
36        self.0.iter().all(|b| *b)
37    }
38}
39
40#[duplicate::duplicate_item(
41    _mod_        _vec_;
42    [simd_impls] [Simd];
43    [impls_a4]   [Aligned4];
44    [impls_a8]   [Aligned8];
45    [impls_a16]  [Aligned16];
46    [impls_a32]  [Aligned32];
47    [impls_a64]  [Aligned64];
48)]
49mod _mod_ {
50    use super::*;
51
52    /* #region arithmetic operators */
53
54    #[duplicate::duplicate_item(
55        _trait_ _method_;
56        [Add]  [add] ;
57        [Sub]  [sub] ;
58        [Mul]  [mul] ;
59        [Div]  [div] ;
60    )]
61    impl<T, const N: usize> _trait_ for _vec_<T, N>
62    where
63        T: Num + Copy,
64    {
65        type Output = Self;
66
67        #[inline(always)]
68        fn _method_(self, rhs: Self) -> Self {
69            let mut out = self.0;
70            for (o, r) in out.iter_mut().zip(rhs.0) {
71                *o = _trait_::_method_(*o, r);
72            }
73            Self(out)
74        }
75    }
76
77    #[duplicate::duplicate_item(
78        _trait_ _method_;
79        [Add]  [add] ;
80        [Sub]  [sub] ;
81        [Mul]  [mul] ;
82        [Div]  [div] ;
83    )]
84    impl<T, const N: usize> _trait_<T> for _vec_<T, N>
85    where
86        T: Num + Copy,
87    {
88        type Output = Self;
89
90        #[inline(always)]
91        fn _method_(self, rhs: T) -> Self {
92            let mut out = self.0;
93            for o in out.iter_mut() {
94                *o = _trait_::_method_(*o, rhs);
95            }
96            Self(out)
97        }
98    }
99
100    #[duplicate::duplicate_item(
101        _trait_ _method_;
102        [AddAssign] [add_assign];
103        [SubAssign] [sub_assign];
104        [MulAssign] [mul_assign];
105        [DivAssign] [div_assign];
106    )]
107    impl<T, const N: usize> _trait_ for _vec_<T, N>
108    where
109        T: NumAssignOps + Copy,
110    {
111        #[inline(always)]
112        fn _method_(&mut self, rhs: Self) {
113            for (o, r) in self.0.iter_mut().zip(rhs.0) {
114                _trait_::_method_(o, r);
115            }
116        }
117    }
118
119    #[duplicate::duplicate_item(
120        _trait_ _method_;
121        [AddAssign] [add_assign];
122        [SubAssign] [sub_assign];
123        [MulAssign] [mul_assign];
124        [DivAssign] [div_assign];
125    )]
126    impl<T, const N: usize> _trait_<T> for _vec_<T, N>
127    where
128        T: NumAssignOps + Copy,
129    {
130        #[inline(always)]
131        fn _method_(&mut self, rhs: T) {
132            for o in self.0.iter_mut() {
133                _trait_::_method_(o, rhs);
134            }
135        }
136    }
137
138    impl<T, const N: usize> Neg for _vec_<T, N>
139    where
140        T: Neg<Output = T> + Copy,
141    {
142        type Output = Self;
143
144        #[inline(always)]
145        fn neg(self) -> Self {
146            let mut out = self.0;
147            for (o, x) in out.iter_mut().zip(self.0) {
148                *o = Neg::neg(x);
149            }
150            Self(out)
151        }
152    }
153
154    /* #endregion */
155
156    /* #region bitwise and shift operators */
157
158    #[duplicate::duplicate_item(
159        _trait_ _method_;
160        [BitAnd] [bitand];
161        [BitOr]  [bitor];
162        [BitXor] [bitxor];
163    )]
164    impl<T, const N: usize> _trait_ for _vec_<T, N>
165    where
166        T: _trait_<Output = T> + Copy,
167    {
168        type Output = Self;
169
170        #[inline(always)]
171        fn _method_(self, rhs: Self) -> Self {
172            let mut out = self.0;
173            for (o, r) in out.iter_mut().zip(rhs.0) {
174                *o = _trait_::_method_(*o, r);
175            }
176            Self(out)
177        }
178    }
179
180    #[duplicate::duplicate_item(
181        _trait_ _method_;
182        [BitAndAssign] [bitand_assign];
183        [BitOrAssign]  [bitor_assign];
184        [BitXorAssign] [bitxor_assign];
185    )]
186    impl<T, const N: usize> _trait_ for _vec_<T, N>
187    where
188        T: _trait_ + Copy,
189    {
190        #[inline(always)]
191        fn _method_(&mut self, rhs: Self) {
192            for (o, r) in self.0.iter_mut().zip(rhs.0) {
193                _trait_::_method_(o, r);
194            }
195        }
196    }
197
198    impl<T, const N: usize> Not for _vec_<T, N>
199    where
200        T: Not<Output = T> + Copy,
201    {
202        type Output = Self;
203
204        #[inline(always)]
205        fn not(self) -> Self {
206            let mut out = self.0;
207            for (o, x) in out.iter_mut().zip(self.0) {
208                *o = Not::not(x);
209            }
210            Self(out)
211        }
212    }
213
214    #[duplicate::duplicate_item(
215        _trait_ _method_;
216        [Shl] [shl];
217        [Shr] [shr];
218    )]
219    impl<T, const N: usize> _trait_<usize> for _vec_<T, N>
220    where
221        T: _trait_<usize, Output = T> + Copy,
222    {
223        type Output = Self;
224
225        #[inline(always)]
226        fn _method_(self, rhs: usize) -> Self {
227            let mut out = self.0;
228            for o in out.iter_mut() {
229                *o = _trait_::_method_(*o, rhs);
230            }
231            Self(out)
232        }
233    }
234
235    /* #endregion */
236
237    /* #region fused multiply-add */
238    //
239    // The fused path is selected when the compilation target has hardware
240    // FMA (`target_feature = "fma"`, which follows `-C target-cpu`, i.e. any
241    // x86-64-v3+ or `native` build; on aarch64 scalar/vector FMA is baseline)
242    // or when the `use_libm_fma` feature pins it on. Everything else —
243    // including architectures the predicate does not know — gets the fast
244    // separated form, never the slow libm software `fma`. See docs/adr/0005.
245
246    #[cfg(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma"))]
247    impl<T, const N: usize> _vec_<T, N>
248    where
249        T: MulAdd<Output = T> + Copy,
250    {
251        /// Lane-wise fused multiply-add: `self * b + c` with a single
252        /// rounding per lane.
253        ///
254        /// This is the default on targets with hardware FMA (x86 built with
255        /// `-C target-cpu` of `x86-64-v3` or higher, `native`, and all
256        /// aarch64), where it lowers to one FMA instruction per register
257        /// with no extra flags — and is also faster than a separated
258        /// multiply and add there. The non-default cargo feature
259        /// `use_libm_fma` selects this path on *every* target, accepting a
260        /// correctly-rounded but slow libm `fma` call (~4.6x a separated
261        /// mul+add) where hardware FMA is absent.
262        #[inline(always)]
263        pub fn mul_add(self, b: Self, c: Self) -> Self {
264            let mut out = self.0;
265            for ((o, b), c) in out.iter_mut().zip(b.0).zip(c.0) {
266                *o = MulAdd::mul_add(*o, b, c);
267            }
268            Self(out)
269        }
270
271        /// Lane-wise fused `*self = b * c + *self`, by the receiver.
272        ///
273        /// Note that the operand order differs from [`mul_add`](Self::mul_add):
274        /// the accumulator is the receiver, matching `self += b * c`.
275        #[inline(always)]
276        pub fn fma_from(&mut self, b: Self, c: Self) {
277            for ((b, c), o) in b.0.iter().zip(c.0.iter()).zip(self.0.iter_mut()) {
278                *o = MulAdd::mul_add(*b, *c, *o);
279            }
280        }
281    }
282
283    #[cfg(not(any(feature = "use_libm_fma", target_arch = "aarch64", target_feature = "fma")))]
284    impl<T, const N: usize> _vec_<T, N>
285    where
286        T: Mul<Output = T> + Add<Output = T> + Copy,
287    {
288        /// Lane-wise multiply-add: `self * b + c`, evaluated as a separate
289        /// multiply and add (two roundings per lane).
290        ///
291        /// This is the fallback on targets without hardware FMA (generic
292        /// x86-64, `x86-64-v2`), where it avoids the slow software-fused
293        /// libm `fma` calls; it also fuses into a single hardware FMA
294        /// instruction when compiled with `-C llvm-args=-fp-contract=fast`.
295        /// Enable the `use_libm_fma` feature to get the single-rounding
296        /// fused form on such targets instead.
297        #[inline(always)]
298        pub fn mul_add(self, b: Self, c: Self) -> Self {
299            let mut out = self.0;
300            for ((o, b), c) in out.iter_mut().zip(b.0).zip(c.0) {
301                *o = *o * b + c;
302            }
303            Self(out)
304        }
305
306        /// Lane-wise `*self = b * c + *self`, by the receiver.
307        ///
308        /// Note that the operand order differs from [`mul_add`](Self::mul_add):
309        /// the accumulator is the receiver, matching `self += b * c`. The
310        /// evaluation strategy is the same as [`mul_add`](Self::mul_add)'s.
311        #[inline(always)]
312        pub fn fma_from(&mut self, b: Self, c: Self) {
313            for ((b, c), o) in b.0.iter().zip(c.0.iter()).zip(self.0.iter_mut()) {
314                *o = *b * *c + *o;
315            }
316        }
317    }
318
319    /* #endregion */
320
321    /* #region float functions */
322
323    impl<T, const N: usize> _vec_<T, N>
324    where
325        T: Float + Copy,
326    {
327        #[duplicate::duplicate_item(
328            _name_;
329            [abs];
330            [sqrt];
331            [recip];
332            [floor];
333            [ceil];
334            [round];
335            [trunc];
336            [fract];
337        )]
338        #[doc = concat!("Lane-wise [`Float::", stringify!(_name_), "`].")]
339        #[inline(always)]
340        pub fn _name_(self) -> Self {
341            Self(self.0.map(|x| x._name_()))
342        }
343
344        /// Lane-wise minimum; NaN handling follows the element's scalar
345        /// [`Float::min`].
346        #[inline(always)]
347        pub fn min(self, other: Self) -> Self {
348            let mut out = self.0;
349            for (o, r) in out.iter_mut().zip(other.0) {
350                *o = o.min(r);
351            }
352            Self(out)
353        }
354
355        /// Lane-wise maximum; NaN handling follows the element's scalar
356        /// [`Float::max`].
357        #[inline(always)]
358        pub fn max(self, other: Self) -> Self {
359            let mut out = self.0;
360            for (o, r) in out.iter_mut().zip(other.0) {
361                *o = o.max(r);
362            }
363            Self(out)
364        }
365
366        /// Lane-wise [`Float::copysign`].
367        #[inline(always)]
368        pub fn copysign(self, sign: Self) -> Self {
369            let mut out = self.0;
370            for (o, r) in out.iter_mut().zip(sign.0) {
371                *o = o.copysign(r);
372            }
373            Self(out)
374        }
375    }
376
377    impl<const N: usize> _vec_<f32, N> {
378        /// Lane-wise [`f32::round_ties_even`].
379        ///
380        /// Not available generically through [`num_traits::Float`], so this is
381        /// implemented only for the builtin float types.
382        #[inline(always)]
383        pub fn round_ties_even(self) -> Self {
384            Self(self.0.map(f32::round_ties_even))
385        }
386    }
387
388    impl<const N: usize> _vec_<f64, N> {
389        /// Lane-wise [`f64::round_ties_even`].
390        ///
391        /// Not available generically through [`num_traits::Float`], so this is
392        /// implemented only for the builtin float types.
393        #[inline(always)]
394        pub fn round_ties_even(self) -> Self {
395            Self(self.0.map(f64::round_ties_even))
396        }
397    }
398
399    /* #endregion */
400
401    /* #region comparisons */
402
403    impl<T, const N: usize> _vec_<T, N>
404    where
405        T: PartialOrd + Copy,
406    {
407        /// Lane-wise `self < rhs`.
408        #[inline(always)]
409        pub fn simd_lt(self, rhs: Self) -> Simd<bool, N> {
410            Simd(core::array::from_fn(|i| self.0[i] < rhs.0[i]))
411        }
412
413        /// Lane-wise `self <= rhs`.
414        #[inline(always)]
415        pub fn simd_le(self, rhs: Self) -> Simd<bool, N> {
416            Simd(core::array::from_fn(|i| self.0[i] <= rhs.0[i]))
417        }
418
419        /// Lane-wise `self > rhs`.
420        #[inline(always)]
421        pub fn simd_gt(self, rhs: Self) -> Simd<bool, N> {
422            Simd(core::array::from_fn(|i| self.0[i] > rhs.0[i]))
423        }
424
425        /// Lane-wise `self >= rhs`.
426        #[inline(always)]
427        pub fn simd_ge(self, rhs: Self) -> Simd<bool, N> {
428            Simd(core::array::from_fn(|i| self.0[i] >= rhs.0[i]))
429        }
430    }
431
432    impl<T, const N: usize> _vec_<T, N>
433    where
434        T: PartialEq + Copy,
435    {
436        /// Lane-wise `self == rhs`.
437        #[inline(always)]
438        pub fn simd_eq(self, rhs: Self) -> Simd<bool, N> {
439            Simd(core::array::from_fn(|i| self.0[i] == rhs.0[i]))
440        }
441
442        /// Lane-wise `self != rhs`.
443        #[inline(always)]
444        pub fn simd_ne(self, rhs: Self) -> Simd<bool, N> {
445            Simd(core::array::from_fn(|i| self.0[i] != rhs.0[i]))
446        }
447    }
448
449    /* #endregion */
450}