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, seal::Seal};
9use core::fmt::{Binary, Debug, Display, LowerExp, UpperExp};
10use core::iter::{Product, Sum};
11use core::ops::{
12    Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
13    Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
14};
15use core::str::FromStr;
16
17/// Element-wise selection between two SIMD vectors using `self`.
18pub trait Select<T: Seal>: Seal {
19    /// For each logical lane of this mask, select the first operand if the lane is true, and select the second
20    /// operand if the lane is false.
21    ///
22    /// Masks may be converted to and from signed integer lane arrays for compatibility with older APIs. For those
23    /// conversions, false is encoded as all zeroes (integer value 0) and true is encoded as all ones (integer value -1).
24    /// If a mask is constructed from any other integer bit pattern, the result of this operation is unspecified.
25    fn select(self, if_true: T, if_false: T) -> T;
26}
27
28/// Conversion of SIMD vectors to and from same-width vectors of `u8` lanes.
29///
30/// [`Bytes::bitcast`] uses this byte representation to reinterpret any two
31/// non-mask SIMD vectors with the same total width and SIMD token. This is a
32/// bitwise reinterpretation: it does not perform numeric conversion.
33pub trait Bytes: Sized + Seal {
34    /// The same-width SIMD vector of `u8` lanes used as the byte representation.
35    ///
36    /// This type is its own byte representation.
37    type Bytes: Bytes<Bytes = Self::Bytes>;
38
39    /// Reinterpret this vector as a same-width vector of `u8` lanes.
40    fn to_bytes(self) -> Self::Bytes;
41
42    /// Reinterpret a same-width vector of `u8` lanes as this vector type.
43    fn from_bytes(value: Self::Bytes) -> Self;
44
45    #[doc(alias = "reinterpret")]
46    #[doc(alias = "transmute")]
47    /// Bitcast directly to another SIMD vector with the same byte representation.
48    /// This is effectively a safe [transmute](core::mem::transmute) for SIMD types.
49    ///
50    /// This works in code generic over a [`Simd`] implementation,
51    /// including between native-width vectors with different lane types:
52    ///
53    /// ```
54    /// # use fearless_simd::prelude::*;
55    /// fn i8s_as_f64s<S: Simd>(value: S::i8s) -> S::f64s {
56    ///     value.bitcast()
57    /// }
58    /// ```
59    #[inline(always)]
60    fn bitcast<U: Bytes<Bytes = Self::Bytes>>(self) -> U {
61        U::from_bytes(self.to_bytes())
62    }
63}
64
65pub(crate) mod seal {
66    #[expect(
67        unnameable_types,
68        reason = "This is a sealed trait, so being unnameable is the entire point"
69    )]
70    pub trait Seal {}
71}
72
73impl Seal for f32 {}
74impl Seal for f64 {}
75impl Seal for u8 {}
76impl Seal for i8 {}
77impl Seal for u16 {}
78impl Seal for i16 {}
79impl Seal for u32 {}
80impl Seal for i32 {}
81impl Seal for u64 {}
82impl Seal for i64 {}
83
84/// Value conversion, adding a SIMD blessing.
85///
86/// Analogous to [`From`], but takes a SIMD token, which is used to bless
87/// the new value. Most such conversions are safe transmutes, but this
88/// trait also supports splats, and implementations can use the SIMD token
89/// to use an efficient splat intrinsic.
90///
91/// The [`SimdInto`] trait is also provided for convenience.
92pub trait SimdFrom<T, S: Simd> {
93    fn simd_from(simd: S, value: T) -> Self;
94}
95
96/// Value conversion, adding a SIMD blessing.
97///
98/// This trait is syntactic sugar for [`SimdFrom`] and exists only to allow
99/// `impl SimdInto` syntax in signatures, which would otherwise require
100/// cumbersome `where` clauses in terms of `SimdFrom`.
101///
102/// Avoid implementing this trait directly, prefer implementing [`SimdFrom`].
103pub trait SimdInto<T, S> {
104    fn simd_into(self, simd: S) -> T;
105}
106
107impl<F, T: SimdFrom<F, S>, S: Simd> SimdInto<T, S> for F {
108    fn simd_into(self, simd: S) -> T {
109        SimdFrom::simd_from(simd, self)
110    }
111}
112
113impl<T, S: Simd> SimdFrom<T, S> for T {
114    fn simd_from(_simd: S, value: T) -> Self {
115        value
116    }
117}
118
119/// Types that can be used as elements in SIMD vectors.
120pub trait SimdElement:
121    Copy
122    + Clone
123    + Seal
124    + Default
125    + Debug
126    + Display
127    + FromStr
128    + LowerExp
129    + UpperExp
130    + PartialOrd
131    + PartialEq
132    + From<bool>
133    + Add<Self, Output = Self>
134    + AddAssign<Self>
135    + Sub<Self, Output = Self>
136    + SubAssign<Self>
137    + Mul<Self, Output = Self>
138    + MulAssign<Self>
139    + Div<Self, Output = Self>
140    + DivAssign<Self>
141    + Rem<Self, Output = Self>
142    + RemAssign<Self>
143    + Sum<Self>
144    + Product<Self>
145    + for<'a> Add<&'a Self, Output = Self>
146    + for<'a> AddAssign<&'a Self>
147    + for<'a> Sub<&'a Self, Output = Self>
148    + for<'a> SubAssign<&'a Self>
149    + for<'a> Mul<&'a Self, Output = Self>
150    + for<'a> MulAssign<&'a Self>
151    + for<'a> Div<&'a Self, Output = Self>
152    + for<'a> DivAssign<&'a Self>
153    + for<'a> Rem<&'a Self, Output = Self>
154    + for<'a> RemAssign<&'a Self>
155    + for<'a> Sum<&'a Self>
156    + for<'a> Product<&'a Self>
157{
158    /// The associated mask lane type. This will be a signed integer of the same size as this type.
159    type Mask: SimdElement<Mask = Self::Mask>;
160
161    /// The size of an element in bits.
162    const BITS: usize = size_of::<Self>() * u8::BITS as usize;
163}
164
165impl SimdElement for f32 {
166    type Mask = i32;
167}
168
169impl SimdElement for f64 {
170    type Mask = i64;
171}
172
173impl SimdElement for u8 {
174    type Mask = i8;
175}
176
177impl SimdElement for i8 {
178    type Mask = Self;
179}
180
181impl SimdElement for u16 {
182    type Mask = i16;
183}
184
185impl SimdElement for i16 {
186    type Mask = Self;
187}
188
189impl SimdElement for u32 {
190    type Mask = i32;
191}
192
193impl SimdElement for i32 {
194    type Mask = Self;
195}
196
197impl SimdElement for u64 {
198    type Mask = i64;
199}
200
201impl SimdElement for i64 {
202    type Mask = Self;
203}
204
205/// Types that can be used as elements in integer SIMD vectors.
206pub trait SimdIntElement:
207    SimdElement
208    + Eq
209    + Ord
210    + Binary
211    + Not<Output = Self>
212    + Shl<usize, Output = Self>
213    + ShlAssign<usize>
214    + Shr<usize, Output = Self>
215    + ShrAssign<usize>
216    + BitAnd<Self, Output = Self>
217    + BitAndAssign<Self>
218    + BitOr<Self, Output = Self>
219    + BitOrAssign<Self>
220    + BitXor<Self, Output = Self>
221    + BitXorAssign<Self>
222    + for<'a> Shl<&'a usize, Output = Self>
223    + for<'a> ShlAssign<&'a usize>
224    + for<'a> Shr<&'a usize, Output = Self>
225    + for<'a> ShrAssign<&'a usize>
226    + for<'a> BitAnd<&'a Self, Output = Self>
227    + for<'a> BitAndAssign<&'a Self>
228    + for<'a> BitOr<&'a Self, Output = Self>
229    + for<'a> BitOrAssign<&'a Self>
230    + for<'a> BitXor<&'a Self, Output = Self>
231    + for<'a> BitXorAssign<&'a Self>
232{
233}
234
235impl SimdIntElement for u8 {}
236impl SimdIntElement for u16 {}
237impl SimdIntElement for u32 {}
238impl SimdIntElement for u64 {}
239impl SimdIntElement for i8 {}
240impl SimdIntElement for i16 {}
241impl SimdIntElement for i32 {}
242impl SimdIntElement for i64 {}
243
244/// Types that can be used as elements in float SIMD vectors.
245///
246/// The scalar conversion bounds are limited to types that every floating-point
247/// element can represent losslessly, including f16 for forward-compatibility.
248pub trait SimdFloatElement: SimdElement + Neg<Output = Self> + From<i8> + From<u8> {}
249
250impl SimdFloatElement for f32 {}
251impl SimdFloatElement for f64 {}
252
253/// Construction of integer vectors from floats by truncation
254pub trait SimdCvtTruncate<T: Seal>: Seal {
255    fn truncate_from(x: T) -> Self;
256    fn truncate_from_precise(x: T) -> Self;
257}
258
259/// Construction of floating point vectors from integers
260pub trait SimdCvtFloat<T: Seal>: Seal {
261    fn float_from(x: T) -> Self;
262}
263
264/// Interleaved loads and stores for SIMD vectors.
265///
266/// This trait is currently implemented only for numeric 128-bit vector types.
267/// These operations load/store up to 512 bits in one go, and loading into wider vectors
268/// usually degrades performance by causing more register pressure.
269///
270/// If processing in wider vectors is desirable, combine the 128-bit vectors into larger ones
271/// and process them together as a single vector. This avoids issues with register pressure.
272///
273/// # Example
274///
275/// This generic color transform swaps the red and blue channels of RGBA pixels:
276///
277/// ```
278/// use fearless_simd::prelude::*;
279///
280/// fn swap_red_blue<S: Simd, V: SimdInterleaved<S>>(
281///     simd: S,
282///     pixels: &mut [V::Element],
283/// ) {
284///     let mut chunks = pixels.chunks_exact_mut(V::N * 4);
285///     for chunk in &mut chunks {
286///         let [red, green, blue, alpha] = V::load_four_interleaved(simd, chunk);
287///         V::store_four_interleaved([blue, green, red, alpha], chunk);
288///     }
289///
290///     for pixel in chunks.into_remainder().chunks_exact_mut(4) {
291///         pixel.swap(0, 2);
292///     }
293/// }
294/// ```
295pub trait SimdInterleaved<S: Simd>: SimdBase<S> {
296    /// Load four 128-bit vectors from a slice with 4-way interleaving.
297    ///
298    /// This is useful e.g. in image processing to turn interleaved RGBA pixels into vectors of each color component.
299    ///
300    /// For example, with 32-bit lanes, memory laid out as
301    /// `[r0, g0, b0, a0, r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3]` loads as
302    /// `[[r0, r1, r2, r3], [g0, g1, g2, g3], [b0, b1, b2, b3], [a0, a1, a2, a3]]`.
303    ///
304    /// # Panics
305    ///
306    /// Panics unless `src.len()` is exactly `Self::N * 4`.
307    fn load_four_interleaved(simd: S, src: &[Self::Element]) -> [Self; 4];
308
309    /// Store four vectors into a scalar slice with four-way interleaving.
310    ///
311    /// This is the inverse of [`load_four_interleaved`](Self::load_four_interleaved).
312    ///
313    /// This is useful e.g. in image processing to turn vectors of each color component into interleaved RGBA pixels.
314    /// For example, with 32-bit lanes, vectors containing
315    /// `[[r0, r1, r2, r3], [g0, g1, g2, g3], [b0, b1, b2, b3], [a0, a1, a2, a3]]` get stored as
316    /// `[r0, g0, b0, a0, r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3]`.
317    ///
318    /// # Panics
319    ///
320    /// Panics unless `dest.len()` is exactly `Self::N * 4`.
321    fn store_four_interleaved(vectors: [Self; 4], dest: &mut [Self::Element]);
322}
323
324/// Concatenation of two SIMD vectors.
325///
326/// This is implemented on all vectors 256 bits and lower, producing vectors of up to 512 bits.
327pub trait SimdCombine<S: Simd>: SimdBase<S> + Seal {
328    type Combined: SimdBase<S, Element = Self::Element, Block = Self::Block>
329        + SimdSplit<S, Split = Self>;
330
331    /// Concatenate two vectors into a new one that's twice as long.
332    fn combine(self, rhs: impl SimdInto<Self, S>) -> Self::Combined;
333}
334
335/// Splitting of one SIMD vector into two.
336///
337/// This is implemented on all vectors 256 bits and higher, producing vectors of down to 128 bits.
338pub trait SimdSplit<S: Simd>: SimdBase<S> + Seal {
339    type Split: SimdBase<S, Element = Self::Element, Block = Self::Block>
340        + SimdCombine<S, Combined = Self>;
341
342    /// Split this vector into left and right halves.
343    fn split(self) -> (Self::Split, Self::Split);
344}
345
346/// Widening conversion of a numeric SIMD vector.
347///
348/// Integer lanes are sign-extended or zero-extended according to their type. Finite floating-point
349/// lanes are converted losslessly from `f32` to `f64`; infinities and NaNs remain infinities and
350/// NaNs. The result is returned as two vectors with the same bit width as the input: the first
351/// contains the widened lower lanes and the second contains the widened upper lanes.
352///
353/// ```
354/// use fearless_simd::{f32x4, f64x2, prelude::*, u8x16, u16x8};
355///
356/// fn fixed<S: Simd>(value: u8x16<S>) -> (u16x8<S>, u16x8<S>) {
357///     value.widen()
358/// }
359///
360/// fn native<S: Simd>(value: S::u8s) -> (S::u16s, S::u16s) {
361///     value.widen()
362/// }
363///
364/// fn fixed_float<S: Simd>(value: f32x4<S>) -> (f64x2<S>, f64x2<S>) {
365///     value.widen()
366/// }
367///
368/// fn native_float<S: Simd>(value: S::f32s) -> (S::f64s, S::f64s) {
369///     value.widen()
370/// }
371/// ```
372pub trait SimdWiden<S: Simd>: SimdBase<S> + Seal {
373    /// The same-width vector type with lanes twice as wide.
374    type Widened: SimdNarrow<S, Narrowed = Self>;
375
376    /// Widen every lane, returning the lower and upper halves in that order.
377    fn widen(self) -> (Self::Widened, Self::Widened);
378}
379
380/// Narrowing conversion of two numeric SIMD vectors.
381///
382/// Both inputs have the same bit width as the result. The first input supplies the lower result
383/// lanes and the second input supplies the upper result lanes. Integer narrowing either retains
384/// the low destination-width bits or saturates, depending on the method. Floating-point narrowing
385/// converts `f64` to `f32` using Rust's `as` semantics; for floats,
386/// [`saturating_narrow`](Self::saturating_narrow) and
387/// [`relaxed_narrow`](Self::relaxed_narrow) are identical to [`narrow`](Self::narrow).
388///
389/// ```
390/// use fearless_simd::{f32x4, f64x2, prelude::*, i16x8, i8x16};
391///
392/// fn fixed<S: Simd>(low: i16x8<S>, high: i16x8<S>) -> i8x16<S> {
393///     low.narrow(high)
394/// }
395///
396/// fn native<S: Simd>(low: S::i16s, high: S::i16s) -> S::i8s {
397///     low.saturating_narrow(high)
398/// }
399///
400/// fn fixed_float<S: Simd>(low: f64x2<S>, high: f64x2<S>) -> f32x4<S> {
401///     low.narrow(high)
402/// }
403///
404/// fn native_float<S: Simd>(low: S::f64s, high: S::f64s) -> S::f32s {
405///     low.saturating_narrow(high)
406/// }
407/// ```
408pub trait SimdNarrow<S: Simd>: SimdBase<S> + Seal {
409    /// The same-width vector type with lanes half as wide.
410    type Narrowed: SimdWiden<S, Widened = Self>;
411
412    /// Narrow every lane.
413    ///
414    /// This conversion behaves identically to the `as` operator:
415    ///  - Integers are truncated.
416    ///  - Floating-point values follow IEEE 754 narrowing behavior in round-to-even mode:
417    ///    they are rounded to the nearest representable `f32`, with ties resolved to even; overflow produces signed infinity.
418    ///
419    /// # Example
420    ///
421    /// ```
422    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
423    ///
424    /// let level = Level::new();
425    /// dispatch!(level, simd => {
426    ///     let low = i64x2::simd_from(simd, [1, -1]);
427    ///     let high = i64x2::simd_from(simd, [i64::MAX - 5, i64::MIN + 5]);
428    ///     let narrowed: i32x4<_> = low.narrow(high);
429    ///     assert_eq!(*narrowed, [1, -1, -6, 5]);
430    /// });
431    /// ```
432    fn narrow(self, high: Self) -> Self::Narrowed;
433
434    /// Narrow with saturation for integers. Floats behave identically to [`narrow`](Self::narrow).
435    ///
436    /// Integer values that overflow the narrowed type become the closest representable value for the narrowed type.
437    /// For example, `1234u16` becomes `u8::MAX` after narrowing, and `-1234i16` becomes `i8::MIN`.
438    ///
439    /// # Example
440    ///
441    /// ```
442    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
443    ///
444    /// let level = Level::new();
445    /// dispatch!(level, simd => {
446    ///     let low = i64x2::simd_from(simd, [1, -1]);
447    ///     let high = i64x2::simd_from(simd, [i64::MAX - 5, i64::MIN + 5]);
448    ///     let narrowed: i32x4<_> = low.saturating_narrow(high);
449    ///     assert_eq!(*narrowed, [1, -1, i32::MAX, i32::MIN]);
450    /// });
451    fn saturating_narrow(self, high: Self) -> Self::Narrowed;
452
453    /// Narrow using the cheapest operation for the active SIMD backend, assuming no overflow.
454    ///
455    /// This is useful when you're sure the result fits into the destination type,
456    /// so the distinction between [`narrow`](Self::narrow) and [`saturating_narrow`](Self::saturating_narrow)
457    /// doesn't matter.
458    ///
459    /// This method will panic in debug mode if any of the inputs do not fit into the narrower type.
460    /// This operation remains memory-safe and never causes undefined behavior,
461    /// but will produce arbitrary values on overflow in release mode.
462    ///
463    /// Floats behave identically to [`narrow`](Self::narrow), with no additional precondition.
464    ///
465    /// # Example
466    ///
467    /// ```
468    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
469    ///
470    /// let level = Level::new();
471    /// dispatch!(level, simd => {
472    ///     let low = i64x2::simd_from(simd, [1, -1]);
473    ///     let high = i64x2::simd_from(simd, [5, -5]);
474    ///     let narrowed: i32x4<_> = low.relaxed_narrow(high);
475    ///     assert_eq!(*narrowed, [1, -1, 5, -5]);
476    /// });
477    fn relaxed_narrow(self, high: Self) -> Self::Narrowed;
478}