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}