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}