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, 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    fn simd_into(self, simd: S) -> T {
167        SimdFrom::simd_from(simd, self)
168    }
169}
170
171impl<T, S: Simd> SimdFrom<T, S> for T {
172    fn simd_from(_simd: S, value: T) -> Self {
173        value
174    }
175}
176
177/// Types that can be used as elements in SIMD vectors.
178pub trait SimdElement:
179    Copy
180    + Clone
181    + Seal
182    + Default
183    + Debug
184    + Display
185    + FromStr
186    + LowerExp
187    + UpperExp
188    + PartialOrd
189    + PartialEq
190    + From<bool>
191    + Add<Self, Output = Self>
192    + AddAssign<Self>
193    + Sub<Self, Output = Self>
194    + SubAssign<Self>
195    + Mul<Self, Output = Self>
196    + MulAssign<Self>
197    + Div<Self, Output = Self>
198    + DivAssign<Self>
199    + Rem<Self, Output = Self>
200    + RemAssign<Self>
201    + Sum<Self>
202    + Product<Self>
203    + for<'a> Add<&'a Self, Output = Self>
204    + for<'a> AddAssign<&'a Self>
205    + for<'a> Sub<&'a Self, Output = Self>
206    + for<'a> SubAssign<&'a Self>
207    + for<'a> Mul<&'a Self, Output = Self>
208    + for<'a> MulAssign<&'a Self>
209    + for<'a> Div<&'a Self, Output = Self>
210    + for<'a> DivAssign<&'a Self>
211    + for<'a> Rem<&'a Self, Output = Self>
212    + for<'a> RemAssign<&'a Self>
213    + for<'a> Sum<&'a Self>
214    + for<'a> Product<&'a Self>
215{
216    /// The associated mask lane type. This will be a signed integer of the same size as this type.
217    type Mask: SimdElement<Mask = Self::Mask>;
218
219    /// The size of an element in bits.
220    const BITS: usize = size_of::<Self>() * u8::BITS as usize;
221}
222
223impl SimdElement for f32 {
224    type Mask = i32;
225}
226
227impl SimdElement for f64 {
228    type Mask = i64;
229}
230
231impl SimdElement for u8 {
232    type Mask = i8;
233}
234
235impl SimdElement for i8 {
236    type Mask = Self;
237}
238
239impl SimdElement for u16 {
240    type Mask = i16;
241}
242
243impl SimdElement for i16 {
244    type Mask = Self;
245}
246
247impl SimdElement for u32 {
248    type Mask = i32;
249}
250
251impl SimdElement for i32 {
252    type Mask = Self;
253}
254
255impl SimdElement for u64 {
256    type Mask = i64;
257}
258
259impl SimdElement for i64 {
260    type Mask = Self;
261}
262
263/// Types that can be used as elements in integer SIMD vectors.
264///
265/// [`Self::Native`] selects the native-width vector for this scalar type and a
266/// SIMD backend, so an integer-generic caller can use [`crate::dispatch!`]:
267///
268/// ```
269/// use fearless_simd::{dispatch, prelude::*, Level};
270///
271/// #[inline(always)]
272/// fn count_ones_simd<S: Simd, T: SimdIntElement>(simd: S, value: T) -> T {
273///     T::Native::<S>::splat(simd, value).count_ones()[0]
274/// }
275///
276/// fn count_ones<T: SimdIntElement>(level: Level, value: T) -> T {
277///     dispatch!(level, simd => count_ones_simd(simd, value))
278/// }
279///
280/// let level = Level::new();
281/// assert_eq!(count_ones(level, 7_u8), 3);
282/// assert_eq!(count_ones(level, 7_i64), 3);
283/// ```
284pub trait SimdIntElement:
285    SimdElement
286    + Eq
287    + Ord
288    + Binary
289    + Not<Output = Self>
290    + Shl<usize, Output = Self>
291    + ShlAssign<usize>
292    + Shr<usize, Output = Self>
293    + ShrAssign<usize>
294    + BitAnd<Self, Output = Self>
295    + BitAndAssign<Self>
296    + BitOr<Self, Output = Self>
297    + BitOrAssign<Self>
298    + BitXor<Self, Output = Self>
299    + BitXorAssign<Self>
300    + TryFrom<u8, Error: Copy + Error + Eq>
301    + TryFrom<u16, Error: Copy + Error + Eq>
302    + TryFrom<u32, Error: Copy + Error + Eq>
303    + TryFrom<u64, Error: Copy + Error + Eq>
304    + TryFrom<u128, Error: Copy + Error + Eq>
305    + TryFrom<usize, Error: Copy + Error + Eq>
306    + TryFrom<i8, Error: Copy + Error + Eq>
307    + TryFrom<i16, Error: Copy + Error + Eq>
308    + TryFrom<i32, Error: Copy + Error + Eq>
309    + TryFrom<i64, Error: Copy + Error + Eq>
310    + TryFrom<i128, Error: Copy + Error + Eq>
311    + TryFrom<isize, Error: Copy + Error + Eq>
312    + for<'a> Shl<&'a usize, Output = Self>
313    + for<'a> ShlAssign<&'a usize>
314    + for<'a> Shr<&'a usize, Output = Self>
315    + for<'a> ShrAssign<&'a usize>
316    + for<'a> BitAnd<&'a Self, Output = Self>
317    + for<'a> BitAndAssign<&'a Self>
318    + for<'a> BitOr<&'a Self, Output = Self>
319    + for<'a> BitOrAssign<&'a Self>
320    + for<'a> BitXor<&'a Self, Output = Self>
321    + for<'a> BitXorAssign<&'a Self>
322{
323    /// The native-width integer vector for this scalar type and backend `S`.
324    ///
325    /// Its lane count depends on both the scalar type and the backend, and is
326    /// available as `T::Native::<S>::LEN` with [`SimdBase`] in scope.
327    type Native<S: Simd>: SimdInt<S, Element = Self>;
328}
329
330impl SimdIntElement for u8 {
331    type Native<S: Simd> = S::u8s;
332}
333impl SimdIntElement for u16 {
334    type Native<S: Simd> = S::u16s;
335}
336impl SimdIntElement for u32 {
337    type Native<S: Simd> = S::u32s;
338}
339impl SimdIntElement for u64 {
340    type Native<S: Simd> = S::u64s;
341}
342impl SimdIntElement for i8 {
343    type Native<S: Simd> = S::i8s;
344}
345impl SimdIntElement for i16 {
346    type Native<S: Simd> = S::i16s;
347}
348impl SimdIntElement for i32 {
349    type Native<S: Simd> = S::i32s;
350}
351impl SimdIntElement for i64 {
352    type Native<S: Simd> = S::i64s;
353}
354
355/// Types that can be used as elements in float SIMD vectors.
356///
357/// The scalar conversion bounds are limited to types that every floating-point
358/// element can represent losslessly, including f16 for forward-compatibility.
359///
360/// [`Self::Native`] selects the native-width vector for this scalar type and a
361/// SIMD backend, so a float-generic caller can use [`crate::dispatch!`]:
362///
363/// ```
364/// use fearless_simd::{dispatch, prelude::*, Level};
365///
366/// #[inline(always)]
367/// fn sqrt_simd<S: Simd, T: SimdFloatElement>(simd: S, value: T) -> T {
368///     T::Native::<S>::splat(simd, value).sqrt()[0]
369/// }
370///
371/// fn sqrt<T: SimdFloatElement>(level: Level, value: T) -> T {
372///     dispatch!(level, simd => sqrt_simd(simd, value))
373/// }
374///
375/// let level = Level::new();
376/// assert_eq!(sqrt(level, 4.0_f32), 2.0);
377/// assert_eq!(sqrt(level, 4.0_f64), 2.0);
378/// ```
379pub trait SimdFloatElement: SimdElement + Neg<Output = Self> + From<i8> + From<u8> {
380    /// The native-width floating-point vector for this scalar type and backend `S`.
381    ///
382    /// Its lane count depends on both the scalar type and the backend, and is
383    /// available as `T::Native::<S>::LEN` with [`SimdBase`] in scope.
384    type Native<S: Simd>: SimdFloat<S, Element = Self>;
385}
386
387impl SimdFloatElement for f32 {
388    type Native<S: Simd> = S::f32s;
389}
390impl SimdFloatElement for f64 {
391    type Native<S: Simd> = S::f64s;
392}
393
394/// Construction of integer vectors from floats by truncation
395pub trait SimdCvtTruncate<T: Seal>: Seal {
396    fn truncate_from(x: T) -> Self;
397    fn truncate_from_precise(x: T) -> Self;
398}
399
400/// Construction of floating point vectors from integers
401pub trait SimdCvtFloat<T: Seal>: Seal {
402    fn float_from(x: T) -> Self;
403}
404
405/// Interleaved loads and stores for SIMD vectors.
406///
407/// This trait is currently implemented only for numeric 128-bit vector types.
408/// These operations load/store up to 512 bits in one go, and loading into wider vectors
409/// usually degrades performance by causing more register pressure.
410///
411/// If processing in wider vectors is desirable, combine the 128-bit vectors into larger ones
412/// and process them together as a single vector. This avoids issues with register pressure.
413///
414/// # Example
415///
416/// This generic color transform swaps the red and blue channels of RGBA pixels:
417///
418/// ```
419/// use fearless_simd::prelude::*;
420///
421/// fn swap_red_blue<S: Simd, V: SimdInterleaved<S>>(
422///     simd: S,
423///     pixels: &mut [V::Element],
424/// ) {
425///     let mut chunks = pixels.chunks_exact_mut(V::LEN * 4);
426///     for chunk in &mut chunks {
427///         let [red, green, blue, alpha] = V::load_four_interleaved(simd, chunk);
428///         V::store_four_interleaved([blue, green, red, alpha], chunk);
429///     }
430///
431///     for pixel in chunks.into_remainder().chunks_exact_mut(4) {
432///         pixel.swap(0, 2);
433///     }
434/// }
435/// ```
436pub trait SimdInterleaved<S: Simd>: SimdBase<S> {
437    /// Load four 128-bit vectors from a slice with 4-way interleaving.
438    ///
439    /// This is useful e.g. in image processing to turn interleaved RGBA pixels into vectors of each color component.
440    ///
441    /// For example, with 32-bit lanes, memory laid out as
442    /// `[r0, g0, b0, a0, r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3]` loads as
443    /// `[[r0, r1, r2, r3], [g0, g1, g2, g3], [b0, b1, b2, b3], [a0, a1, a2, a3]]`.
444    ///
445    /// # Panics
446    ///
447    /// Panics unless `src.len()` is exactly `Self::LEN * 4`.
448    fn load_four_interleaved(simd: S, src: &[Self::Element]) -> [Self; 4];
449
450    /// Store four vectors into a scalar slice with four-way interleaving.
451    ///
452    /// This is the inverse of [`load_four_interleaved`](Self::load_four_interleaved).
453    ///
454    /// This is useful e.g. in image processing to turn vectors of each color component into interleaved RGBA pixels.
455    /// For example, with 32-bit lanes, vectors containing
456    /// `[[r0, r1, r2, r3], [g0, g1, g2, g3], [b0, b1, b2, b3], [a0, a1, a2, a3]]` get stored as
457    /// `[r0, g0, b0, a0, r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3]`.
458    ///
459    /// # Panics
460    ///
461    /// Panics unless `dest.len()` is exactly `Self::LEN * 4`.
462    fn store_four_interleaved(vectors: [Self; 4], dest: &mut [Self::Element]);
463}
464
465/// Concatenation of two SIMD vectors.
466///
467/// This is implemented on all vectors 256 bits and lower, producing vectors of up to 512 bits.
468pub trait SimdCombine<S: Simd>: SimdBase<S> + Seal {
469    type Combined: SimdBase<S, Element = Self::Element, Block = Self::Block>
470        + SimdSplit<S, Split = Self>;
471
472    /// Concatenate two vectors into a new one that's twice as long.
473    fn combine(self, rhs: impl SimdInto<Self, S>) -> Self::Combined;
474}
475
476/// Splitting of one SIMD vector into two.
477///
478/// This is implemented on all vectors 256 bits and higher, producing vectors of down to 128 bits.
479pub trait SimdSplit<S: Simd>: SimdBase<S> + Seal {
480    type Split: SimdBase<S, Element = Self::Element, Block = Self::Block>
481        + SimdCombine<S, Combined = Self>;
482
483    /// Split this vector into left and right halves.
484    fn split(self) -> (Self::Split, Self::Split);
485}
486
487/// Widening conversion of a numeric SIMD vector.
488///
489/// Integer lanes are sign-extended or zero-extended according to their type. Finite floating-point
490/// lanes are converted losslessly from `f32` to `f64`; infinities and NaNs remain infinities and
491/// NaNs. The result is returned as two vectors with the same bit width as the input: the first
492/// contains the widened lower lanes and the second contains the widened upper lanes.
493///
494/// ```
495/// use fearless_simd::{f32x4, f64x2, prelude::*, u8x16, u16x8};
496///
497/// fn fixed<S: Simd>(value: u8x16<S>) -> (u16x8<S>, u16x8<S>) {
498///     value.widen()
499/// }
500///
501/// fn native<S: Simd>(value: S::u8s) -> (S::u16s, S::u16s) {
502///     value.widen()
503/// }
504///
505/// fn fixed_float<S: Simd>(value: f32x4<S>) -> (f64x2<S>, f64x2<S>) {
506///     value.widen()
507/// }
508///
509/// fn native_float<S: Simd>(value: S::f32s) -> (S::f64s, S::f64s) {
510///     value.widen()
511/// }
512/// ```
513pub trait SimdWiden<S: Simd>: SimdBase<S> + Seal {
514    /// The same-width vector type with lanes twice as wide.
515    type Widened: SimdNarrow<S, Narrowed = Self>;
516
517    /// Widen every lane, returning the lower and upper halves in that order.
518    fn widen(self) -> (Self::Widened, Self::Widened);
519}
520
521/// Narrowing conversion of two numeric SIMD vectors.
522///
523/// Both inputs have the same bit width as the result. The first input supplies the lower result
524/// lanes and the second input supplies the upper result lanes. Integer narrowing either retains
525/// the low destination-width bits or saturates, depending on the method. Floating-point narrowing
526/// converts `f64` to `f32` using Rust's `as` semantics; for floats,
527/// [`saturating_narrow`](Self::saturating_narrow) and
528/// [`relaxed_narrow`](Self::relaxed_narrow) are identical to [`narrow`](Self::narrow).
529///
530/// ```
531/// use fearless_simd::{f32x4, f64x2, prelude::*, i16x8, i8x16};
532///
533/// fn fixed<S: Simd>(low: i16x8<S>, high: i16x8<S>) -> i8x16<S> {
534///     low.narrow(high)
535/// }
536///
537/// fn native<S: Simd>(low: S::i16s, high: S::i16s) -> S::i8s {
538///     low.saturating_narrow(high)
539/// }
540///
541/// fn fixed_float<S: Simd>(low: f64x2<S>, high: f64x2<S>) -> f32x4<S> {
542///     low.narrow(high)
543/// }
544///
545/// fn native_float<S: Simd>(low: S::f64s, high: S::f64s) -> S::f32s {
546///     low.saturating_narrow(high)
547/// }
548/// ```
549pub trait SimdNarrow<S: Simd>: SimdBase<S> + Seal {
550    /// The same-width vector type with lanes half as wide.
551    type Narrowed: SimdWiden<S, Widened = Self>;
552
553    /// Narrow every lane.
554    ///
555    /// This conversion behaves identically to the `as` operator:
556    ///  - Integers are truncated.
557    ///  - Floating-point values follow IEEE 754 narrowing behavior in round-to-even mode:
558    ///    they are rounded to the nearest representable `f32`, with ties resolved to even; overflow produces signed infinity.
559    ///
560    /// # Example
561    ///
562    /// ```
563    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
564    ///
565    /// let level = Level::new();
566    /// dispatch!(level, simd => {
567    ///     let low = i64x2::simd_from(simd, [1, -1]);
568    ///     let high = i64x2::simd_from(simd, [i64::MAX - 5, i64::MIN + 5]);
569    ///     let narrowed: i32x4<_> = low.narrow(high);
570    ///     assert_eq!(*narrowed, [1, -1, -6, 5]);
571    /// });
572    /// ```
573    fn narrow(self, high: Self) -> Self::Narrowed;
574
575    /// Narrow with saturation for integers. Floats behave identically to [`narrow`](Self::narrow).
576    ///
577    /// Integer values that overflow the narrowed type become the closest representable value for the narrowed type.
578    /// For example, `1234u16` becomes `u8::MAX` after narrowing, and `-1234i16` becomes `i8::MIN`.
579    ///
580    /// # Example
581    ///
582    /// ```
583    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
584    ///
585    /// let level = Level::new();
586    /// dispatch!(level, simd => {
587    ///     let low = i64x2::simd_from(simd, [1, -1]);
588    ///     let high = i64x2::simd_from(simd, [i64::MAX - 5, i64::MIN + 5]);
589    ///     let narrowed: i32x4<_> = low.saturating_narrow(high);
590    ///     assert_eq!(*narrowed, [1, -1, i32::MAX, i32::MIN]);
591    /// });
592    fn saturating_narrow(self, high: Self) -> Self::Narrowed;
593
594    /// Narrow using the cheapest operation for the active SIMD backend, assuming no overflow.
595    ///
596    /// This is useful when you're sure the result fits into the destination type,
597    /// so the distinction between [`narrow`](Self::narrow) and [`saturating_narrow`](Self::saturating_narrow)
598    /// doesn't matter.
599    ///
600    /// This method will panic in debug mode if any of the inputs do not fit into the narrower type.
601    /// This operation remains memory-safe and never causes undefined behavior,
602    /// but will produce arbitrary values on overflow in release mode.
603    ///
604    /// Floats behave identically to [`narrow`](Self::narrow), with no additional precondition.
605    ///
606    /// # Example
607    ///
608    /// ```
609    /// use fearless_simd::{dispatch, Level, i64x2, i32x4, prelude::*};
610    ///
611    /// let level = Level::new();
612    /// dispatch!(level, simd => {
613    ///     let low = i64x2::simd_from(simd, [1, -1]);
614    ///     let high = i64x2::simd_from(simd, [5, -5]);
615    ///     let narrowed: i32x4<_> = low.relaxed_narrow(high);
616    ///     assert_eq!(*narrowed, [1, -1, 5, -5]);
617    /// });
618    fn relaxed_narrow(self, high: Self) -> Self::Narrowed;
619}