Skip to main content

vectoreyes/
lib.rs

1#![deny(missing_docs)]
2#![allow(unsafe_op_in_unsafe_fn)]
3// Because we generate a large volume of code that uses wildcard
4// imports, we allow their use in vectoreyes.
5#![allow(clippy::wildcard_imports)]
6//! VectorEyes is a (almost entirely) safe and cross-platform wrapper library around vectorized
7//! operations.
8//!
9//! While a normal `add` CPU instruction will add two numbers together, a
10//! [SIMD/Vectorized](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) `add`
11//! instruction will perform multiple additions from the same instruction. This will amortize the
12//! per-instruction cost (e.g. of the CPU decoding the instruction) across all the additions of the
13//! single instruction. This can provide large speed boosts on many platforms.
14//!
15//! Unfortunately, using these operations require using per-platform unsafe intrinsics. To make
16//! this easier, VectorEyes provide safe functions which will function identically on all
17//! platforms.
18//!
19//! The core of this crate is vector types (such as [`U64x2`]). You can think of vectors as arrays
20//! with some extra SIMD operations on top.
21//!
22//! Just like arrays vectors have an element type ([`u64`] in the example above), and an element
23//! count, frequently referred to as _lanes_ (2 in the above example).
24//!
25//! In fact, you can freely convert between arrays and vectors!
26//!
27//! ```
28//! # use vectoreyes::*;
29//! // These two represent the same thing.
30//! let vector_form = U64x2::from([123_u64, 456_u64]);
31//! let array_form: [u64; 2] = vector_form.into();
32//! ```
33//!
34//! However, the vector form has _special SIMD powers_! These two functions perform the same
35//! operation, but the SIMD variant may[^may_be_faster] take better advantage of the CPU hardware.
36//!
37//! [^may_be_faster]: As always, only a Sith deals in absolutes. The Rust compiler can, in some
38//! cases, employ _autovectorization_ to compile code which doesn't use SIMD operations into code
39//! which uses SIMD instructions. Unfortunately, the compiler can't always autovectorize the way we
40//! want it to, which is why VectorEyes exists!
41//!
42//! While normal _bog-standard_ arrays don't implement the `+` operator, our vector types do!
43//! Adding two vectors together performs pairwise addition, using (for the vector backends) a
44//! single CPU instruction!
45//!
46//! ```
47//! # use vectoreyes::*;
48//! fn double_without_simd(arr: [u64; 2]) -> [u64; 2] {
49//!     [arr[0] + arr[0], arr[1] + arr[1]]
50//! }
51//! fn double_with_simd(arr: U64x2) -> U64x2 {
52//!     arr + arr
53//! }
54//! assert_eq!(
55//!     U64x2::from(double_without_simd([1, 2])),
56//!     double_with_simd(U64x2::from([1, 2])),
57//! );
58//! ```
59//!
60//! The documentation for every method on a vector (e.g. [`I64x2::and_not`]) lists the equivalent
61//! scalar code, as well as information on how the operation is implemented on each backend.
62//!
63//! # Vector Sizes
64//! There aren't vector types for every conceivable `(type, element count)` pair. Instead, we have
65//! vector types that correspond to the vector registers that many CPUs have. Because these
66//! registers are 128- or 256-bits wide, we choose vector types which also have this size. For
67//! example, there's a [`U64x2`] type and a [`U32x4`] type, since both are 128-bits wide. But
68//! there's no `U32x2` type, because that'd only be 64-bits wide.
69//!
70//!
71//! # Backends
72//! VectorEyes chooses what backend to execute vector operations with at compile-time.
73//!
74//! ## AVX2
75//! x86-64 CPUs that support the `AVX`, `AVX2`, `SSE4.1`, `AES`, `SSE4.2`, and
76//! `PCLMULQDQ` features will use the `AVX2` backend.
77//!
78//! ## Neon
79//! This is available on aarch64/arm64 machines with `neon` and `aes` features.
80//!
81//! ## Scalar
82//! This is a fallback implementation that works on all CPUs. It's not
83//! particularly performant.
84//!
85//! # Cargo Configuration
86//! If using VectorEyes from the `swanky` repo, all this configuration has already been done for
87//! you!
88//! ## Native CPU Setup
89//! Compile on the machine that you'll be running your code on, and add the
90//! following to your `.cargo/config` file:
91//! ```toml
92//! [build]
93//! rustflags = ["-C", "target-cpu=native", "--cfg=vectoreyes-target-cpu-native"]
94//! rustdocflags = ["-C", "target-cpu=native", "--cfg=vectoreyes-target-cpu-native"]
95//! ```
96//! ## Specific CPU Selection
97//! If you want to compile for some specific CPU, add the following to your
98//! `.cargo/config` file:
99//! ```toml
100//! [build]
101//! rustflags = ["-C", "target-cpu=TARGET", "--cfg=vectoreyes-target-cpu=\"TARGET\""]
102//! rustdocflags = ["-C", "target-cpu=TARGET", "--cfg=vectoreyes-target-cpu=\"TARGET\""]
103//! ```
104//! ## Maximal Compatibility
105//! If you do not put any of the above in your `.cargo/config` file,
106//! `vectoreyes` will always use its `scalar` backend, which does not use vector
107//! instructions.
108//!
109//! # Limitations
110//! VectorEyes was designed around the AVX2 backend. For example, shuffle operations tend to be
111//! constrained to 128-bit lanes because that's how the Intel intrinsics are constrained. As a
112//! result, while code that uses VectorEyes might be optimal for an Intel platform, it might not be
113//! optimal for an ARM platform with different intrinsics. (This is a limitation, generally, with
114//! cross-platform SIMD libraries like VectorEyes.)
115//!
116//! In addition, many SIMD intrinsics are currently not wrapped in VectorEyes.
117
118use std::ops::*;
119
120/// What backend will be used when targeting the current CPU?
121#[non_exhaustive]
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum VectorBackend {
124    /// The fallback scalar backend (doesn't use vector instructions).
125    Scalar,
126    /// A vector backend targeting [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2).
127    Avx2,
128    /// A vector backend targeting [ARM Neon](https://developer.arm.com/Architectures/Neon).
129    Neon,
130}
131
132/// The vector backend that this process is using.
133pub const VECTOR_BACKEND: VectorBackend = current_vector_backend();
134
135/// Panic if the current binary uses features unsupported by the current CPU.
136///
137/// `vectoreyes` uses compile-time flags to select which backend to use and which CPU features to
138/// require. If this backend is used on an unsupported CPU, it will result in an "Illegal
139/// instruction" error (technically, _all_ Rust code--not even just `vectoreyes` code--may result in
140/// undefined behavior if run on a CPU that doesn't support the compile-time selected feature
141/// flags).
142///
143/// It would be advisable to call this in the `main()` function of executables to try to catch
144/// these errors early.
145pub fn assert_cpu_features() {
146    vector_backend_check_cpu()
147}
148
149/// A scalar that can live in the lane of a vector.
150pub trait Scalar:
151    'static
152    + std::fmt::Debug
153    + num_traits::PrimInt
154    + num_traits::WrappingAdd
155    + num_traits::WrappingSub
156    + num_traits::WrappingMul
157    + subtle::ConstantTimeEq
158    + subtle::ConditionallySelectable
159{
160    /// A scalar of the same width as this scalar, but signed.
161    type Signed: Scalar;
162    /// A scalar of the same width as this scalar, but unsigned.
163    type Unsigned: Scalar;
164
165    /// A scalar of the same sign as this scalar, but with width 8.
166    type SameSign8: Scalar<Signed = i8, Unsigned = u8>;
167    /// A scalar of the same sign as this scalar, but with width 16.
168    type SameSign16: Scalar<Signed = i16, Unsigned = u16>;
169    /// A scalar of the same sign as this scalar, but with width 32.
170    type SameSign32: Scalar<Signed = i32, Unsigned = u32>;
171    /// A scalar of the same sign as this scalar, but with width 64.
172    type SameSign64: Scalar<Signed = i64, Unsigned = u64>;
173}
174macro_rules! scalar_impls {
175    ($(($s:ty, $u:ty)),*) => {$(
176        impl Scalar for $s {
177            type Signed = $s;
178            type Unsigned = $u;
179
180            type SameSign8 = i8;
181            type SameSign16 = i16;
182            type SameSign32 = i32;
183            type SameSign64 = i64;
184        }
185        impl Scalar for $u {
186            type Signed = $s;
187            type Unsigned = $u;
188
189            type SameSign8 = u8;
190            type SameSign16 = u16;
191            type SameSign32 = u32;
192            type SameSign64 = u64;
193        }
194    )*};
195}
196scalar_impls!((i64, u64), (i32, u32), (i16, u16), (i8, u8));
197/// A vector equivalent to `[T; Self::Lanes]`.
198///
199/// # Representation
200/// This type should have the same size as `[T; Self::Lanes]`, though it may have increased
201/// alignment requirements.
202///
203/// # Effects of signedness on shift operations
204/// When `Scalar` is _signed_, shift operations are signed shifts. When `Scalar` is _unsigned_,
205/// shift operations are unsigned shifts.
206///
207/// ## Example
208/// A signed shift right will add the sign bit
209/// ```
210/// # use vectoreyes::*;
211/// assert_eq!(
212///     U64x2::from([0xffffffffffffffff, 0x2]) >> 1,
213///     U64x2::from([0x7fffffffffffffff, 0x1]),
214/// );
215/// assert_eq!(
216///     // Because the sign bit of 0xffffffffffffffff is 1, shifting right will cause a 1 to be
217///     // inserted which, in this case, results in the same 0xffffffffffffffff value.
218///     U64x2::from(I64x2::from(U64x2::from([0xffffffffffffffff, 0x2])) >> 1),
219///     U64x2::from([0xffffffffffffffff, 0x1]),
220/// );
221/// ```
222pub trait SimdBase:
223    'static
224    + Sized
225    + Clone
226    + Copy
227    + Sync
228    + Send
229    + std::fmt::Debug
230    + PartialEq
231    + Eq
232    + Default
233    + bytemuck::Pod
234    + bytemuck::Zeroable
235    + BitXor
236    + BitXorAssign
237    + BitOr
238    + BitOrAssign
239    + BitAnd
240    + BitAndAssign
241    + AddAssign
242    + Add<Output = Self>
243    + SubAssign
244    + Sub<Output = Self>
245    + ShlAssign<u64>
246    + Shl<u64, Output = Self>
247    + ShrAssign<u64>
248    + Shr<u64, Output = Self>
249    + ShlAssign<Self>
250    + Shl<Self, Output = Self>
251    + ShrAssign<Self>
252    + Shr<Self, Output = Self>
253    + subtle::ConstantTimeEq
254    + subtle::ConditionallySelectable
255    + AsRef<[Self::Scalar]>
256    + AsMut<[Self::Scalar]>
257{
258    /// The number of elements of this vector.
259    const LANES: usize;
260
261    /// The equivalent array type of this vector.
262    type Array: 'static
263        + Sized
264        + Clone
265        + Copy
266        + Sync
267        + Send
268        + std::fmt::Debug
269        + bytemuck::Pod
270        + bytemuck::Zeroable
271        + PartialEq
272        + Eq
273        + Default
274        + std::hash::Hash
275        + AsRef<[Self::Scalar]>
276        + From<Self>
277        + Into<Self>;
278
279    /// The scalar that this value holds.
280    type Scalar: Scalar;
281    /// The signed version of this vector.
282    type Signed: SimdBase<Scalar = <<Self as SimdBase>::Scalar as Scalar>::Signed>
283        + From<Self>
284        + Into<Self>;
285    /// The unsigned version of this vector.
286    type Unsigned: SimdBase<Scalar = <<Self as SimdBase>::Scalar as Scalar>::Unsigned>
287        + From<Self>
288        + Into<Self>;
289
290    /// A vector where every element is zero.
291    const ZERO: Self;
292    /// Is `self == Self::ZERO`?
293    ///
294    /// # Example
295    /// ```
296    /// # use vectoreyes::*;
297    /// assert!(U32x4::from([0, 0, 0, 0]).is_zero());
298    /// assert!(!U32x4::from([1, 0, 0, 0]).is_zero());
299    /// ```
300    fn is_zero(&self) -> bool;
301
302    /// Create a new vector by setting element 0 to `value`, and the rest of the elements to `0`.
303    ///
304    /// # Example
305    /// ```
306    /// # use vectoreyes::*;
307    /// assert_eq!(U32x4::from([64, 0, 0, 0]), U32x4::set_lo(64));
308    /// ````
309    fn set_lo(value: Self::Scalar) -> Self;
310
311    /// Create a new vector by setting every element to `value`.
312    ///
313    /// # Example
314    /// ```
315    /// # use vectoreyes::*;
316    /// assert_eq!(U32x4::from([64, 64, 64, 64]), U32x4::broadcast(64));
317    /// ````
318    fn broadcast(value: Self::Scalar) -> Self;
319
320    /// A vector of `[Self::Scalar; 128 / (8 * std::mem::size_of::<Self::Scalar>())]`
321    type BroadcastLoInput: SimdBase<Scalar = Self::Scalar>;
322    /// Create a vector by setting every element to element 0 of `of`.
323    ///
324    /// # Example
325    /// ```
326    /// # use vectoreyes::*;
327    /// assert_eq!(U32x4::from([1, 1, 1, 1]), U32x4::broadcast_lo(U32x4::from([1, 2, 3, 4])));
328    /// ````
329    fn broadcast_lo(of: Self::BroadcastLoInput) -> Self;
330
331    /// Get the `I`-th element of this vector.
332    ///
333    /// # Example
334    /// ```
335    /// # use vectoreyes::*;
336    /// let v = U32x4::from([1, 2, 3, 4]);
337    /// assert_eq!(v.extract::<0>(), 1);
338    /// assert_eq!(v.extract::<1>(), 2);
339    /// assert_eq!(v.extract::<2>(), 3);
340    /// assert_eq!(v.extract::<3>(), 4);
341    /// ````
342    fn extract<const I: usize>(&self) -> Self::Scalar;
343
344    /// Convert the vector to an array.
345    #[inline(always)]
346    fn as_array(&self) -> Self::Array {
347        (*self).into()
348    }
349
350    /// Shift each element left by `BITS`.
351    ///
352    /// # Example
353    /// ```
354    /// # use vectoreyes::*;
355    /// assert_eq!(U32x4::from([1, 2, 3, 4]).shift_left::<1>(), U32x4::from([2, 4, 6, 8]));
356    /// ````
357    fn shift_left<const BITS: usize>(&self) -> Self;
358    /// Shift each element right by `BITS`.
359    /// # Effects of Signedness
360    /// When `T` is _signed_, this will shift in sign bits, as opposed to zeroes.
361    ///
362    /// # Example
363    /// ```
364    /// # use vectoreyes::*;
365    /// assert_eq!(U32x4::from([1, 2, 3, 4]).shift_right::<1>(), U32x4::from([0, 1, 1, 2]));
366    /// assert_eq!(I32x4::from([-1, -2, -3, -4]).shift_right::<1>(), I32x4::from([-1, -1, -2, -2]));
367    /// ````
368    fn shift_right<const BITS: usize>(&self) -> Self;
369
370    /// Compute `self & (! other)`.
371    ///
372    /// # Example
373    /// ```
374    /// # use vectoreyes::*;
375    /// assert_eq!(
376    ///     U64x2::from([0b11, 0b00]).and_not(U64x2::from([0b10, 0b10])),
377    ///     U64x2::from([0b01, 0b00]),
378    /// );
379    /// ````
380    fn and_not(&self, other: Self) -> Self;
381
382    /// Create a vector where each element is all 1's if the elements are equal, and all 0's otherwise.
383    ///
384    /// # Example
385    /// ```
386    /// # use vectoreyes::*;
387    /// assert_eq!(
388    ///     U64x2::from([1, 2]).cmp_eq(U64x2::from([1, 3])),
389    ///     U64x2::from([0xffffffffffffffff, 0]),
390    /// );
391    /// ````
392    fn cmp_eq(&self, other: Self) -> Self;
393    /// Create a vector where each element is all 1's if the element of `self` is greater than the
394    /// corresponding element of `other`, and all 0's otherwise.
395    ///
396    /// # Example
397    /// ```
398    /// # use vectoreyes::*;
399    /// assert_eq!(
400    ///     U64x2::from([1, 28]).cmp_gt(U64x2::from([1, 3])),
401    ///     U64x2::from([0, 0xffffffffffffffff]),
402    /// );
403    /// ````
404    fn cmp_gt(&self, other: Self) -> Self;
405
406    /// Interleave the elements of the low half of `self` and `other`.
407    ///
408    /// # Example
409    /// ```
410    /// # use vectoreyes::*;
411    /// assert_eq!(
412    ///     U32x4::from([101, 102, 103, 104]).unpack_lo(U32x4::from([201, 202, 203, 204])),
413    ///     U32x4::from([101, 201, 102, 202]),
414    /// );
415    /// ````
416    fn unpack_lo(&self, other: Self) -> Self;
417    /// Interleave the elements of the high half of `self` and `other`.
418    ///
419    /// # Example
420    /// ```
421    /// # use vectoreyes::*;
422    /// assert_eq!(
423    ///     U32x4::from([101, 102, 103, 104]).unpack_hi(U32x4::from([201, 202, 203, 204])),
424    ///     U32x4::from([103, 203, 104, 204]),
425    /// );
426    /// ````
427    fn unpack_hi(&self, other: Self) -> Self;
428
429    /// Make a vector consisting of the maximum elements of `self` and other.
430    ///
431    /// # Example
432    /// ```
433    /// # use vectoreyes::*;
434    /// assert_eq!(
435    ///     U32x4::from([1, 2, 3, 4]).max(U32x4::from([0, 9, 0, 0])),
436    ///     U32x4::from([1, 9, 3, 4]),
437    /// );
438    /// ````
439    fn max(&self, other: Self) -> Self;
440    /// Make a vector consisting of the minimum elements of `self` and other.
441    ///
442    /// # Example
443    /// ```
444    /// # use vectoreyes::*;
445    /// assert_eq!(
446    ///     U32x4::from([1, 2, 3, 4]).min(U32x4::from([0, 9, 0, 0])),
447    ///     U32x4::from([0, 2, 0, 0]),
448    /// );
449    /// ````
450    fn min(&self, other: Self) -> Self;
451}
452
453/// A vector supporting the gather operation (indexing into an array using indices from a vector).
454pub trait SimdBaseGatherable<IV: SimdBase>: SimdBase {
455    /// Construct a vector by accessing values at `base + indices[i]`.
456    ///
457    /// # Safety
458    /// This operation is safe if `std::ptr::read(base.add(indices[i]))` is safe for all `i`.
459    ///
460    /// # Example
461    /// ```
462    /// # use vectoreyes::*;
463    /// let arr: Vec<i32> = (0..=1024).map(|x| x + 1).collect();
464    /// let out = unsafe {
465    ///     // SAFETY: All the indices are within bounds.
466    ///     I32x4::gather(arr.as_ptr(), U64x4::from([32, 647, 827, 920]))
467    /// };
468    /// assert_eq!(out, I32x4::from([33, 648, 828, 921]));
469    /// ```
470    unsafe fn gather(base: *const Self::Scalar, indices: IV) -> Self;
471    /// Construct a vector by accessing values at `base + indices[i]`, if the mask's MSB is set.
472    /// Else return `src[i]`.
473    ///
474    /// # Safety
475    /// This operation is safe if `std::ptr::read(base.add(indices[i]))` is safe for all `i`.
476    ///
477    /// # Example
478    /// ```
479    /// # use vectoreyes::*;
480    /// let arr: Vec<i32> = (0..=1024).map(|x| x + 1).collect();
481    /// let out = unsafe {
482    ///     // SAFETY: All the indices are within bounds.
483    ///     I32x4::gather_masked(
484    ///         arr.as_ptr(),
485    ///         U64x4::from([32, 647, 827, 920]),
486    ///         I32x4::from([-1, -1, 0, 0]),
487    ///         I32x4::from([1, 2, 3, 4]),
488    ///     )
489    /// };
490    /// assert_eq!(out, I32x4::from([33, 648, 3, 4]));
491    /// ```
492    unsafe fn gather_masked(base: *const Self::Scalar, indices: IV, mask: Self, src: Self) -> Self;
493}
494
495/// A vector containing 4 lanes.
496pub trait SimdBase4x: SimdBase {
497    /// If `Bi` is true, then that lane will be filled by `if_true`. Otherwise the lane
498    /// will be filled from `self`.
499    ///
500    /// # Example
501    /// ```
502    /// # use vectoreyes::*;
503    /// assert_eq!(
504    ///     U64x4::from([11, 12, 13, 14])
505    ///         .blend::<true, true, true, false>(U64x4::from([21, 22, 23, 24])),
506    ///     U64x4::from([11, 22, 23, 24]),
507    /// );
508    /// ````
509    fn blend<const B3: bool, const B2: bool, const B1: bool, const B0: bool>(
510        &self,
511        if_true: Self,
512    ) -> Self;
513}
514
515/// A vector containing 8 lanes.
516pub trait SimdBase8x: SimdBase {
517    /// If `Bi` is true, then that lane will be filled by `if_true`. Otherwise the lane
518    /// will be filled from `self`.
519    ///
520    /// # Example
521    /// ```
522    /// # use vectoreyes::*;
523    /// assert_eq!(
524    ///     U32x8::from([11, 12, 13, 14, 15, 16, 17, 18])
525    ///         .blend::<true, true, true, false, false, true, true, false>(
526    ///             U32x8::from([21, 22, 23, 24, 25, 26, 27, 28])),
527    ///     U32x8::from([11, 22, 23, 14, 15, 26, 27, 28]),
528    /// );
529    /// ````
530    fn blend<
531        const B7: bool,
532        const B6: bool,
533        const B5: bool,
534        const B4: bool,
535        const B3: bool,
536        const B2: bool,
537        const B1: bool,
538        const B0: bool,
539    >(
540        &self,
541        if_true: Self,
542    ) -> Self;
543}
544
545/// A vector supporting saturating arithmetic on each entry.
546///
547/// Saturating operations clamp their outputs to the scalar's maximum or minimum value on
548/// overflow/underflow.
549pub trait SimdSaturatingArithmetic: SimdBase {
550    /// Pairwise add vectors. On overflow, the entry's value goes to the maximum scalar value.
551    fn saturating_add(&self, other: Self) -> Self;
552    /// Pairwise add vectors. On overflow, the entry's value goes to the minimum scalar value.
553    fn saturating_sub(&self, other: Self) -> Self;
554}
555
556/// A vector containing 8-bit values.
557pub trait SimdBase8: SimdBase + SimdSaturatingArithmetic
558where
559    Self::Scalar: Scalar<Unsigned = u8, Signed = i8>,
560{
561    /// Split the vector into groups of 16 bytes. Within each group, shift the _entire_ bytes left
562    /// by `AMOUNT`.
563    ///
564    /// # Example
565    /// ```
566    /// # use vectoreyes::*;
567    /// assert_eq!(
568    ///     U8x16::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).shift_bytes_left::<1>(),
569    ///     U8x16::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
570    /// );
571    /// ```
572    fn shift_bytes_left<const AMOUNT: usize>(&self) -> Self;
573    /// Split the vector into groups of 16 bytes. Within each group, shift the _entire_ bytes right
574    /// by `AMOUNT`.
575    ///
576    /// # Example
577    /// ```
578    /// # use vectoreyes::*;
579    /// assert_eq!(
580    ///     U8x16::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).shift_bytes_right::<1>(),
581    ///     U8x16::from([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0]),
582    /// );
583    /// ```
584    fn shift_bytes_right<const AMOUNT: usize>(&self) -> Self;
585    /// Get the sign/most significant bits of the elements of the vector.
586    ///
587    /// # Example
588    /// ```
589    /// # use vectoreyes::*;
590    /// assert_eq!(
591    ///     (U8x16::from([0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1]) << 7).most_significant_bits(),
592    ///     0b1111001001010000,
593    /// );
594    /// ```
595    fn most_significant_bits(&self) -> u32;
596}
597
598/// A vector containing 16-bit values.
599pub trait SimdBase16: SimdBase + SimdSaturatingArithmetic
600where
601    Self::Scalar: Scalar<Unsigned = u16, Signed = i16>,
602{
603    /// Shuffle within the lower 64-bits of each 128-bit subvector.
604    ///
605    /// # Example
606    /// ```
607    /// # use vectoreyes::*;
608    /// assert_eq!(
609    ///     U16x16::from([
610    ///         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
611    ///     ]).shuffle_lo::<0, 1, 1, 3>(),
612    ///     U16x16::from([
613    ///         3, 1, 1, 0, 4, 5, 6, 7, 11, 9, 9, 8, 12, 13, 14, 15
614    ///     ]),
615    /// );
616    /// ```
617    fn shuffle_lo<const I3: usize, const I2: usize, const I1: usize, const I0: usize>(
618        &self,
619    ) -> Self;
620    /// Shuffle within the upper 64-bits of each 128-bit subvector.
621    ///
622    /// # Example
623    /// ```
624    /// # use vectoreyes::*;
625    /// assert_eq!(
626    ///     U16x16::from([
627    ///         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
628    ///     ]).shuffle_hi::<0, 1, 1, 3>(),
629    ///     U16x16::from([
630    ///         0, 1, 2, 3, 7, 5, 5, 4, 8, 9, 10, 11, 15, 13, 13, 12
631    ///     ]),
632    /// );
633    /// ```
634    fn shuffle_hi<const I3: usize, const I2: usize, const I1: usize, const I0: usize>(
635        &self,
636    ) -> Self;
637}
638
639/// A vector containing 32-bit values.
640pub trait SimdBase32: SimdBase
641where
642    Self::Scalar: Scalar<Unsigned = u32, Signed = i32>,
643{
644    /// Shuffle within 128-bit subvector.
645    ///
646    /// # Example
647    /// ```
648    /// # use vectoreyes::*;
649    /// assert_eq!(
650    ///     U32x8::from([
651    ///         0, 1, 2, 3, 4, 5, 6, 7
652    ///     ]).shuffle::<0, 1, 1, 3>(),
653    ///     U32x8::from([
654    ///         3, 1, 1, 0, 7, 5, 5, 4
655    ///     ]),
656    /// );
657    /// ```
658    fn shuffle<const I3: usize, const I2: usize, const I1: usize, const I0: usize>(&self) -> Self;
659}
660
661/// A vector containing 64-bit values.
662pub trait SimdBase64: SimdBase
663where
664    Self::Scalar: Scalar<Unsigned = u64, Signed = i64>,
665{
666    /// Zero out the upper-32 bits of each word, and then perform pairwise multiplication.
667    ///
668    /// # Example
669    /// ```
670    /// # use vectoreyes::*;
671    /// assert_eq!(
672    ///     U64x4::from([6, 7, 8, 9]).mul_lo(U64x4::from([1, 2, 3, 4])),
673    ///     U64x4::from([6, 14, 24, 36]),
674    /// );
675    /// assert_eq!(
676    ///     U64x4::from([6, 7, 8, 9]).mul_lo(
677    ///         U64x4::from([1, 2, 3, 4]) | U64x4::broadcast(u64::MAX << 32)
678    ///     ),
679    ///     U64x4::from([6, 14, 24, 36]),
680    /// );
681    /// ```
682    fn mul_lo(&self, other: Self) -> Self;
683}
684
685/// A vector containing 4 64-bit values.
686pub trait SimdBase4x64: SimdBase64 + SimdBase4x
687where
688    Self::Scalar: Scalar<Unsigned = u64, Signed = i64>,
689{
690    /// Shuffle the 64-bit values.
691    ///
692    /// # Example
693    /// ```
694    /// # use vectoreyes::*;
695    /// assert_eq!(
696    ///     U64x4::from([0, 1, 2, 3]).shuffle::<0, 1, 1, 3>(),
697    ///     U64x4::from([3, 1, 1, 0]),
698    /// );
699    /// ```
700    fn shuffle<const I3: usize, const I2: usize, const I1: usize, const I0: usize>(&self) -> Self;
701}
702
703// TODO: deprecate the uses of from() everywhere and use traits/functions that make it obvious which
704// casts are free and which aren't.
705
706/// Lossily cast a vector by {zero,sign}-extending its values.
707pub trait ExtendingCast<T: SimdBase>: SimdBase {
708    /// Cast from one vector to another by sign or zero extending the values from the source until it
709    /// fills the destination.
710    ///
711    /// The lowest-index values in `t` are kept. Any values which don't fit are discarded.
712    ///
713    /// # Example
714    /// ```
715    /// # use vectoreyes::*;
716    /// assert_eq!(
717    ///     U64x2::extending_cast_from(U32x4::from([1, 2, 3, 4])),
718    ///     U64x2::from([1, 2]),
719    /// );
720    /// ```
721    fn extending_cast_from(t: T) -> Self;
722}
723
724/// A [`Scalar`] type which has a vector type of length `N`.
725///
726/// See [`Simd`] for how this trait is used.
727pub trait HasVector<const N: usize>: Scalar {
728    /// The vector of `[Self; N]`.
729    type Vector: SimdBase<Scalar = Self>;
730}
731
732/// An alternative way of naming SIMD types.
733///
734/// This allows for functions to be written which are generic in the type or length of a vector.
735///
736/// # Example
737/// ```
738/// # use vectoreyes::*;
739/// type MyVector = Simd<u8, 16>; // The same as U8x16.
740///
741/// fn my_length_generic_code<const N: usize>(x: Simd<u32, N>, y: Simd<u32, N>) -> Simd<u32, N>
742///     where u32: HasVector<N>
743/// {
744///     x + x + y
745/// }
746/// ```
747pub type Simd<T, const N: usize> = <T as HasVector<N>>::Vector;
748
749/// An AES block cipher, suitable for encryption.
750///
751/// This cipher can be used for encryption. Decryption operations are handled in the subtrait
752/// [`AesBlockCipherDecrypt`].
753pub trait AesBlockCipher: 'static + Clone + Sync + Send {
754    /// The type of the AES key.
755    type Key: 'static + Clone + Sync + Send;
756
757    /// Running `encrypt_many` with this many blocks will typically result in good
758    /// performance.
759    const BLOCK_COUNT_HINT: usize;
760
761    /// Run the AES key schedule operation with a given key.
762    fn new_with_key(key: Self::Key) -> Self;
763
764    /// Encrypt a single 128-bit AES block.
765    #[inline(always)]
766    fn encrypt(&self, block: U8x16) -> U8x16 {
767        self.encrypt_many([block])[0]
768    }
769    /// Encrypt an array of `N` 128-bit AES blocks using ECB mode.
770    fn encrypt_many<const N: usize>(&self, blocks: [U8x16; N]) -> [U8x16; N]
771    where
772        array_utils::ArrayUnrolledOps: array_utils::UnrollableArraySize<N>;
773}
774
775/// An AES block cipher, suitable for encryption and decryption.
776pub trait AesBlockCipherDecrypt: AesBlockCipher {
777    /// Decrypt a single 128-bit AES block.
778    #[inline(always)]
779    fn decrypt(&self, block: U8x16) -> U8x16 {
780        self.decrypt_many([block])[0]
781    }
782    /// Decrypt an array of `N` 128-bit AES blocks using ECB mode.
783    fn decrypt_many<const N: usize>(&self, blocks: [U8x16; N]) -> [U8x16; N]
784    where
785        array_utils::ArrayUnrolledOps: array_utils::UnrollableArraySize<N>;
786}
787
788pub mod array_utils;
789pub(crate) mod utils;
790
791// We want to allow `which_lane * 0 + 0` expressions.
792// These also allow for simpler generated code. For example, sometimes we have code which looks
793// like:
794//    let x: {{ty}};
795//    x as u8
796// When {{ty}} _is_ u8, this cast isn't neccessary. But it's simpler to always insert it in the
797// generated code.
798#[allow(
799    clippy::identity_op,
800    clippy::erasing_op,
801    clippy::unnecessary_cast,
802    clippy::useless_conversion
803)]
804// intel intrinsics have many arguments
805#[allow(clippy::too_many_arguments)]
806// our compressed code doesn't have newlines
807#[allow(clippy::suspicious_else_formatting)]
808// You can't put inline(always) without a closure
809#[allow(clippy::redundant_closure)]
810// These two lints let us have extra parentheses in the generated source (which makes generation
811// easier).
812#[allow(unused_parens)]
813#[allow(clippy::needless_borrow)]
814// </the two lints>
815mod generated;
816pub use generated::implementation::*;