diskann_wide/traits.rs
1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use super::{
7 arch,
8 bitmask::{BitMask, FromInt},
9 constant::{Const, SupportedLaneCount},
10};
11
12/// Rust currently lacks the ability to use a trait's associated constants as constraints
13/// or constant parameters to other type definitions within the same trait.
14///
15/// The use of the `Const` type moves the const generic into the type domain, allowing us
16/// to properly constrain and define other associated items.
17///
18/// In particular, we currently can't do nice things like:
19/// ```ignore
20/// trait MyTrait {
21/// const MY_CONSTANT: usize;
22/// type ArrayType = [f32; Self::MY_CONSTANT];
23/// ----------------- Currently unsupported.
24/// }
25/// ```
26///
27/// See:
28/// - <https://users.rust-lang.org/t/limitation-of-associated-const-in-traits/73491/2>
29/// - <https://github.com/rust-lang/rust/issues/76560>
30pub trait ArrayType<T>: SupportedLaneCount {
31 type Type;
32}
33
34/// Map scalar + lengths to arrays.
35impl<T, const N: usize> ArrayType<T> for Const<N>
36where
37 Const<N>: SupportedLaneCount,
38{
39 type Type = [T; N];
40}
41
42/// Stable Rust does not allow expressions involving compile-time computation with
43/// const generic parameters: <http://github.com/rust-lang/rust/issues/76560>
44///
45/// This makes it difficult to go from a const parameter defining the number of SIMD lanes
46/// to an appropriately sized mask.
47///
48/// This helper trait provides a level of indirection to map SIMD representations to
49/// the associated bitmask.
50pub trait BitMaskType<A: arch::Sealed>: SupportedLaneCount {
51 type Type; // should always be a `BitMask`.
52}
53
54impl<A, const N: usize> BitMaskType<A> for Const<N>
55where
56 Const<N>: SupportedLaneCount,
57 A: arch::Sealed,
58{
59 type Type = BitMask<N, A>;
60}
61
62/// Convert `Self` to the SIMD type `T`. This is mainly useful when implementing fallback
63/// operations through [`crate::Emulated`] to restore the original SIMD type.
64pub trait AsSIMD<T>: Copy
65where
66 T: SIMDVector,
67{
68 fn as_simd(self, arch: T::Arch) -> T;
69}
70
71/// A logical mask for SIMD operations.
72///
73/// The representation of this type varies between architectures and micro-architectures.
74/// For example:
75///
76/// * On AVX 2 systems, a SIMD mask for type/length pairs `(T, N)` consists of a SIMD
77/// register of an unsigned integer with the same size of `T` and length `N`.
78///
79/// The semantics of such registers are to allow operations in lanes where the top-most
80/// bit is set to 1.
81///
82/// * On AVX-512 systems, the story is much simpler as the masks used in that instruction
83/// set are simply the corresponding bit mask.
84///
85/// So a mask for 8-wide operations is simply an 8-bit unsigned integer.
86///
87/// * Emulated systems should use a bit-mask for the most compact representation.
88pub trait SIMDMask: Copy + std::fmt::Debug {
89 /// The architecture type this struct belongs to.
90 type Arch: arch::Sealed;
91
92 /// The type of the underlying intrinsic.
93 type Underlying: Copy + std::fmt::Debug;
94
95 /// The bitmask associated with the logical mask.
96 type BitMask: SIMDMask<Arch = Self::Arch> + Into<Self> + From<Self>;
97
98 /// The number of lanes in the bitmask.
99 const LANES: usize;
100
101 /// Whether or not this mask implementation is a bit mask.
102 const ISBITS: bool;
103
104 /// Return the architecture object associated with this vector.
105 fn arch(self) -> Self::Arch;
106
107 /// Retrieve the underlying type.
108 /// This will always be an unsigned integer of the minimum width required to contain
109 /// `LANES` bits.
110 fn to_underlying(self) -> Self::Underlying;
111
112 /// Construct the mask from the underlying type.
113 fn from_underlying(arch: Self::Arch, value: Self::Underlying) -> Self;
114
115 /// Return `true` if lane `i` is set and `false` otherwise.
116 ///
117 /// This method is unchecked, but safe in the sense that if `i >= LANES` false will
118 /// always be returned. No out of bounds access will be made, but no error indication
119 /// will be provided.
120 fn get_unchecked(&self, i: usize) -> bool;
121
122 /// Efficiently construct a new mask with the first `i` bits set and the remainder
123 /// set to zero.
124 ///
125 /// If `i >= LANES` then all bits will be set.
126 fn keep_first(arch: Self::Arch, i: usize) -> Self;
127
128 /// Return the first set index in the mask or `None` if no entries are set.
129 fn first(&self) -> Option<usize> {
130 self.bitmask().first()
131 }
132
133 //////////////////////////////
134 // Provided Implementations //
135 //////////////////////////////
136
137 /// Return the associated BitMask for this Mask.
138 fn bitmask(self) -> Self::BitMask {
139 <Self::BitMask as From<Self>>::from(self)
140 }
141
142 /// Return `true` if lane `i` is set and `false` otherwise. Returns an empty `Option`
143 /// if the index `i` is out-of-bounds.
144 fn get(&self, i: usize) -> Option<bool> {
145 if i >= Self::LANES {
146 None
147 } else {
148 Some(self.get_unchecked(i))
149 }
150 }
151
152 /// Construct a mask based on the result of invoking `f` once for each element in the range
153 /// `0..Self::LANES` in order.
154 ///
155 /// In the returned mask `m`, `m.get(0)` corresponds to the value of `f(0)`. Similarly,
156 /// `m.get(1)` corresponds to `f(1)` etc.
157 #[inline(always)]
158 fn from_fn<F>(arch: Self::Arch, f: F) -> Self
159 where
160 F: FnMut(usize) -> bool,
161 {
162 // Recurse to BitMask.
163 Self::BitMask::from_fn(arch, f).into()
164 }
165
166 /// Return `true` if any lane in the mask is set. Otherwise, return `false`.
167 #[inline(always)]
168 fn any(self) -> bool {
169 // Recurse to BitMask.
170 <Self::BitMask as From<Self>>::from(self).any()
171 }
172
173 /// Return `true` if all lanes in the mask are set. Otherwise, return `false`.
174 #[inline(always)]
175 fn all(self) -> bool {
176 // Recurse to BitMask.
177 <Self::BitMask as From<Self>>::from(self).all()
178 }
179
180 /// Return `true` if no lanes in the mask are set. Otherwise, return `false`.
181 #[inline(always)]
182 fn none(self) -> bool {
183 !self.any()
184 }
185
186 /// Return the number of lanes that evaluate to `true`.
187 #[inline(always)]
188 fn count(self) -> usize {
189 // Recurse to BitMask.
190 <Self::BitMask as From<Self>>::from(self).count()
191 }
192}
193
194/// A trait representing minimal behavior for a SIMD-like vector.
195///
196/// A SIMDVector can be thought of as a homogeneous array `[T; N]` (with potentially
197/// stricter alignment requirements) that generally behave for arithmetic purposes like
198/// scalars in the sense that if
199/// ```ignore
200/// fn add(a: V, b: V) -> V
201/// where V: SIMDVector {
202/// a + b
203/// }
204/// ```
205/// will have the same semantics of broadcasting the `+` operation across all lanes in the
206/// vector.
207pub trait SIMDVector: Copy + std::fmt::Debug {
208 /// The architecture this vector belongs to.
209 type Arch: arch::Sealed;
210
211 /// The type of each element in the vector.
212 type Scalar: Copy + std::fmt::Debug;
213
214 /// The underlying representation.
215 type Underlying: Copy;
216
217 /// The number of lanes in the vector.
218 const LANES: usize;
219
220 /// The value of `LANES` but in the type domain so we can use it to constrain other
221 /// aspects of this trait.
222 ///
223 /// Should be the type `Const<Self::LANES>`.
224 type ConstLanes: ArrayType<Self::Scalar> + BitMaskType<Self::Arch>;
225
226 /// The expanded logical mask representation.
227 /// This may-or-may-not actually be a bitmask, but should be easily convertible to and
228 /// from a bitmask.
229 type Mask: SIMDMask<Arch = Self::Arch>
230 + From<<Self::ConstLanes as BitMaskType<Self::Arch>>::Type>
231 + Into<<Self::ConstLanes as BitMaskType<Self::Arch>>::Type>;
232
233 /// Whether or not this is an emulated vector.
234 ///
235 /// Emulated vectors are backed by Rust arrays and use scalar loops to implement
236 /// arithmetic operations.
237 const EMULATED: bool;
238
239 /// Return the architecture object associated with this vector.
240 ///
241 /// # NOTE
242 ///
243 /// This is safe because construction of `self` serves as the witness that we are on
244 /// a compatible architecture.
245 fn arch(self) -> Self::Arch;
246
247 /// Return the default value for the type. This is always the numeric 0 for the
248 /// associated scalar type.
249 fn default(arch: Self::Arch) -> Self;
250
251 /// Return the underlying type.
252 fn to_underlying(self) -> Self::Underlying;
253
254 /// Construct from the underlying type.
255 fn from_underlying(arch: Self::Arch, repr: Self::Underlying) -> Self;
256
257 /// Retrieve the contents as an array.
258 fn to_array(self) -> <Self::ConstLanes as ArrayType<Self::Scalar>>::Type;
259
260 /// Construct from the associated array.
261 ///
262 /// The argument `arch` provides a "proof of compatibility" as `A` can only be safely
263 /// instantiated when all the requirements for the architecture are met.
264 fn from_array(arch: Self::Arch, x: <Self::ConstLanes as ArrayType<Self::Scalar>>::Type)
265 -> Self;
266
267 /// Broadcast the provided scalar across all lanes.
268 ///
269 /// The argument `arch` provides a "proof of compatibility" as `A` can only be safely
270 /// instantiated when all the requirements for the architecture are met.
271 fn splat(arch: Self::Arch, value: Self::Scalar) -> Self;
272
273 /// Return the number of lanes in this vector.
274 fn num_lanes() -> usize {
275 Self::LANES
276 }
277
278 /// Load `<Self as SIMDVector>::LANES` number of elements starting at the provided
279 /// pointer.
280 ///
281 /// There are no alignment requirements on `ptr`.
282 ///
283 /// # Safety
284 ///
285 /// A contiguous read of `<Self as SIMDVector>::LANES` must touch valid memory.
286 unsafe fn load_simd(arch: Self::Arch, ptr: *const <Self as SIMDVector>::Scalar) -> Self;
287
288 /// Load `<Self as SIMDVector>::LANES` number of elements starting at the provided
289 /// pointer.
290 ///
291 /// There are no alignment requirements on `ptr`.
292 ///
293 /// Entries in the mask that evaluate to `false` will not be accessed.
294 /// This makes it safe to use this function with lanes masked out that would otherwise
295 /// cross a page boundary or otherwise cause an out-of-bounds read.
296 ///
297 /// # Safety
298 ///
299 /// Offsets from the `ptr` where the mask evaluates to true must be dereferenceable to
300 /// the underlying scalar type.
301 unsafe fn load_simd_masked_logical(
302 arch: Self::Arch,
303 ptr: *const <Self as SIMDVector>::Scalar,
304 mask: <Self as SIMDVector>::Mask,
305 ) -> Self;
306
307 /// The same as `load_simd_masked_logical` but taking a BitMask instead.
308 ///
309 /// There are no alignment requirements on `ptr`.
310 ///
311 /// No load attempt will be made to lanes that are masked out.
312 ///
313 /// # Safety
314 ///
315 /// Offsets from the `ptr` where the mask evaluates to true must be dereferenceable to
316 /// the underlying scalar type. For implementations using the provided default, the
317 /// conversion from the bitmask to the actual mask must be correct.
318 #[inline(always)]
319 unsafe fn load_simd_masked(
320 arch: Self::Arch,
321 ptr: *const <Self as SIMDVector>::Scalar,
322 mask: <<Self as SIMDVector>::ConstLanes as BitMaskType<Self::Arch>>::Type,
323 ) -> Self {
324 // SAFETY: Bitmasks must be convertible to their corresponding logical mask.
325 // When the logical mask **is** a bitmask, this is a no-op.
326 unsafe { Self::load_simd_masked_logical(arch, ptr, mask.into()) }
327 }
328
329 /// The same as `load_simd_masked_logical`, but potentially specialized for situations
330 /// where it is known that some number of first elements will be accessed.
331 ///
332 /// If `first` is greater than or equal to the number of lanes, then all lanes will be
333 /// loaded.
334 ///
335 /// # Safety
336 ///
337 /// A contiguous read of `first.min(<Self as SIMDVector>::LANES)` must be valid.
338 #[inline(always)]
339 unsafe fn load_simd_first(
340 arch: Self::Arch,
341 ptr: *const <Self as SIMDVector>::Scalar,
342 first: usize,
343 ) -> Self {
344 // SAFETY: The implementation of `SIMDMask` must be correct.
345 unsafe {
346 Self::load_simd_masked_logical(
347 arch,
348 ptr,
349 <Self as SIMDVector>::Mask::keep_first(arch, first),
350 )
351 }
352 }
353
354 /// Store `<Self as SIMDVector>::LANES` number of elements contiguously starting at the
355 /// provided pointer.
356 ///
357 /// There are no alignment requirements on `ptr`.
358 ///
359 /// # Safety
360 ///
361 /// The pointed-to memory must adhere to Rust's exclusive reference rules.
362 ///
363 /// A contiguous store of `<Self as SIMDVector>::LANES` must touch valid memory.
364 unsafe fn store_simd(self, ptr: *mut <Self as SIMDVector>::Scalar);
365
366 /// Store `<Self as SIMDVector>::LANES` number of elements starting at the provided
367 /// pointer.
368 ///
369 /// There are no alignment requirements on `ptr`.
370 ///
371 /// Entries in the mask that evaluate to `false` will not be accessed.
372 /// This makes it safe to use this function with lanes masked out that would otherwise
373 /// cross a page boundary or otherwise cause an out-of-bounds write.
374 ///
375 /// # Safety
376 ///
377 /// The pointed-to memory must adhere to Rust's exclusive reference rules.
378 ///
379 /// Offsets from the `ptr` where the mask evaluates to true must be mutably
380 /// dereferenceable to the underlying scalar type.
381 unsafe fn store_simd_masked_logical(
382 self,
383 ptr: *mut <Self as SIMDVector>::Scalar,
384 mask: <Self as SIMDVector>::Mask,
385 );
386
387 /// The same as `store_simd_masked_logical` but taking a BitMask instead.
388 ///
389 /// There are no alignment requirements on `ptr`.
390 ///
391 /// No store attempt will be made to lanes that are masked out.
392 ///
393 /// # Safety
394 ///
395 /// The pointed-to memory must adhere to Rust's exclusive reference rules.
396 ///
397 /// Offsets from the `ptr` where the mask evaluates to true must be mutably
398 /// dereferenceable to the underlying scalar type.
399 ///
400 /// For implementations using the provided default, the conversion from the bitmask to
401 /// the actual mask must be correct.
402 #[inline(always)]
403 unsafe fn store_simd_masked(
404 self,
405 ptr: *mut <Self as SIMDVector>::Scalar,
406 mask: <<Self as SIMDVector>::ConstLanes as BitMaskType<Self::Arch>>::Type,
407 ) {
408 // SAFETY: Bitmasks must be convertible to their corresponding logical mask.
409 // When the logical mask **is** a bitmask, this is a no-op.
410 unsafe { self.store_simd_masked_logical(ptr, mask.into()) }
411 }
412
413 /// The same as `store_simd_masked_logical`, but potentially specialized for situations
414 /// where it is known that some number of first elements will be accessed.
415 ///
416 /// If `first` is greater than or equal to the number of lanes, then all lanes will be
417 /// written.
418 ///
419 /// There are no alignment requirements on `ptr`.
420 ///
421 /// # Safety
422 ///
423 /// The pointed-to memory must adhere to Rust's exclusive reference rules.
424 ///
425 /// A contiguous write of `first.min(<Self as SIMDVector>::LANES)` must be valid.
426 #[inline(always)]
427 unsafe fn store_simd_first(self, ptr: *mut <Self as SIMDVector>::Scalar, first: usize) {
428 // SAFETY: The implementation of `SIMDMask` must be correct.
429 unsafe {
430 self.store_simd_masked_logical(
431 ptr,
432 <Self as SIMDVector>::Mask::keep_first(self.arch(), first),
433 )
434 }
435 }
436
437 /// Perform a numeric cast on each element, returning a new SIMD vector.
438 ///
439 /// See also: [`SIMDCast`].
440 #[inline(always)]
441 fn cast<T>(self) -> <Self as SIMDCast<T>>::Cast
442 where
443 Self: SIMDCast<T>,
444 {
445 self.simd_cast()
446 }
447}
448
449/// Efficiently perform the operation
450/// ```ignore
451/// self * rhs + accumulator
452/// ```
453/// with the following semantics dependent on the associated scalar type.
454///
455/// * floating point: Perform a fused multiply-add, implementing the operation with only a
456/// single rounding instance.
457///
458/// * integer: Perform the multiplication followed by the accumulation. Both binary
459/// operations will be performed using wrap-around arithmetic.
460pub trait SIMDMulAdd {
461 fn mul_add_simd(self, rhs: Self, accumulator: Self) -> Self;
462}
463
464/// Efficiently retrieve the pairwise minimum or maximum for the two arguments.
465///
466/// Each function comes in two flavors:
467///
468/// * Standard (suffixed): Compute the minimum or maximum in a way that is equivalent to
469/// Rust's built-in minimum or maximum functions.
470///
471/// When the scalar type is integral, the behavior is unambiguous.
472///
473/// When the scalar type is a floating point and one value of a pair is NaN, the other
474/// value is returned. When the result is zero, either a positive or a negative zero can
475/// be returned.
476///
477/// * Fast (unsuffixed): Compute the minimum or maximum using the fastest possible method
478/// on the given architecture with non-standard NaN handling.
479///
480/// When the scalar type is integral, the behavior is the same as the standard
481/// implementations.
482///
483/// When the scalar type is a floating point, the implementation is allowed to differ
484/// with respect to NaN handling. That is, when one of the arguments is NaN, the
485/// implementation is allowed to return **either** the other argument (like the standard
486/// implementation) or NaN. Like the standard implementation, if the result is zero, then
487/// a zero of either sign can be returned.
488///
489/// This method should be preferred when precise NaN handling is not needed as it can be
490/// more efficient.
491pub trait SIMDMinMax: Sized {
492 /// Return the pairwise minimum of `self` and `rhs`, subject to looser NaN handling.
493 fn min_simd(self, rhs: Self) -> Self;
494
495 /// Return the pairwise minimum of `self` and `rhs` as if by applying the standard
496 /// library's `min` method for the scalar type.
497 #[inline(always)]
498 fn min_simd_standard(self, rhs: Self) -> Self {
499 self.min_simd(rhs)
500 }
501
502 /// Return the pairwise maximum of `self` and `rhs`, subject to looser NaN handling.
503 fn max_simd(self, rhs: Self) -> Self;
504
505 /// Return the pairwise maximum of `self` and `rhs` as if by applying the standard
506 /// library's `max` method for the scalar type.
507 #[inline(always)]
508 fn max_simd_standard(self, rhs: Self) -> Self {
509 self.max_simd(rhs)
510 }
511}
512
513/// Take the absolute value of each lane.
514///
515/// # Notes
516///
517/// For signed integer types T, this works as expected for all values except for `T::MIN`,
518/// in which case `T::MIN` is returned. This keeps the behavior in line with hardware
519/// intrinsics.
520///
521/// A correct answer can be retrieved by casting the result to the equivalent unsigned
522/// integer.
523pub trait SIMDAbs {
524 fn abs_simd(self) -> Self;
525}
526
527/// A SIMD equivalent of `std::cmp::PartialEq`.
528///
529/// Instead of a boolean, return `Self::Mask` containing the result of the element-wise
530/// comparison of the two vectors.
531pub trait SIMDPartialEq: SIMDVector {
532 /// SIMD equivalent of `std::cmp::PartialEq::eq`, applying the latter trait to each
533 /// lane-wise pair of elements in `self` and `other`.
534 fn eq_simd(self, other: Self) -> Self::Mask;
535
536 /// SIMD equivalent of `std::cmp::PartialEq::neq`, applying the latter trait to each
537 /// lane-wise pair of elements in `self` and `other`.
538 fn ne_simd(self, other: Self) -> Self::Mask;
539}
540
541/// A SIMD equivalent of `std::cmp::PartialOrd`.
542///
543/// Instead of a boolean, return `Self::Mask` containing the result of the element-wise
544/// comparisons of the two vectors.
545pub trait SIMDPartialOrd: SIMDVector {
546 /// SIMD equivalent of `std::cmp::PartialOrd::lt`.
547 fn lt_simd(self, other: Self) -> Self::Mask;
548
549 /// SIMD equivalent of `std::cmp::PartialOrd::le`.
550 fn le_simd(self, other: Self) -> Self::Mask;
551
552 //////////////////////
553 // Provided Methods //
554 //////////////////////
555
556 /// SIMD equivalent of `std::cmp::PartialOrd::gt`.
557 ///
558 /// Types are free to override the provided method if a more efficient implementation
559 /// is possible.
560 #[inline(always)]
561 fn gt_simd(self, other: Self) -> Self::Mask {
562 other.lt_simd(self)
563 }
564
565 /// SIMD equivalent of `std::cmp::PartialOrd::ge`.
566 ///
567 /// Types are free to override the provided method if a more efficient implementation
568 /// is possible.
569 #[inline(always)]
570 fn ge_simd(self, other: Self) -> Self::Mask {
571 other.le_simd(self)
572 }
573}
574
575/// Perform a pairwise reducing sum of all lanes in the vector and return the result as a
576/// scalar.
577///
578/// For example, the summing pattern for a vector of 8 elements is as follows:
579/// ```text
580/// let v0 = [x0, x1, x2, x3, x4, x5, x6, x7];
581/// let v1 = [v0[0] + v0[4], v0[1] + v0[5], v0[2] + v0[6], v0[3] + v0[7]];
582/// let v2 = [v1[0] + v1[2], v1[1] + v1[3]];
583/// v2[0] + v2[1]
584/// ```
585pub trait SIMDSumTree: SIMDVector {
586 fn sum_tree(self) -> <Self as SIMDVector>::Scalar;
587}
588
589/// A vectorized "if else".
590pub trait SIMDSelect<V: SIMDVector>: SIMDMask {
591 fn select(self, x: V, y: V) -> V;
592}
593
594/// Optimized dot-product style accumulation.
595///
596/// This tries to match against intrinsics like:
597///
598/// * `_mm256_madd_epi16`
599/// * `_mm256_dpbusd_epi32`
600///
601/// The gist is to perform element-wise multiplication between left and right, promoting the
602/// result to the element-type of `Self`, adding adjacent entries
603///
604/// # Precise Enumeration of Semantics for Implementations
605///
606/// The semantics depend on the source and destination type, but are intended to be the same
607/// for each type combination across architectures.
608///
609/// ## `SIMDDotProduct<i16x16> for i32x8`
610///
611/// 1. Perform multiplication as `i16x16 x i16x16` as if converting each lane to `i32`,
612/// resulting in effectively `i32x16`. No overflow can happen.
613/// 2. Add together adjacent pairs in the resulting `i32x16` to yield `i32x8`. Again, this
614/// step cannot overflow.
615/// 3. Add the resulting `i32x8` into `Self`, returning the result.
616///
617/// ## `SIMDDotProduct<u8x32, i8x32> for i32x8`
618///
619/// 1. Perform multiplication as `i32x32 x i32x32` as if converting each lane to `i32`,
620/// resulting in effectively `i32x32`. No overflow can happen.
621/// 2. Sum together consecutive groups of 4 in the resulting `i32x32` to yield `i32x8`.
622/// 3. Add the resulting `i32x8` into `Self`.
623///
624/// The same applies when the order of `u8x32` and `i8x32` are swapped and for types that
625/// are twice as wide.
626///
627/// The main goal of this function is to hit VNNI instructions like `_mm512_dpbusd_epi32`
628/// that can do the whole operation in a single go on the `V4` architecture. Use of this
629/// instruction is not recommended on non-`V4` architectures.
630pub trait SIMDDotProduct<L: SIMDVector, R: SIMDVector = L> {
631 /// Element wise multiply each component of `left` and `right`, promoting the
632 /// intermediate results to a higher precision.
633 ///
634 /// Then, horizontally add together groups of the accumulated values and add the
635 /// resulting sums to `self`.
636 ///
637 /// The size of the group depends on the relative number of lanes in `Self` and `Source`.
638 ///
639 /// However, it is required that `Self::num_lanes()` evenly divides `Source::num_lanes()`
640 /// so that the size of each group is uniform.
641 fn dot_simd(self, left: L, right: R) -> Self;
642}
643
644/// Perform a bit-cast from one SIMD type to another.
645pub trait SIMDReinterpret<To: SIMDVector>: SIMDVector {
646 fn reinterpret_simd(self) -> To;
647}
648
649/// Perform a numeric cast on the scalar type.
650///
651/// Unlike `From`, this conversion is allowed to be lossy, with similar semantics to
652/// numeric casts in scalar Rust.
653///
654/// This is meant to model Rust's numeric conversion with the "as" operator.
655pub trait SIMDCast<T>: SIMDVector {
656 /// The [`SIMDVector`] type of the result.
657 type Cast: SIMDVector<Scalar = T, ConstLanes = Self::ConstLanes>;
658 /// Perform the cast.
659 fn simd_cast(self) -> Self::Cast;
660}
661
662/// A roll-up of traits required for SIMD floating point types.
663pub trait SIMDFloat:
664 SIMDVector
665 + std::ops::Add<Output = Self>
666 + std::ops::Mul<Output = Self>
667 + std::ops::Sub<Output = Self>
668 + SIMDMulAdd
669 + SIMDMinMax
670 + SIMDPartialEq
671 + SIMDPartialOrd
672{
673}
674
675impl<T> SIMDFloat for T where
676 T: SIMDVector
677 + std::ops::Add<Output = Self>
678 + std::ops::Mul<Output = Self>
679 + std::ops::Sub<Output = Self>
680 + SIMDMulAdd
681 + SIMDMinMax
682 + SIMDPartialEq
683 + SIMDPartialOrd
684{
685}
686
687/// A roll-up of traits required for SIMD integer types.
688pub trait SIMDUnsigned:
689 SIMDVector
690 + std::ops::Add<Output = Self>
691 + std::ops::Mul<Output = Self>
692 + std::ops::Sub<Output = Self>
693 + std::ops::BitAnd<Output = Self>
694 + std::ops::BitOr<Output = Self>
695 + std::ops::BitXor<Output = Self>
696 + std::ops::Shr<Output = Self>
697 + std::ops::Shl<Output = Self>
698 + std::ops::Shr<Self::Scalar, Output = Self>
699 + std::ops::Shl<Self::Scalar, Output = Self>
700 + SIMDMulAdd
701 + SIMDPartialEq
702 + SIMDPartialOrd
703{
704}
705
706impl<T> SIMDUnsigned for T where
707 T: SIMDVector
708 + std::ops::Add<Output = Self>
709 + std::ops::Mul<Output = Self>
710 + std::ops::Sub<Output = Self>
711 + std::ops::BitAnd<Output = Self>
712 + std::ops::BitOr<Output = Self>
713 + std::ops::BitXor<Output = Self>
714 + std::ops::Shr<Output = Self>
715 + std::ops::Shl<Output = Self>
716 + std::ops::Shr<Self::Scalar, Output = Self>
717 + std::ops::Shl<Self::Scalar, Output = Self>
718 + SIMDMulAdd
719 + SIMDPartialEq
720 + SIMDPartialOrd
721{
722}
723
724pub trait SIMDSigned: SIMDUnsigned + SIMDAbs {}
725impl<T> SIMDSigned for T where T: SIMDUnsigned + SIMDAbs {}
726
727/// Element-wise zip and unzip of two half-width SIMD vectors.
728///
729/// `zip` interleaves elements from two halves into one full-width vector:
730/// `zip([a0, a1, …], [b0, b1, …]) = [a0, b0, a1, b1, …]`
731///
732/// `unzip` is the inverse, separating even- and odd-indexed elements:
733/// `unzip([a0, b0, a1, b1, …]) = ([a0, a1, …], [b0, b1, …])`
734///
735/// Two additional "flat" methods operate on a single full-width register:
736///
737/// `zip_flat` treats the low half of `self` as one input and the high half as the other,
738/// interleaving them in-place:
739/// `zip_flat([a0, a1, …, b0, b1, …]) = [a0, b0, a1, b1, …]`
740///
741/// `unzip_flat` is the inverse, collecting even-indexed elements into the low half and
742/// odd-indexed elements into the high half:
743/// `unzip_flat([a0, b0, a1, b1, …]) = [a0, a1, …, b0, b1, …]`
744///
745/// `zip` and `unzip` are required. `zip_flat` and `unzip_flat` have default
746/// implementations that delegate through [`SplitJoin::split`] / [`SplitJoin::join`].
747/// Backends should override whichever pair is cheapest on their ISA.
748pub trait ZipUnzip: crate::SplitJoin + Sized {
749 /// Interleave elements from `halves.lo` and `halves.hi` into `Self`.
750 fn zip(halves: crate::LoHi<Self::Halved>) -> Self;
751
752 /// Separate even-indexed elements into `lo` and odd-indexed into `hi`.
753 fn unzip(self) -> crate::LoHi<Self::Halved>;
754
755 /// Interleave in-place: the low half of `self` supplies the even-indexed
756 /// positions and the high half supplies the odd-indexed positions.
757 ///
758 /// Equivalent to `Self::zip(self.split())` but may be implemented with a
759 /// single cross-lane permute on architectures that support it.
760 fn zip_flat(self) -> Self {
761 Self::zip(self.split())
762 }
763
764 /// Deinterleave in-place: even-indexed elements are collected into the low
765 /// half of the result and odd-indexed elements into the high half.
766 ///
767 /// Equivalent to `Self::join(self.unzip())` but may be implemented with a
768 /// single cross-lane permute on architectures that support it.
769 fn unzip_flat(self) -> Self {
770 Self::unzip(self).join()
771 }
772}
773
774// Since it is so difficult to work directly with generic integers, resort to using a macro
775// to stamp out implementations of `SIMDMask` for `BitMask`.
776//
777// The argument `submask` is a bit-pattern to apply to the underlying type and is to mask
778// out upper-bits of the representation for 2 and 4 bit masks.
779macro_rules! impl_simd_mask_for_bitmask {
780 ($N:literal, $repr:ty, $submask:expr) => {
781 impl<A: arch::Sealed> SIMDMask for BitMask<$N, A> {
782 type Arch = A;
783 type Underlying = $repr;
784 type BitMask = Self;
785 const ISBITS: bool = true;
786 const LANES: usize = $N;
787
788 #[inline(always)]
789 fn arch(self) -> A {
790 self.get_arch()
791 }
792
793 #[inline(always)]
794 fn to_underlying(self) -> Self::Underlying {
795 self.0
796 }
797
798 #[inline(always)]
799 fn from_underlying(arch: A, value: Self::Underlying) -> Self {
800 Self::from_int(arch, value)
801 }
802
803 #[inline(always)]
804 fn keep_first(arch: A, i: usize) -> Self {
805 // Ensure that providing a value that is too big still yields sensible
806 // results.
807 let i = i.min(Self::LANES);
808
809 // Handle 64-bit integers properly.
810 // It is expected that the compiler will be able to optimize out this branch
811 // for non-64-bit types.
812 if Self::LANES == 64 && i == 64 {
813 return Self::from_underlying(arch, Self::Underlying::MAX);
814 }
815
816 let one: u64 = 1;
817 // "as" conversion in Rust performs truncation on the integers.
818 Self::from_underlying(arch, ((one << i) - one) as Self::Underlying)
819 }
820
821 #[inline(always)]
822 fn get_unchecked(&self, i: usize) -> bool {
823 if i >= Self::LANES {
824 false
825 } else {
826 (self.0 >> i) % 2 == 1
827 }
828 }
829
830 #[inline(always)]
831 fn first(&self) -> Option<usize> {
832 let count = self.0.trailing_zeros() as usize;
833 if count >= Self::LANES {
834 None
835 } else {
836 Some(count)
837 }
838 }
839
840 // End of recursion functions
841 fn from_fn<F>(arch: A, mut f: F) -> Self
842 where
843 F: FnMut(usize) -> bool,
844 {
845 let mut x: $repr = 0;
846 for i in 0..Self::LANES {
847 if f(i) {
848 x |= (1 << i);
849 }
850 }
851 Self::from_underlying(arch, x)
852 }
853
854 #[inline(always)]
855 fn any(self) -> bool {
856 self.0 != 0
857 }
858
859 #[inline(always)]
860 fn all(self) -> bool {
861 let v: u64 = self.0.into();
862
863 // We again need to handle 64-wide masks differently.
864 if $N == 64 {
865 v == u64::MAX
866 } else {
867 v == (1 << $N) - 1
868 }
869 }
870
871 #[inline(always)]
872 fn count(self) -> usize {
873 // We keep the invariant that all constructors of `BitMask` must zero out
874 // upper bits (for 2 and 4 width BitMasks).
875 self.0.count_ones() as usize
876 }
877 }
878
879 // Transformation from bitmask to integer.
880 impl From<BitMask<$N>> for $repr {
881 fn from(value: BitMask<$N>) -> Self {
882 value.to_underlying()
883 }
884 }
885 };
886}
887
888// Stamp out a bunch of implementations.
889impl_simd_mask_for_bitmask!(1, u8, 0x1);
890impl_simd_mask_for_bitmask!(2, u8, 0x3);
891impl_simd_mask_for_bitmask!(4, u8, 0xf);
892impl_simd_mask_for_bitmask!(8, u8, u8::MAX);
893impl_simd_mask_for_bitmask!(16, u16, u16::MAX);
894impl_simd_mask_for_bitmask!(32, u32, u32::MAX);
895impl_simd_mask_for_bitmask!(64, u64, u64::MAX);
896
897#[cfg(test)]
898mod test_traits {
899 use rand::{
900 SeedableRng,
901 distr::{Distribution, StandardUniform},
902 rngs::StdRng,
903 };
904
905 use super::*;
906 use crate::{
907 ARCH, arch,
908 splitjoin::{LoHi, SplitJoin},
909 test_utils,
910 };
911
912 // Allow unsigned 128-bit integers to be converted to narrow types.
913 trait FromU128 {
914 fn from_(value: u128) -> Self;
915 }
916
917 impl FromU128 for u8 {
918 fn from_(value: u128) -> Self {
919 value as u8
920 }
921 }
922 impl FromU128 for u16 {
923 fn from_(value: u128) -> Self {
924 value as u16
925 }
926 }
927 impl FromU128 for u32 {
928 fn from_(value: u128) -> Self {
929 value as u32
930 }
931 }
932 impl FromU128 for u64 {
933 fn from_(value: u128) -> Self {
934 value as u64
935 }
936 }
937
938 /// Test that bitmasks faithfully implement the trait `SIMDMask`.
939 ///
940 /// Since conversion between `u128` and arbitrary generic parameters `T` are now
941 /// allowed, we take a conversion function to do this for us with all the known type
942 /// information.
943 fn test_bitmask_impl<const N: usize, T>()
944 where
945 Const<N>: SupportedLaneCount, // this value of `N` has a bitmask representation.
946 T: std::fmt::Debug + std::cmp::Eq + FromU128 + From<BitMask<N, arch::Current>>,
947 BitMask<N, arch::Current>: SIMDMask<Arch = arch::Current, Underlying = T>,
948 {
949 const MAXLEN: usize = 64;
950 assert_eq!(N, BitMask::<N, arch::Current>::LANES);
951
952 // The bit-mask corresponding to all lanes.
953 let one = 1_u128;
954
955 let all: u128 = (one << N) - one;
956
957 for i in 0..=MAXLEN {
958 let mask = BitMask::<N, arch::Current>::keep_first(arch::current(), i);
959
960 let expected: u128 = ((one << i) - one) & all;
961
962 // Cannot use "as" since T is not known to be a primitive type ...
963 assert_eq!(mask.to_underlying(), T::from_(expected));
964 assert_eq!(T::from_(expected), mask.into());
965 for j in 0..=MAXLEN {
966 let b = mask.get_unchecked(j);
967 let o = mask.get(j);
968
969 let expected: bool = j < i;
970 if j < N {
971 assert_eq!(b, expected);
972 assert_eq!(o.unwrap(), expected);
973 } else {
974 assert!(!b);
975 assert!(o.is_none());
976 }
977 }
978
979 // Check reductions.
980 if i == 0 {
981 assert!(!mask.any());
982 assert!(!mask.all());
983 assert!(mask.none());
984 } else if i >= N {
985 assert!(mask.any());
986 assert!(mask.all());
987 assert!(!mask.none());
988 } else {
989 assert!(mask.any());
990 assert!(!mask.all());
991 assert!(!mask.none());
992 }
993 }
994 }
995
996 #[test]
997 fn test_bitmask() {
998 test_bitmask_impl::<1, u8>();
999 test_bitmask_impl::<2, u8>();
1000 test_bitmask_impl::<4, u8>();
1001 test_bitmask_impl::<8, u8>();
1002 test_bitmask_impl::<16, u16>();
1003 test_bitmask_impl::<32, u32>();
1004 test_bitmask_impl::<64, u64>();
1005 }
1006
1007 fn test_bitmask_splitjoin_impl<const N: usize, const NHALF: usize>(ntrials: usize, seed: u64)
1008 where
1009 Const<N>: SupportedLaneCount,
1010 Const<NHALF>: SupportedLaneCount,
1011 BitMask<N, arch::Current>:
1012 SIMDMask<Arch = arch::Current> + SplitJoin<Halved = BitMask<NHALF, arch::Current>>,
1013 BitMask<NHALF, arch::Current>: SIMDMask<Arch = arch::Current>,
1014 {
1015 let mut rng = StdRng::seed_from_u64(seed);
1016 for _ in 0..ntrials {
1017 let base = BitMask::<N>::from_fn(ARCH, |_| StandardUniform {}.sample(&mut rng));
1018 let LoHi { lo, hi } = base.split();
1019
1020 for i in 0..NHALF {
1021 assert_eq!(base.get(i).unwrap(), lo.get(i).unwrap());
1022 }
1023
1024 for i in 0..NHALF {
1025 assert_eq!(base.get(i + NHALF).unwrap(), hi.get(i).unwrap());
1026 }
1027
1028 let joined = BitMask::<N>::join(LoHi::new(lo, hi));
1029 bitmasks_equal(base, joined);
1030 }
1031 }
1032
1033 #[test]
1034 fn test_bitmask_splitjoin() {
1035 test_bitmask_splitjoin_impl::<2, 1>(100, 0xcbdbdca310caec88);
1036 test_bitmask_splitjoin_impl::<4, 2>(100, 0x9c8b9b6c70d941c5);
1037 test_bitmask_splitjoin_impl::<8, 4>(100, 0xc81a25918b683d39);
1038 test_bitmask_splitjoin_impl::<16, 8>(50, 0xad045b437c3fa0cc);
1039 test_bitmask_splitjoin_impl::<32, 16>(50, 0xe710ccdbbd329c77);
1040 test_bitmask_splitjoin_impl::<64, 32>(25, 0xd6697e3c534fc134);
1041 }
1042
1043 // Explicit tests to ensure that upper bits are masked out during construction of
1044 // 2 and 4 bit masks.
1045 #[test]
1046 fn test_zeroing() {
1047 let b = BitMask::<2>::from_underlying(arch::current(), 0xff);
1048 assert_eq!(b.to_underlying(), 0x3);
1049 assert_eq!(b.count(), 2);
1050
1051 let b = BitMask::<4>::from_underlying(arch::current(), 0xff);
1052 assert_eq!(b.to_underlying(), 0xf);
1053 assert_eq!(b.count(), 4);
1054 }
1055
1056 fn bitmasks_equal<const N: usize>(x: BitMask<N, arch::Current>, y: BitMask<N, arch::Current>)
1057 where
1058 Const<N>: SupportedLaneCount,
1059 BitMask<N, arch::Current>: SIMDMask,
1060 {
1061 assert_eq!(x.0, y.0);
1062 }
1063
1064 // A helper macro to run a BitMask through the SIMDMask test routines.
1065 macro_rules! test_simdmask {
1066 ($N:literal) => {
1067 paste::paste! {
1068 #[test]
1069 fn [<test_simd_mask_ $N>]() {
1070 let arch = arch::current();
1071 test_utils::mask::test_keep_first::<BitMask<$N, arch::Current>, $N, _, _>(
1072 arch,
1073 bitmasks_equal
1074 );
1075 test_utils::mask::test_from_fn::<BitMask<$N, arch::Current>, $N, _, _>(
1076 arch,
1077 bitmasks_equal
1078 );
1079 test_utils::mask::test_reductions::<BitMask<$N, arch::Current>, $N, _, _>(
1080 arch,
1081 bitmasks_equal
1082 );
1083 test_utils::mask::test_first::<BitMask<$N, arch::Current>, $N, _, _>(
1084 arch,
1085 bitmasks_equal
1086 );
1087 }
1088 }
1089 };
1090 }
1091
1092 test_simdmask!(2);
1093 test_simdmask!(4);
1094 test_simdmask!(8);
1095 test_simdmask!(16);
1096 test_simdmask!(32);
1097 test_simdmask!(64);
1098}