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}