Skip to main content

fearless_simd/
traits.rs

1// Copyright 2025 the Fearless_SIMD Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4#![expect(
5    missing_docs,
6    reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
7)]
8use crate::{Simd, SimdBase, SimdFloat, SimdInt, SimdMask, seal::Seal};
9use core::error::Error;
10use core::fmt::{Binary, Debug, Display, LowerExp, UpperExp};
11use core::iter::{Product, Sum};
12use core::ops::{
13    Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
14    Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
15};
16use core::str::FromStr;
17
18/// A value that carries a SIMD token.
19///
20/// Implemented by all SIMD tokens, vectors, and masks, and by references to these
21/// types. User-defined wrappers can implement this trait by returning the token
22/// of a contained value.
23///
24/// The [`#[simd]`](https://docs.rs/fearless_simd_macros/latest/fearless_simd_macros/attr.simd.html)
25/// attribute extracts a token from its first non-receiver parameter using this
26/// trait.
27///
28/// ```
29/// use fearless_simd::{ExtractToken, Simd, f32x4};
30///
31/// // Four consecutive audio samples, processed together.
32/// struct AudioSamples<S: Simd>(f32x4<S>);
33///
34/// impl<S: Simd> ExtractToken for AudioSamples<S> {
35///     type S = S;
36///
37///     #[inline]
38///     fn token(&self) -> S {
39///         self.0.token()
40///     }
41/// }
42/// ```
43#[diagnostic::on_unimplemented(
44    message = "`{Self}` does not carry a SIMD token",
45    note = "If you are using #[simd], the first non-receiver parameter must carry a SIMD token. See the #[simd] documentation for more information."
46)]
47pub trait ExtractToken {
48    /// The SIMD implementation associated with this value.
49    type S: Simd;
50
51    /// Get the SIMD token associated with this value.
52    fn token(&self) -> Self::S;
53}
54
55impl<T: ExtractToken + ?Sized> ExtractToken for &T {
56    type S = T::S;
57
58    #[inline]
59    fn token(&self) -> Self::S {
60        T::token(*self)
61    }
62}
63
64impl<T: ExtractToken + ?Sized> ExtractToken for &mut T {
65    type S = T::S;
66
67    #[inline]
68    fn token(&self) -> Self::S {
69        T::token(&**self)
70    }
71}
72
73/// Element-wise selection between two SIMD vectors using `self`.
74pub trait Select<T: Seal>: Seal {
75    /// For each logical lane of this mask, select the first operand if the lane is true, and select the second
76    /// operand if the lane is false.
77    ///
78    /// Masks may be converted to and from signed integer lane arrays for compatibility with older APIs. For those
79    /// conversions, false is encoded as all zeroes (integer value 0) and true is encoded as all ones (integer value -1).
80    /// If a mask is constructed from any other integer bit pattern, the result of this operation is unspecified.
81    fn select(self, if_true: T, if_false: T) -> T;
82}
83
84/// Conversion of SIMD vectors to and from same-width vectors of `u8` lanes.
85///
86/// [`Bytes::bitcast`] uses this byte representation to reinterpret any two
87/// non-mask SIMD vectors with the same total width and SIMD token. This is a
88/// bitwise reinterpretation: it does not perform numeric conversion.
89pub trait Bytes: Sized + Seal {
90    /// The same-width SIMD vector of `u8` lanes used as the byte representation.
91    ///
92    /// This type is its own byte representation.
93    type Bytes: Bytes<Bytes = Self::Bytes>;
94
95    /// Reinterpret this vector as a same-width vector of `u8` lanes.
96    fn to_bytes(self) -> Self::Bytes;
97
98    /// Reinterpret a same-width vector of `u8` lanes as this vector type.
99    fn from_bytes(value: Self::Bytes) -> Self;
100
101    #[doc(alias = "reinterpret")]
102    #[doc(alias = "transmute")]
103    #[doc(alias = "to_bits")]
104    #[doc(alias = "from_bits")]
105    /// Bitcast directly to another SIMD vector with the same byte representation.
106    /// This is effectively a safe [transmute](core::mem::transmute) for SIMD types.
107    ///
108    /// This works in code generic over a [`Simd`] implementation,
109    /// including between native-width vectors with different lane types:
110    ///
111    /// ```
112    /// # use fearless_simd::prelude::*;
113    /// fn i8s_as_f64s<S: Simd>(value: S::i8s) -> S::f64s {
114    ///     value.bitcast()
115    /// }
116    /// ```
117    #[inline(always)]
118    fn bitcast<U: Bytes<Bytes = Self::Bytes>>(self) -> U {
119        U::from_bytes(self.to_bytes())
120    }
121}
122
123pub(crate) mod seal {
124    #[expect(
125        unnameable_types,
126        reason = "This is a sealed trait, so being unnameable is the entire point"
127    )]
128    pub trait Seal {}
129}
130
131impl Seal for f32 {}
132impl Seal for f64 {}
133impl Seal for u8 {}
134impl Seal for i8 {}
135impl Seal for u16 {}
136impl Seal for i16 {}
137impl Seal for u32 {}
138impl Seal for i32 {}
139impl Seal for u64 {}
140impl Seal for i64 {}
141
142/// Value conversion, adding a SIMD blessing.
143///
144/// Analogous to [`From`], but takes a SIMD token, which is used to bless
145/// the new value. Most such conversions are safe transmutes, but this
146/// trait also supports splats, and implementations can use the SIMD token
147/// to use an efficient splat intrinsic.
148///
149/// The [`SimdInto`] trait is also provided for convenience.
150pub trait SimdFrom<T, S: Simd> {
151    fn simd_from(simd: S, value: T) -> Self;
152}
153
154/// Value conversion, adding a SIMD blessing.
155///
156/// This trait is syntactic sugar for [`SimdFrom`] and exists only to allow
157/// `impl SimdInto` syntax in signatures, which would otherwise require
158/// cumbersome `where` clauses in terms of `SimdFrom`.
159///
160/// Avoid implementing this trait directly, prefer implementing [`SimdFrom`].
161pub trait SimdInto<T, S> {
162    fn simd_into(self, simd: S) -> T;
163}
164
165impl<F, T: SimdFrom<F, S>, S: Simd> SimdInto<T, S> for F {
166    #[inline(always)]
167    fn simd_into(self, simd: S) -> T {
168        SimdFrom::simd_from(simd, self)
169    }
170}
171
172impl<T, S: Simd> SimdFrom<T, S> for T {
173    #[inline(always)]
174    fn simd_from(_simd: S, value: T) -> Self {
175        value
176    }
177}
178
179/// Types that can be used as elements in SIMD vectors.
180pub trait SimdElement:
181    Copy
182    + Clone
183    + Seal
184    + Default
185    + Debug
186    + Display
187    + FromStr
188    + LowerExp
189    + UpperExp
190    + PartialOrd
191    + PartialEq
192    + From<bool>
193    + Add<Self, Output = Self>
194    + AddAssign<Self>
195    + Sub<Self, Output = Self>
196    + SubAssign<Self>
197    + Mul<Self, Output = Self>
198    + MulAssign<Self>
199    + Div<Self, Output = Self>
200    + DivAssign<Self>
201    + Rem<Self, Output = Self>
202    + RemAssign<Self>
203    + Sum<Self>
204    + Product<Self>
205    + for<'a> Add<&'a Self, Output = Self>
206    + for<'a> AddAssign<&'a Self>
207    + for<'a> Sub<&'a Self, Output = Self>
208    + for<'a> SubAssign<&'a Self>
209    + for<'a> Mul<&'a Self, Output = Self>
210    + for<'a> MulAssign<&'a Self>
211    + for<'a> Div<&'a Self, Output = Self>
212    + for<'a> DivAssign<&'a Self>
213    + for<'a> Rem<&'a Self, Output = Self>
214    + for<'a> RemAssign<&'a Self>
215    + for<'a> Sum<&'a Self>
216    + for<'a> Product<&'a Self>
217{
218    /// The associated mask lane type. This will be a signed integer of the same size as this type.
219    type Mask: SimdElement<Mask = Self::Mask>;
220
221    /// The size of an element in bits.
222    const BITS: usize = size_of::<Self>() * u8::BITS as usize;
223}
224
225impl SimdElement for f32 {
226    type Mask = i32;
227}
228
229impl SimdElement for f64 {
230    type Mask = i64;
231}
232
233impl SimdElement for u8 {
234    type Mask = i8;
235}
236
237impl SimdElement for i8 {
238    type Mask = Self;
239}
240
241impl SimdElement for u16 {
242    type Mask = i16;
243}
244
245impl SimdElement for i16 {
246    type Mask = Self;
247}
248
249impl SimdElement for u32 {
250    type Mask = i32;
251}
252
253impl SimdElement for i32 {
254    type Mask = Self;
255}
256
257impl SimdElement for u64 {
258    type Mask = i64;
259}
260
261impl SimdElement for i64 {
262    type Mask = Self;
263}
264
265/// Types that can be used as elements in integer SIMD vectors.
266///
267/// [`Self::Native`] selects the native-width vector for this scalar type and a
268/// SIMD backend, so an integer-generic caller can use [`crate::dispatch!`]:
269///
270/// ```
271/// use fearless_simd::{dispatch, prelude::*, Level};
272///
273/// #[inline(always)]
274/// fn count_ones_simd<S: Simd, T: SimdIntElement>(simd: S, value: T) -> T {
275///     T::Native::<S>::splat(simd, value).count_ones()[0]
276/// }
277///
278/// fn count_ones<T: SimdIntElement>(level: Level, value: T) -> T {
279///     dispatch!(level, simd => count_ones_simd(simd, value))
280/// }
281///
282/// let level = Level::new();
283/// assert_eq!(count_ones(level, 7_u8), 3);
284/// assert_eq!(count_ones(level, 7_i64), 3);
285/// ```
286pub trait SimdIntElement:
287    SimdElement
288    + Eq
289    + Ord
290    + Binary
291    + Not<Output = Self>
292    + Shl<usize, Output = Self>
293    + ShlAssign<usize>
294    + Shr<usize, Output = Self>
295    + ShrAssign<usize>
296    + BitAnd<Self, Output = Self>
297    + BitAndAssign<Self>
298    + BitOr<Self, Output = Self>
299    + BitOrAssign<Self>
300    + BitXor<Self, Output = Self>
301    + BitXorAssign<Self>
302    + TryFrom<u8, Error: Copy + Error + Eq>
303    + TryFrom<u16, Error: Copy + Error + Eq>
304    + TryFrom<u32, Error: Copy + Error + Eq>
305    + TryFrom<u64, Error: Copy + Error + Eq>
306    + TryFrom<u128, Error: Copy + Error + Eq>
307    + TryFrom<usize, Error: Copy + Error + Eq>
308    + TryFrom<i8, Error: Copy + Error + Eq>
309    + TryFrom<i16, Error: Copy + Error + Eq>
310    + TryFrom<i32, Error: Copy + Error + Eq>
311    + TryFrom<i64, Error: Copy + Error + Eq>
312    + TryFrom<i128, Error: Copy + Error + Eq>
313    + TryFrom<isize, Error: Copy + Error + Eq>
314    + for<'a> Shl<&'a usize, Output = Self>
315    + for<'a> ShlAssign<&'a usize>
316    + for<'a> Shr<&'a usize, Output = Self>
317    + for<'a> ShrAssign<&'a usize>
318    + for<'a> BitAnd<&'a Self, Output = Self>
319    + for<'a> BitAndAssign<&'a Self>
320    + for<'a> BitOr<&'a Self, Output = Self>
321    + for<'a> BitOrAssign<&'a Self>
322    + for<'a> BitXor<&'a Self, Output = Self>
323    + for<'a> BitXorAssign<&'a Self>
324{
325    /// The native-width integer vector for this scalar type and backend `S`.
326    ///
327    /// Its lane count depends on both the scalar type and the backend, and is
328    /// available as `T::Native::<S>::LEN` with [`SimdBase`] in scope.
329    type Native<S: Simd>: SimdInt<S, Element = Self>;
330}
331
332impl SimdIntElement for u8 {
333    type Native<S: Simd> = S::u8s;
334}
335impl SimdIntElement for u16 {
336    type Native<S: Simd> = S::u16s;
337}
338impl SimdIntElement for u32 {
339    type Native<S: Simd> = S::u32s;
340}
341impl SimdIntElement for u64 {
342    type Native<S: Simd> = S::u64s;
343}
344impl SimdIntElement for i8 {
345    type Native<S: Simd> = S::i8s;
346}
347impl SimdIntElement for i16 {
348    type Native<S: Simd> = S::i16s;
349}
350impl SimdIntElement for i32 {
351    type Native<S: Simd> = S::i32s;
352}
353impl SimdIntElement for i64 {
354    type Native<S: Simd> = S::i64s;
355}
356
357/// Types that can be used as elements in float SIMD vectors.
358///
359/// The scalar conversion bounds are limited to types that every floating-point
360/// element can represent losslessly, including f16 for forward-compatibility.
361///
362/// [`Self::Native`] selects the native-width vector for this scalar type and a
363/// SIMD backend, so a float-generic caller can use [`crate::dispatch!`]:
364///
365/// ```
366/// use fearless_simd::{dispatch, prelude::*, Level};
367///
368/// #[inline(always)]
369/// fn sqrt_simd<S: Simd, T: SimdFloatElement>(simd: S, value: T) -> T {
370///     T::Native::<S>::splat(simd, value).sqrt()[0]
371/// }
372///
373/// fn sqrt<T: SimdFloatElement>(level: Level, value: T) -> T {
374///     dispatch!(level, simd => sqrt_simd(simd, value))
375/// }
376///
377/// let level = Level::new();
378/// assert_eq!(sqrt(level, 4.0_f32), 2.0);
379/// assert_eq!(sqrt(level, 4.0_f64), 2.0);
380/// ```
381pub trait SimdFloatElement: SimdElement + Neg<Output = Self> + From<i8> + From<u8> {
382    /// The native-width floating-point vector for this scalar type and backend `S`.
383    ///
384    /// Its lane count depends on both the scalar type and the backend, and is
385    /// available as `T::Native::<S>::LEN` with [`SimdBase`] in scope.
386    type Native<S: Simd>: SimdFloat<S, Element = Self>;
387    /// The scalar bit representation of this floating-point type.
388    type Bits: SimdIntElement;
389}
390
391impl SimdFloatElement for f32 {
392    type Native<S: Simd> = S::f32s;
393    type Bits = u32;
394}
395impl SimdFloatElement for f64 {
396    type Native<S: Simd> = S::f64s;
397    type Bits = u64;
398}
399
400/// Construction of integer vectors from floats by truncation
401pub trait SimdCvtTruncate<T: Seal>: Seal {
402    fn truncate_from(x: T) -> Self;
403    fn truncate_from_precise(x: T) -> Self;
404}
405
406/// Construction of floating point vectors from integers
407pub trait SimdCvtFloat<T: Seal>: Seal {
408    fn float_from(x: T) -> Self;
409}
410
411/// Interleaved loads and stores for SIMD vectors.
412///
413/// This trait is currently implemented only for numeric 128-bit vector types.
414/// These operations load/store up to 512 bits in one go, and loading into wider vectors
415/// usually degrades performance by causing more register pressure.
416///
417/// If processing in wider vectors is desirable, combine the 128-bit vectors into larger ones
418/// and process them together as a single vector. This avoids issues with register pressure.
419///
420/// # Example
421///
422/// This generic color transform swaps the red and blue channels of RGBA pixels:
423///
424/// ```
425/// use fearless_simd::prelude::*;
426///
427/// fn swap_red_blue<S: Simd, V: SimdInterleaved<S>>(
428///     simd: S,
429///     pixels: &mut [V::Element],
430/// ) {
431///     let mut chunks = pixels.chunks_exact_mut(V::LEN * 4);
432///     for chunk in &mut chunks {
433///         let [red, green, blue, alpha] = V::load_four_interleaved(simd, chunk);
434///         V::store_four_interleaved([blue, green, red, alpha], chunk);
435///     }
436///
437///     for pixel in chunks.into_remainder().chunks_exact_mut(4) {
438///         pixel.swap(0, 2);
439///     }
440/// }
441/// ```
442pub trait SimdInterleaved<S: Simd>: SimdBase<S> {
443    /// Load four 128-bit vectors from a slice with 4-way interleaving.
444    ///
445    /// This is useful e.g. in image processing to turn interleaved RGBA pixels into vectors of each color component.
446    ///
447    /// For example, with 32-bit lanes, memory laid out as
448    /// `[r0, g0, b0, a0, r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3]` loads as
449    /// `[[r0, r1, r2, r3], [g0, g1, g2, g3], [b0, b1, b2, b3], [a0, a1, a2, a3]]`.
450    ///
451    /// # Panics
452    ///
453    /// Panics unless `src.len()` is exactly `Self::LEN * 4`.
454    fn load_four_interleaved(simd: S, src: &[Self::Element]) -> [Self; 4];
455
456    /// Store four vectors into a scalar slice with four-way interleaving.
457    ///
458    /// This is the inverse of [`load_four_interleaved`](Self::load_four_interleaved).
459    ///
460    /// This is useful e.g. in image processing to turn vectors of each color component into interleaved RGBA pixels.
461    /// For example, with 32-bit lanes, vectors containing
462    /// `[[r0, r1, r2, r3], [g0, g1, g2, g3], [b0, b1, b2, b3], [a0, a1, a2, a3]]` get stored as
463    /// `[r0, g0, b0, a0, r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3]`.
464    ///
465    /// # Panics
466    ///
467    /// Panics unless `dest.len()` is exactly `Self::LEN * 4`.
468    fn store_four_interleaved(vectors: [Self; 4], dest: &mut [Self::Element]);
469}
470
471/// Concatenation of two SIMD vectors.
472///
473/// This is implemented on all vectors 256 bits and lower, producing vectors of up to 512 bits.
474pub trait SimdCombine<S: Simd>: SimdBase<S> + Seal {
475    type Combined: SimdBase<S, Element = Self::Element, Block = Self::Block>
476        + SimdSplit<S, Split = Self>;
477
478    /// Concatenate two vectors into a new one that's twice as long.
479    fn combine(self, rhs: impl SimdInto<Self, S>) -> Self::Combined;
480}
481
482/// Splitting of one SIMD vector into two.
483///
484/// This is implemented on all vectors 256 bits and higher, producing vectors of down to 128 bits.
485pub trait SimdSplit<S: Simd>: SimdBase<S> + Seal {
486    type Split: SimdBase<S, Element = Self::Element, Block = Self::Block>
487        + SimdCombine<S, Combined = Self>;
488
489    /// Split this vector into left and right halves.
490    fn split(self) -> (Self::Split, Self::Split);
491}
492
493/// Widening conversion of a SIMD mask, preserving each lane's boolean value.
494///
495/// The results have the same logical bit width as the input, with lanes twice as wide and half
496/// as many lanes. The first result contains the lower lanes and the second contains the upper
497/// lanes. This is supported for 8-, 16-, and 32-bit lanes at logical widths of 128, 256, and 512
498/// bits, independently of the backend's physical mask representation.
499///
500/// Masks constructed from integer lanes other than 0 or -1 have unspecified behavior.
501pub trait MaskWiden<S: Simd>: SimdMask<S> + Seal {
502    /// The same logical-width mask type with lanes twice as wide.
503    type Widened: MaskNarrow<S, Narrowed = Self>;
504
505    /// Widen each boolean lane, returning the lower and upper halves in that order.
506    fn widen(self) -> (Self::Widened, Self::Widened);
507}
508
509/// Narrowing conversion of two SIMD masks, preserving each lane's boolean value.
510///
511/// Both inputs have the same logical bit width as the result. The result has lanes half as
512/// wide and twice as many lanes: `self` supplies the lower lanes and `high` supplies the upper
513/// lanes. This is supported for 16-, 32-, and 64-bit lanes at logical widths of 128, 256, and 512
514/// bits, independently of the backend's physical mask representation.
515///
516/// This is the inverse of [`MaskWiden::widen`]. Masks constructed from integer lanes other than
517/// 0 or -1 have unspecified behavior.
518pub trait MaskNarrow<S: Simd>: SimdMask<S> + Seal {
519    /// The same logical-width mask type with lanes half as wide.
520    type Narrowed: MaskWiden<S, Widened = Self>;
521
522    /// Narrow each boolean lane and concatenate `self` followed by `high`.
523    fn narrow(self, high: Self) -> Self::Narrowed;
524}
525
526/// Widening conversion of a numeric SIMD vector.
527///
528/// Integer lanes are sign-extended or zero-extended according to their type. Finite floating-point
529/// lanes are converted losslessly from `f32` to `f64`; infinities and NaNs remain infinities and
530/// NaNs. The result is returned as two vectors with the same bit width as the input: the first
531/// contains the widened lower lanes and the second contains the widened upper lanes.
532///
533/// ```
534/// use fearless_simd::{f32x4, f64x2, prelude::*, u8x16, u16x8};
535///
536/// fn fixed<S: Simd>(value: u8x16<S>) -> (u16x8<S>, u16x8<S>) {
537///     value.widen()
538/// }
539///
540/// fn native<S: Simd>(value: S::u8s) -> (S::u16s, S::u16s) {
541///     value.widen()
542/// }
543///
544/// fn fixed_float<S: Simd>(value: f32x4<S>) -> (f64x2<S>, f64x2<S>) {
545///     value.widen()
546/// }
547///
548/// fn native_float<S: Simd>(value: S::f32s) -> (S::f64s, S::f64s) {
549///     value.widen()
550/// }
551/// ```
552pub trait SimdWiden<S: Simd>:
553    SimdBase<S, Mask: MaskWiden<S, Widened = <Self::Widened as SimdBase<S>>::Mask>> + Seal
554{
555    /// The same-width vector type with lanes twice as wide.
556    type Widened: SimdNarrow<S, Narrowed = Self>;
557
558    /// Widen every lane, returning the lower and upper halves in that order.
559    fn widen(self) -> (Self::Widened, Self::Widened);
560}
561
562/// Widening arithmetic on integer SIMD vectors.
563///
564/// Each output vector has twice-wide lanes and the same total bit width as the inputs.
565/// [`Self::widening_mul`] and [`Self::widening_add`] return the lower and upper halves
566/// as two vectors, just like [`SimdWiden::widen`]. The pairwise operations combine
567/// adjacent lanes into a single vector with half as many lanes.
568pub trait SimdIntWiden<S: Simd>: SimdInt<S> + SimdWiden<S, Widened: SimdInt<S>> {
569    /// Multiply corresponding lanes exactly, returning the lower and upper halves.
570    ///
571    /// # Examples
572    ///
573    /// ```
574    /// # use fearless_simd::*;
575    /// # dispatch!(Level::new(), simd => {
576    /// let a = i16x8::from_slice(simd, &[100, 200, 300, 400, 500, 600, 700, 800]);
577    /// let (lower, upper) = a.widening_mul(10_000);
578    /// assert_eq!(*lower, [1_000_000, 2_000_000, 3_000_000, 4_000_000]);
579    /// assert_eq!(*upper, [5_000_000, 6_000_000, 7_000_000, 8_000_000]);
580    /// # });
581    /// ```
582    ///
583    fn widening_mul(self, rhs: impl SimdInto<Self, S>) -> (Self::Widened, Self::Widened);
584
585    /// Add corresponding lanes exactly, returning the lower and upper halves.
586    ///
587    /// # Examples
588    ///
589    /// ```
590    /// # use fearless_simd::*;
591    /// # dispatch!(Level::new(), simd => {
592    /// let a = u16x8::from_slice(simd, &[1_000, 2_000, 3_000, 4_000, 5_000, 6_000, 7_000, 8_000]);
593    /// let (lower, upper) = a.widening_add(60_000);
594    /// assert_eq!(*lower, [61_000, 62_000, 63_000, 64_000]);
595    /// assert_eq!(*upper, [65_000, 66_000, 67_000, 68_000]);
596    /// # });
597    /// ```
598    fn widening_add(self, rhs: impl SimdInto<Self, S>) -> (Self::Widened, Self::Widened);
599
600    /// Add adjacent integer lanes, widening the result to avoid overflow.
601    ///
602    /// Output lane `i` is `widen(self[2*i]) + widen(self[2*i+1])`. The result has
603    /// half as many lanes, twice the lane width, and the same signedness and total
604    /// vector bit width as the input.
605    ///
606    /// # Examples
607    ///
608    /// ```
609    /// # use fearless_simd::*;
610    /// # dispatch!(Level::new(), simd => {
611    /// let a = i16x8::from_slice(simd, &[1, 2, 3, 4, 5, 6, 7, 8]);
612    /// //                                \  /  \  /  \  /  \  /
613    /// //                                 3     7     11    15
614    /// let pairs: i32x4<_> = a.pairwise_widening_add();
615    /// assert_eq!(*pairs, [3, 7, 11, 15]);
616    /// # });
617    /// ```
618    ///
619    /// Works on native-width vectors too:
620    /// ```
621    /// # use fearless_simd::*;
622    /// fn native<S: Simd>(a: S::u8s) -> S::u16s {
623    ///     a.pairwise_widening_add()
624    /// }
625    /// ```
626    fn pairwise_widening_add(self) -> Self::Widened;
627
628    /// Multiply corresponding lanes, then add adjacent pairs of products.
629    ///
630    /// Each adjacent pair of lanes is an independent two-element dot product. The result
631    /// has half as many lanes, each twice as wide, with the same signedness and total bit
632    /// width as the inputs. For inputs `a` and `b`, output lane `i` is:
633    ///
634    /// ```text
635    /// result[i] = widen(a[2*i]) * widen(b[2*i])
636    ///           + widen(a[2*i+1]) * widen(b[2*i+1])
637    /// ```
638    ///
639    /// For example:
640    ///
641    /// ```text
642    /// a      = [ 1,  2,   3,  4,   5,  6,   7,  8]
643    ///            |   |    |   |    |   |    |   |
644    ///            |---|    |---|    |---|    |---|
645    ///            |   |    |   |    |   |    |   |
646    /// b      = [10, 20,  30, 40,  50, 60,  70, 80]
647    /// result = [    50,     250,     610,    1130]
648    /// ```
649    ///
650    /// Here, `1*10 + 2*20 = 50`, `3*30 + 4*40 = 250`, and so on.
651    ///
652    /// Multiplication is exact in the widened type. Addition wraps in the widened type.
653    ///
654    /// # Examples
655    ///
656    /// ```
657    /// # use fearless_simd::*;
658    /// # dispatch!(Level::new(), simd => {
659    /// let a = i16x8::from_slice(simd, &[ 1,  2,  3,  4,  5,  6,  7,  8]);
660    /// let b = i16x8::from_slice(simd, &[10, 20, 30, 40, 50, 60, 70, 80]);
661    /// let result: i32x4<_> = a.pairwise_dot_product(b);
662    /// assert_eq!(*result, [50, 250, 610, 1130]);
663    ///
664    /// let min = i8x16::splat(simd, i8::MIN /* = -128 */);
665    /// assert_eq!(*min.pairwise_dot_product(min), [i16::MIN /* = -32768 */; 8]);
666    /// # });
667    /// ```
668    ///
669    /// Works on native-width vectors too:
670    /// ```
671    /// # use fearless_simd::*;
672    /// fn native<S: Simd>(a: S::i16s, b: S::i16s) -> S::i32s {
673    ///     a.pairwise_dot_product(b)
674    /// }
675    /// ```
676    fn pairwise_dot_product(self, rhs: impl SimdInto<Self, S>) -> Self::Widened;
677}
678
679/// Narrowing conversion of two numeric SIMD vectors.
680///
681/// Both inputs have the same bit width as the result. The first input supplies the lower result
682/// lanes and the second input supplies the upper result lanes. Integer narrowing either retains
683/// the low destination-width bits or saturates, depending on the method. Floating-point narrowing
684/// converts `f64` to `f32` using Rust's `as` semantics; for floats,
685/// [`saturating_narrow`](Self::saturating_narrow) and
686/// [`relaxed_narrow`](Self::relaxed_narrow) are identical to [`narrow`](Self::narrow).
687///
688/// ```
689/// use fearless_simd::{f32x4, f64x2, prelude::*, i16x8, i8x16};
690///
691/// fn fixed<S: Simd>(low: i16x8<S>, high: i16x8<S>) -> i8x16<S> {
692///     low.narrow(high)
693/// }
694///
695/// fn native<S: Simd>(low: S::i16s, high: S::i16s) -> S::i8s {
696///     low.saturating_narrow(high)
697/// }
698///
699/// fn fixed_float<S: Simd>(low: f64x2<S>, high: f64x2<S>) -> f32x4<S> {
700///     low.narrow(high)
701/// }
702///
703/// fn native_float<S: Simd>(low: S::f64s, high: S::f64s) -> S::f32s {
704///     low.saturating_narrow(high)
705/// }
706/// ```
707pub trait SimdNarrow<S: Simd>:
708    SimdBase<S, Mask: MaskNarrow<S, Narrowed = <Self::Narrowed as SimdBase<S>>::Mask>> + Seal
709{
710    /// The same-width vector type with lanes half as wide.
711    type Narrowed: SimdWiden<S, Widened = Self>;
712
713    /// Narrow every lane.
714    ///
715    /// This conversion behaves identically to the `as` operator:
716    ///  - Integers are truncated.
717    ///  - Floating-point values follow IEEE 754 narrowing behavior in round-to-even mode:
718    ///    they are rounded to the nearest representable `f32`, with ties resolved to even; overflow produces signed infinity.
719    ///
720    /// # Example
721    ///
722    /// ```
723    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
724    ///
725    /// let level = Level::new();
726    /// dispatch!(level, simd => {
727    ///     let low = i64x2::simd_from(simd, [1, -1]);
728    ///     let high = i64x2::simd_from(simd, [i64::MAX - 5, i64::MIN + 5]);
729    ///     let narrowed: i32x4<_> = low.narrow(high);
730    ///     assert_eq!(*narrowed, [1, -1, -6, 5]);
731    /// });
732    /// ```
733    fn narrow(self, high: Self) -> Self::Narrowed;
734
735    /// Narrow with saturation for integers. Floats behave identically to [`narrow`](Self::narrow).
736    ///
737    /// Integer values that overflow the narrowed type become the closest representable value for the narrowed type.
738    /// For example, `1234u16` becomes `u8::MAX` after narrowing, and `-1234i16` becomes `i8::MIN`.
739    ///
740    /// # Example
741    ///
742    /// ```
743    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
744    ///
745    /// let level = Level::new();
746    /// dispatch!(level, simd => {
747    ///     let low = i64x2::simd_from(simd, [1, -1]);
748    ///     let high = i64x2::simd_from(simd, [i64::MAX - 5, i64::MIN + 5]);
749    ///     let narrowed: i32x4<_> = low.saturating_narrow(high);
750    ///     assert_eq!(*narrowed, [1, -1, i32::MAX, i32::MIN]);
751    /// });
752    fn saturating_narrow(self, high: Self) -> Self::Narrowed;
753
754    /// Narrow using the cheapest operation for the active SIMD backend, assuming no overflow.
755    ///
756    /// This is useful when you're sure the result fits into the destination type,
757    /// so the distinction between [`narrow`](Self::narrow) and [`saturating_narrow`](Self::saturating_narrow)
758    /// doesn't matter.
759    ///
760    /// This method will panic in debug mode if any of the inputs do not fit into the narrower type.
761    /// This operation remains memory-safe and never causes undefined behavior,
762    /// but will produce arbitrary values on overflow in release mode.
763    ///
764    /// Floats behave identically to [`narrow`](Self::narrow), with no additional precondition.
765    ///
766    /// # Example
767    ///
768    /// ```
769    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
770    ///
771    /// let level = Level::new();
772    /// dispatch!(level, simd => {
773    ///     let low = i64x2::simd_from(simd, [1, -1]);
774    ///     let high = i64x2::simd_from(simd, [5, -5]);
775    ///     let narrowed: i32x4<_> = low.relaxed_narrow(high);
776    ///     assert_eq!(*narrowed, [1, -1, 5, -5]);
777    /// });
778    fn relaxed_narrow(self, high: Self) -> Self::Narrowed;
779}