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::*;