Skip to main content

hermes_simd_core/
kernel.rs

1//! Low-level SIMD operations trait implemented per architecture and primitive type.
2//!
3//! # Extension Surface (v2+)
4//!
5//! New in this iteration:
6//! - `sub(a, b)` — elementwise subtraction, required for `Sub` ElementOp strategy.
7//! - `mask_from_bitmask(bm)` — convert `BitMask<LANE_COUNT>` to native mask; default
8//!   calls `mask_from_bools` via `BitMask::to_bools()`. AVX-512 impls override with direct cast.
9//! - `zero()` — returns a vector of zeros; default implementation uses `splat(T::ZERO)`.
10//!   Backends may override with an architecture-specific XOR-zero idiom if profiling shows benefit.
11//!
12//! # Extension Surface (v2)
13//!
14//! Beyond the base load/store/arithmetic/reduce methods, `SimdKernel` now exposes:
15//!
16//! - **Masked operations** (`masked_load_unaligned`, `masked_store_unaligned`,
17//!   `masked_add`, `masked_mul`, `masked_fmadd`, `masked_sum_reduce`) — predicated
18//!   arithmetic using hardware mask registers. The `src` parameter follows AVX-512
19//!   merge-masking semantics: lanes where `mask[i] = 0` are taken from `src`.
20//!
21//! - **Compress / expand** — scatter/gather from/to contiguous storage:
22//!   - `compress`: packs selected lanes (`mask[i]=1`) to low lanes of result.
23//!   - `expand`: scatters low lanes of `src` to positions where `mask[i]=1`.
24//!
25//! - **Gather** (`gather`, `gather_masked`) — indirect indexed load from a base pointer.
26//!
27//! - **Mask construction** (`mask_from_bools`, `leading_k_mask`) — build masks from
28//!   boolean arrays or lane counts for tail handling.
29//!
30//! # Architecture Mapping
31//!
32//! | Method | AVX-512 | AVX2 | NEON | Scalar |
33//! |--------|---------|------|------|--------|
34//! | `masked_add` | `_mm512_mask_add_ps` | `_mm256_blendv_ps(src,add,mask)` | `vbslq_f32` | loop+if |
35//! | `compress` | `_mm512_mask_compressstoreu_ps` | emulated | emulated | loop+if |
36//! | `gather` | `_mm512_i32gather_ps` | `_mm256_i32gather_ps` | emulated | loop |
37
38/// Lane capacity of the fixed scalar-fallback stack buffers used by the default
39/// `SimdKernel` methods (`scan_vector`, `swap_adjacent`, `dup_even`/`dup_odd`,
40/// and the `kernel_helpers` scalar emulations). A backend's [`SimdKernel::LANE_COUNT`]
41/// must not exceed this, or `store_unaligned` into those buffers would overflow
42/// the stack. The current workspace maximum is 64 (AVX-512 `i8`, 64×`i8`); the
43/// bound is checked at compile time by [`SimdKernel::LANE_BOUND_CHECK`], so a
44/// future wider backend fails to build rather than silently overflowing the stack.
45pub const MAX_SIMD_LANES: usize = 64;
46
47/// Abstract trait defining low-level vector operations.
48///
49/// Implemented by ZST architecture markers. All methods are `unsafe` — the caller is
50/// responsible for ensuring target-feature prerequisites are satisfied. The `#[target_feature]`
51/// attribute on each `impl` block ensures the compiler emits the correct machine instruction;
52/// calling from a non-gated context requires wrapping in an `unsafe { ... }` block inside
53/// a function that is itself gated by `#[target_feature(enable = "...")]`.
54///
55/// # Examples
56///
57/// Use the always-available `Scalar` backend for cross-platform code paths:
58///
59/// ```rust
60/// use hermes_simd_intrinsics::Scalar;
61/// use hermes_simd_core::kernel::SimdKernel;
62///
63/// // SAFETY: `Scalar` requires no special ISA features.
64/// let splat4: <Scalar as SimdKernel<f32>>::Vector =
65///     unsafe { <Scalar as SimdKernel<f32>>::splat(1.0_f32) };
66/// let sum: f32 = unsafe { <Scalar as SimdKernel<f32>>::sum_reduce(splat4) };
67/// assert_eq!(sum, <Scalar as SimdKernel<f32>>::LANE_COUNT as f32);
68/// ```
69pub trait SimdKernel<T: crate::scalar::Scalar>:
70    crate::private::Sealed + Send + Sync + Sized + 'static
71{
72    /// The underlying raw register/vector type for this architecture and element type.
73    type Vector: Copy + Send + Sync + 'static;
74
75    /// Hardware-native mask type.
76    ///
77    /// - AVX-512 f32: `__mmask16`
78    /// - AVX-512 f64: `__mmask8`
79    /// - AVX2 f32: `__m256` (float blend mask)
80    /// - AVX2 f64: `__m256d`
81    /// - NEON f32: `uint32x4_t`
82    /// - NEON f64: `uint64x2_t`
83    /// - Scalar f32: `[bool; 4]`
84    /// - Scalar f64: `[bool; 2]`
85    type Mask: Copy + Send + Sync + 'static;
86
87    /// Integer index vector for gather operations.
88    ///
89    /// - AVX-512 f32 (16-lane): `__m512i` (16xi32)
90    /// - AVX-512 f64 (8-lane): `__m256i` (8xi32)
91    /// - AVX2 f32 (8-lane): `__m256i` (8xi32)
92    /// - AVX2 f64 (4-lane): `__m128i` (4xi32)
93    /// - NEON / Scalar: `[i32; LANE_COUNT]`
94    type IndexVector: Copy + Send + Sync + 'static;
95
96    /// Number of primitive elements of type `T` in one `Vector`.
97    const LANE_COUNT: usize;
98
99    /// Compile-time guard that [`LANE_COUNT`](Self::LANE_COUNT) fits the fixed
100    /// `MAX_SIMD_LANES` scalar-fallback stack buffers. Referencing this const in
101    /// the buffer-using default methods forces the assertion to be evaluated for
102    /// each concrete backend at monomorphization, turning a would-be silent
103    /// stack-buffer overflow into a compile error.
104    const LANE_BOUND_CHECK: () = assert!(
105        Self::LANE_COUNT <= MAX_SIMD_LANES,
106        "SimdKernel::LANE_COUNT exceeds MAX_SIMD_LANES; widen the scalar-fallback stack buffers"
107    );
108
109    /// Loop unrolling register accumulation factor to break loop-carried dependency chains.
110    const UNROLL_FACTOR: usize = 4;
111
112    // -------------------------------------------------------------------------
113    // Load / Store
114    // -------------------------------------------------------------------------
115
116    /// Load a vector from an aligned pointer.
117    ///
118    /// # Safety
119    /// `ptr` must be valid for reads and aligned to `LANE_COUNT * size_of::<T>()` bytes.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use hermes_simd_intrinsics::Scalar;
125    /// use hermes_simd_core::kernel::SimdKernel;
126    ///
127    /// #[repr(align(64))]
128    /// struct AlignedBuf([f32; 4]);
129    ///
130    /// let buf = AlignedBuf([1.0, 2.0, 3.0, 4.0]);
131    /// // SAFETY: buf is 64-byte aligned and valid for LANE_COUNT reads.
132    /// let v = unsafe { <Scalar as SimdKernel<f32>>::load_aligned(buf.0.as_ptr()) };
133    /// let sum: f32 = unsafe { <Scalar as SimdKernel<f32>>::sum_reduce(v) };
134    /// assert_eq!(sum, 10.0_f32);
135    /// ```
136    unsafe fn load_aligned(ptr: *const T) -> Self::Vector;
137
138    /// Load a vector from an unaligned pointer.
139    ///
140    /// # Safety
141    /// `ptr` must be valid for reads.
142    unsafe fn load_unaligned(ptr: *const T) -> Self::Vector;
143
144    /// Store a vector to an aligned pointer.
145    ///
146    /// # Safety
147    /// `ptr` must be valid for writes and aligned to `LANE_COUNT * size_of::<T>()` bytes.
148    unsafe fn store_aligned(ptr: *mut T, val: Self::Vector);
149
150    /// Store a vector to an unaligned pointer.
151    ///
152    /// # Safety
153    /// `ptr` must be valid for writes.
154    unsafe fn store_unaligned(ptr: *mut T, val: Self::Vector);
155
156    /// Whether this backend provides a *non-temporal* (cache-bypassing) store
157    /// via [`store_streaming`](Self::store_streaming). Backends leaving this
158    /// `false` keep the regular store default; callers gate the streaming path
159    /// on this const so it is a compile-time branch, dead-code-eliminated where
160    /// unsupported.
161    const SUPPORTS_NT_STORE: bool = false;
162
163    /// Store a vector with a non-temporal (streaming) hint that bypasses the
164    /// cache, avoiding the read-for-ownership traffic a normal write-allocate
165    /// pays for write-only data larger than the last-level cache (measured 1.71×
166    /// on out-of-LLC AVX2 f32 elementwise writes; see `streaming_bench`).
167    ///
168    /// The default is a normal aligned store — correct but not cache-bypassing —
169    /// so a backend without a non-temporal instruction inherits safe behavior.
170    /// After a run of streaming stores the caller must issue
171    /// [`stream_write_barrier`](Self::stream_write_barrier) before the results
172    /// are read, since non-temporal stores are weakly ordered.
173    ///
174    /// # Safety
175    /// `ptr` must be valid for writes and aligned to `LANE_COUNT * size_of::<T>()`
176    /// bytes (non-temporal stores fault on misalignment).
177    #[inline(always)]
178    unsafe fn store_streaming(ptr: *mut T, val: Self::Vector) {
179        Self::store_aligned(ptr, val);
180    }
181
182    /// Fence ordering this backend's non-temporal stores before subsequent
183    /// reads. No-op by default (only meaningful where
184    /// [`store_streaming`](Self::store_streaming) is a weakly ordered
185    /// non-temporal store).
186    #[inline(always)]
187    fn stream_write_barrier() {}
188
189    // -------------------------------------------------------------------------
190    // Dense Arithmetic
191    // -------------------------------------------------------------------------
192
193    /// Elementwise addition: `a + b`.
194    ///
195    /// # Safety
196    /// Processor must support the required target feature.
197    unsafe fn add(a: Self::Vector, b: Self::Vector) -> Self::Vector;
198
199    /// Elementwise multiplication: `a * b`.
200    ///
201    /// # Safety
202    /// Processor must support the required target feature.
203    unsafe fn mul(a: Self::Vector, b: Self::Vector) -> Self::Vector;
204
205    /// Elementwise subtraction: `a - b`.
206    ///
207    /// Default: scalar fallback via `crate::kernel_helpers::generic_binary_op`.
208    /// Float and SIMD backends override this with the appropriate vectorized instruction
209    /// (e.g., `_mm256_sub_ps` for AVX2 f32, `vsubq_f32` for NEON).
210    ///
211    /// # Safety
212    /// Processor must support the required target feature.
213    unsafe fn sub(a: Self::Vector, b: Self::Vector) -> Self::Vector {
214        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| x - y)
215    }
216
217    /// Fused multiply-add: `(a * b) + c`.
218    ///
219    /// # Safety
220    /// Processor must support the required target feature.
221    unsafe fn fmadd(a: Self::Vector, b: Self::Vector, c: Self::Vector) -> Self::Vector;
222
223    /// Horizontal sum of all lanes.
224    ///
225    /// # Safety
226    /// Processor must support the required target feature.
227    ///
228    /// # Examples
229    ///
230    /// ```rust
231    /// use hermes_simd_intrinsics::Scalar;
232    /// use hermes_simd_core::kernel::SimdKernel;
233    ///
234    /// let data = [1.0_f32, 2.0, 3.0, 4.0];
235    /// // SAFETY: Scalar requires no ISA feature; pointer is valid for LANE_COUNT reads.
236    /// let v = unsafe { <Scalar as SimdKernel<f32>>::load_unaligned(data.as_ptr()) };
237    /// let total: f32 = unsafe { <Scalar as SimdKernel<f32>>::sum_reduce(v) };
238    /// assert!((total - 10.0_f32).abs() < 1e-6);
239    /// ```
240    unsafe fn sum_reduce(v: Self::Vector) -> T;
241
242    // -------------------------------------------------------------------------
243    // Masked Load / Store (merge masking: inactive lanes come from `src`)
244    // -------------------------------------------------------------------------
245
246    /// Masked load: active lanes loaded from `ptr`, inactive lanes taken from `src`.
247    ///
248    /// # Safety
249    /// `ptr` must be valid for reading `LANE_COUNT` elements. Active lanes determined by `mask`.
250    ///
251    /// Default: scalar-emulated merge via `kernel_helpers::generic_masked_load`.
252    /// Backends with a native masked load (AVX-512, SVE) override this.
253    unsafe fn masked_load_unaligned(
254        ptr: *const T,
255        mask: Self::Mask,
256        src: Self::Vector,
257    ) -> Self::Vector {
258        crate::kernel_helpers::generic_masked_load::<T, Self>(ptr, mask, src)
259    }
260
261    /// Masked store: active lanes written to `ptr`, inactive lanes left unchanged.
262    ///
263    /// # Safety
264    /// `ptr` must be valid for writing `LANE_COUNT` elements.
265    ///
266    /// Default: scalar-emulated merge via `kernel_helpers::generic_masked_store`.
267    /// Backends with a native masked store override this.
268    unsafe fn masked_store_unaligned(ptr: *mut T, mask: Self::Mask, val: Self::Vector) {
269        crate::kernel_helpers::generic_masked_store::<T, Self>(ptr, mask, val)
270    }
271
272    // -------------------------------------------------------------------------
273    // Masked Arithmetic (merge masking)
274    // -------------------------------------------------------------------------
275
276    /// Masked elementwise add: active lanes compute `a + b`, inactive lanes yield `src`.
277    ///
278    /// # Safety
279    /// Processor must support the required target feature.
280    ///
281    /// Default: `blend(mask_to_vector(mask), add(a, b), src)`. Backends with a
282    /// native masked add override this.
283    unsafe fn masked_add(
284        a: Self::Vector,
285        b: Self::Vector,
286        mask: Self::Mask,
287        src: Self::Vector,
288    ) -> Self::Vector {
289        Self::blend(Self::mask_to_vector(mask), Self::add(a, b), src)
290    }
291
292    /// Masked elementwise multiply: active lanes compute `a * b`, inactive lanes yield `src`.
293    ///
294    /// # Safety
295    /// Processor must support the required target feature.
296    ///
297    /// Default: `blend(mask_to_vector(mask), mul(a, b), src)`. Backends with a
298    /// native masked multiply override this.
299    unsafe fn masked_mul(
300        a: Self::Vector,
301        b: Self::Vector,
302        mask: Self::Mask,
303        src: Self::Vector,
304    ) -> Self::Vector {
305        Self::blend(Self::mask_to_vector(mask), Self::mul(a, b), src)
306    }
307
308    /// Masked fused multiply-add: active lanes compute `(a * b) + c`, inactive lanes retain `c`.
309    ///
310    /// The merge source for inactive lanes is the addend `c`, matching AVX-512 semantics
311    /// for `_mm512_mask_fmadd_ps(a, mask, b, c)`.
312    ///
313    /// # Safety
314    /// Processor must support the required target feature.
315    ///
316    /// Default: `blend(mask_to_vector(mask), fmadd(a, b, c), c)` — inactive lanes
317    /// retain the addend `c`. Backends with a native masked FMA override this.
318    unsafe fn masked_fmadd(
319        a: Self::Vector,
320        b: Self::Vector,
321        c: Self::Vector,
322        mask: Self::Mask,
323    ) -> Self::Vector {
324        Self::blend(Self::mask_to_vector(mask), Self::fmadd(a, b, c), c)
325    }
326
327    /// Masked horizontal sum: only lanes where `mask[i]=1` contribute.
328    ///
329    /// # Safety
330    /// Processor must support the required target feature.
331    ///
332    /// Default: `sum_reduce(blend(mask_to_vector(mask), v, zero))` — inactive
333    /// lanes contribute zero. Backends with a native masked reduction override this.
334    unsafe fn masked_sum_reduce(v: Self::Vector, mask: Self::Mask) -> T {
335        Self::sum_reduce(Self::blend(Self::mask_to_vector(mask), v, Self::zero()))
336    }
337
338    // -------------------------------------------------------------------------
339    // Compress / Expand
340    // -------------------------------------------------------------------------
341
342    /// Compress: pack selected lanes (where `mask[i]=1`) into the low lanes of the result.
343    ///
344    /// Unselected high lanes of the result are unspecified.
345    ///
346    /// # Safety
347    /// Processor must support the required target feature.
348    unsafe fn compress(src: Self::Vector, mask: Self::Mask) -> Self::Vector;
349
350    /// Expand: scatter the low lanes of `src` into result positions where `mask[i]=1`.
351    ///
352    /// Result positions where `mask[i]=0` are filled with `fill`.
353    ///
354    /// # Safety
355    /// Processor must support the required target feature.
356    unsafe fn expand(src: Self::Vector, mask: Self::Mask, fill: Self::Vector) -> Self::Vector;
357
358    // -------------------------------------------------------------------------
359    // Gather (indirect indexed load)
360    // -------------------------------------------------------------------------
361
362    /// Gather: load `LANE_COUNT` elements at `base + indices[i]` for each lane `i`.
363    ///
364    /// # Safety
365    /// All `base + indices[i]` must be valid for reads.
366    unsafe fn gather(base: *const T, indices: Self::IndexVector) -> Self::Vector;
367
368    /// Masked gather: gather active lanes; inactive lanes take value from `src`.
369    ///
370    /// # Safety
371    /// Active `base + indices[i]` must be valid for reads.
372    unsafe fn gather_masked(
373        base: *const T,
374        indices: Self::IndexVector,
375        mask: Self::Mask,
376        src: Self::Vector,
377    ) -> Self::Vector;
378
379    // -------------------------------------------------------------------------
380    // Mask Construction Helpers
381    // -------------------------------------------------------------------------
382
383    /// Construct a mask from a slice of booleans (length must equal `LANE_COUNT`).
384    ///
385    /// # Panics
386    /// Panics in debug builds if `bits.len() != LANE_COUNT`.
387    ///
388    /// # Safety
389    /// Processor must support the required target feature.
390    unsafe fn mask_from_bools(bits: &[bool]) -> Self::Mask;
391
392    /// Construct a mask with the first `k` lanes active and the rest inactive.
393    ///
394    /// If `k >= LANE_COUNT`, all lanes are active. Used for tail handling.
395    ///
396    /// # Safety
397    /// Processor must support the required target feature.
398    unsafe fn leading_k_mask(k: usize) -> Self::Mask;
399
400    /// Convert a raw `u64` bitmask to the architecture-native mask type.
401    ///
402    /// Default: expands to a boolean array then calls `mask_from_bools`.
403    ///
404    /// # Safety
405    /// Processor must support the required target feature.
406    unsafe fn mask_from_bitmask(bm: u64) -> Self::Mask {
407        crate::kernel_helpers::generic_mask_from_bitmask::<T, Self>(bm)
408    }
409
410    /// Convert the native mask back to a vector register where active lanes
411    /// are set to `T::ALL_ONES` and inactive lanes to `T::ZERO`.
412    ///
413    /// # Safety
414    /// Processor must support the required target feature.
415    unsafe fn mask_to_vector(mask: Self::Mask) -> Self::Vector;
416
417    /// Convert a comparison-result vector into the native mask, the inverse of
418    /// [`SimdKernel::mask_to_vector`].
419    ///
420    /// A lane is active iff its sign bit is set, matching hardware movemask
421    /// semantics (`_mm256_movemask_ps` and friends). The `cmp_*` family returns
422    /// `Self::Vector` with active lanes set to `T::ALL_ONES` — whose sign bit is
423    /// set — so composing this with [`SimdKernel::mask_to_bitmask`] yields one
424    /// bit per comparison outcome, and `trailing_zeros` then locates the first
425    /// matching lane without leaving vector registers.
426    ///
427    /// # Safety
428    /// Processor must support the required target feature.
429    unsafe fn vector_to_mask(v: Self::Vector) -> Self::Mask;
430
431    /// Perform an intra-vector prefix scan (inclusive or exclusive) of the vector,
432    /// using the specified `ScanOp` strategy and starting carry value.
433    /// Returns the scanned vector and the final carry value.
434    ///
435    /// # Safety
436    /// Processor must support the required target feature.
437    #[inline(always)]
438    unsafe fn scan_vector<Op: crate::ops::ScanOp<T>, SMode: crate::ops::ScanMode>(
439        v: Self::Vector,
440        mut carry: T,
441    ) -> (Self::Vector, T) {
442        const { Self::LANE_BOUND_CHECK };
443        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
444        let lanes = Self::LANE_COUNT;
445        Self::store_unaligned(buf.as_mut_ptr() as *mut T, v);
446
447        let mut out_buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
448        if SMode::IS_INCLUSIVE {
449            for j in 0..lanes {
450                let temp = buf[j].assume_init();
451                carry = Op::combine(carry, temp);
452                out_buf[j].write(carry);
453            }
454        } else {
455            for j in 0..lanes {
456                let temp = buf[j].assume_init();
457                out_buf[j].write(carry);
458                carry = Op::combine(carry, temp);
459            }
460        }
461
462        (Self::load_unaligned(out_buf.as_ptr() as *const T), carry)
463    }
464
465    /// Set all lanes to zero.
466    ///
467    /// Default: delegates to `splat(T::ZERO)`. Backends may override with an
468    /// architecture-specific XOR-zero idiom (e.g., `_mm256_xor_ps`) if profiling
469    /// shows a register-pressure benefit.
470    ///
471    /// # Safety
472    /// Processor must support the required target feature.
473    unsafe fn zero() -> Self::Vector {
474        Self::splat(T::ZERO)
475    }
476
477    /// Broadcast a scalar value to all lanes.
478    ///
479    /// # Safety
480    /// Processor must support the required target feature.
481    ///
482    /// # Examples
483    ///
484    /// ```rust
485    /// use hermes_simd_intrinsics::Scalar;
486    /// use hermes_simd_core::kernel::SimdKernel;
487    ///
488    /// // SAFETY: Scalar backend requires no ISA feature.
489    /// let v = unsafe { <Scalar as SimdKernel<f32>>::splat(42.0_f32) };
490    /// let sum: f32 = unsafe { <Scalar as SimdKernel<f32>>::sum_reduce(v) };
491    /// assert_eq!(sum, 42.0_f32 * <Scalar as SimdKernel<f32>>::LANE_COUNT as f32);
492    /// ```
493    unsafe fn splat(val: T) -> Self::Vector;
494
495    /// Elementwise division: `a / b`.
496    ///
497    /// # Safety
498    /// Processor must support the required target feature.
499    unsafe fn div(a: Self::Vector, b: Self::Vector) -> Self::Vector {
500        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| x / y)
501    }
502
503    /// Elementwise bitwise AND: `a & b`.
504    ///
505    /// # Safety
506    /// Processor must support the required target feature.
507    unsafe fn bitand(a: Self::Vector, b: Self::Vector) -> Self::Vector {
508        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| x.bitand(y))
509    }
510
511    /// Elementwise bitwise OR: `a | b`.
512    ///
513    /// # Safety
514    /// Processor must support the required target feature.
515    unsafe fn bitor(a: Self::Vector, b: Self::Vector) -> Self::Vector {
516        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| x.bitor(y))
517    }
518
519    /// Elementwise bitwise XOR: `a ^ b`.
520    ///
521    /// # Safety
522    /// Processor must support the required target feature.
523    unsafe fn bitxor(a: Self::Vector, b: Self::Vector) -> Self::Vector {
524        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| x.bitxor(y))
525    }
526
527    /// Elementwise absolute value.
528    ///
529    /// # Safety
530    /// Processor must support the required target feature.
531    unsafe fn abs(a: Self::Vector) -> Self::Vector {
532        crate::kernel_helpers::generic_unary_op::<T, Self, _>(a, |x| x.abs())
533    }
534
535    /// Elementwise minimum of `a` and `b`.
536    ///
537    /// # Safety
538    /// Processor must support the required target feature.
539    unsafe fn min(a: Self::Vector, b: Self::Vector) -> Self::Vector {
540        crate::kernel_helpers::generic_binary_op::<T, Self, _>(
541            a,
542            b,
543            |x, y| if x < y { x } else { y },
544        )
545    }
546
547    /// Elementwise maximum of `a` and `b`.
548    ///
549    /// # Safety
550    /// Processor must support the required target feature.
551    unsafe fn max(a: Self::Vector, b: Self::Vector) -> Self::Vector {
552        crate::kernel_helpers::generic_binary_op::<T, Self, _>(
553            a,
554            b,
555            |x, y| if x > y { x } else { y },
556        )
557    }
558
559    /// Elementwise square root.
560    ///
561    /// # Safety
562    /// Processor must support the required target feature.
563    unsafe fn sqrt(a: Self::Vector) -> Self::Vector {
564        crate::kernel_helpers::generic_unary_op::<T, Self, _>(a, |x| x.sqrt())
565    }
566
567    /// Elementwise reciprocal square root, `1/√x`, to full `T` precision (~1 ulp).
568    ///
569    /// Native backends override this where a faster full-precision path exists: f32
570    /// uses a hardware `rsqrt` seed plus one Newton–Raphson step (which already
571    /// reaches f32's 23-bit mantissa); f64 has no `rsqrt` approximation accurate
572    /// enough for its 52-bit mantissa, so it uses the correctly-rounded hardware
573    /// `sqrt` + divide. The result is therefore precision-consistent across every
574    /// backend — not a reduced-accuracy fast approximation.
575    ///
576    /// # Safety
577    /// Processor must support the required target feature.
578    unsafe fn recip_sqrt(a: Self::Vector) -> Self::Vector {
579        crate::kernel_helpers::generic_unary_op::<T, Self, _>(a, |x| T::ONE / x.sqrt())
580    }
581
582    /// Elementwise equal: `a == b`.
583    ///
584    /// # Safety
585    /// Processor must support the required target feature.
586    unsafe fn cmp_eq(a: Self::Vector, b: Self::Vector) -> Self::Vector {
587        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| {
588            if x == y {
589                T::ALL_ONES
590            } else {
591                T::ZERO
592            }
593        })
594    }
595
596    /// Elementwise not equal: `a != b`.
597    ///
598    /// # Safety
599    /// Processor must support the required target feature.
600    unsafe fn cmp_ne(a: Self::Vector, b: Self::Vector) -> Self::Vector {
601        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| {
602            if x != y {
603                T::ALL_ONES
604            } else {
605                T::ZERO
606            }
607        })
608    }
609
610    /// Elementwise less than: `a < b`.
611    ///
612    /// # Safety
613    /// Processor must support the required target feature.
614    unsafe fn cmp_lt(a: Self::Vector, b: Self::Vector) -> Self::Vector {
615        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| {
616            if x < y {
617                T::ALL_ONES
618            } else {
619                T::ZERO
620            }
621        })
622    }
623
624    /// Elementwise less than or equal: `a <= b`.
625    ///
626    /// # Safety
627    /// Processor must support the required target feature.
628    unsafe fn cmp_le(a: Self::Vector, b: Self::Vector) -> Self::Vector {
629        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| {
630            if x <= y {
631                T::ALL_ONES
632            } else {
633                T::ZERO
634            }
635        })
636    }
637
638    /// Elementwise greater than: `a > b`.
639    ///
640    /// # Safety
641    /// Processor must support the required target feature.
642    unsafe fn cmp_gt(a: Self::Vector, b: Self::Vector) -> Self::Vector {
643        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| {
644            if x > y {
645                T::ALL_ONES
646            } else {
647                T::ZERO
648            }
649        })
650    }
651
652    /// Elementwise greater than or equal: `a >= b`.
653    ///
654    /// # Safety
655    /// Processor must support the required target feature.
656    unsafe fn cmp_ge(a: Self::Vector, b: Self::Vector) -> Self::Vector {
657        crate::kernel_helpers::generic_binary_op::<T, Self, _>(a, b, |x, y| {
658            if x >= y {
659                T::ALL_ONES
660            } else {
661                T::ZERO
662            }
663        })
664    }
665
666    /// Elementwise blend: select lanes from `true_val` where the sign bit of `mask` is set,
667    /// and from `false_val` otherwise.
668    ///
669    /// # Safety
670    /// Processor must support the required target feature.
671    unsafe fn blend(
672        mask: Self::Vector,
673        true_val: Self::Vector,
674        false_val: Self::Vector,
675    ) -> Self::Vector {
676        crate::kernel_helpers::generic_blend::<T, Self>(mask, true_val, false_val)
677    }
678
679    /// Elementwise negate: `-a`.
680    ///
681    /// Default implementation: XOR each lane with `T::SIGN_MASK` (the IEEE 754 sign bit).
682    /// This avoids the `sub(zero, a)` path, which panics on backends that do not implement
683    /// subtraction (e.g. `bf16` on AVX2). Every backend implements `bitxor` and `splat`.
684    ///
685    /// # Safety
686    /// Processor must support the required target feature.
687    #[inline(always)]
688    unsafe fn neg(a: Self::Vector) -> Self::Vector {
689        Self::bitxor(a, Self::splat(T::SIGN_MASK))
690    }
691
692    /// Elementwise bitwise NOT: `!a`.
693    ///
694    /// # Safety
695    /// Processor must support the required target feature.
696    #[inline(always)]
697    unsafe fn bitnot(a: Self::Vector) -> Self::Vector {
698        Self::bitxor(a, Self::splat(T::ALL_ONES))
699    }
700
701    /// Convert the native mask back to a raw `u64` bitmask.
702    ///
703    /// # Safety
704    /// Processor must support the required target feature.
705    unsafe fn mask_to_bitmask(mask: Self::Mask) -> u64;
706
707    /// Horizontal minimum across all lanes.
708    ///
709    /// Default: scalar lane-by-lane scan using [`crate::scalar::NumericElement::min_scalar`].
710    /// AVX-512 impls override with `_mm512_reduce_min_ps` / `_mm256_reduce_min_ps` or equivalent.
711    ///
712    /// # Safety
713    /// Processor must support the required target feature.
714    unsafe fn min_reduce(v: Self::Vector) -> T {
715        crate::kernel_helpers::generic_horizontal_reduce::<T, Self>(v, T::MAX_VALUE, |a, b| {
716            a.min_scalar(b)
717        })
718    }
719
720    /// Horizontal maximum across all lanes.
721    ///
722    /// Default: scalar lane-by-lane scan using [`crate::scalar::NumericElement::max_scalar`].
723    /// AVX-512 impls override with `_mm512_reduce_max_ps` / `_mm256_reduce_max_ps` or equivalent.
724    ///
725    /// # Safety
726    /// Processor must support the required target feature.
727    unsafe fn max_reduce(v: Self::Vector) -> T {
728        crate::kernel_helpers::generic_horizontal_reduce::<T, Self>(v, T::MIN_VALUE, |a, b| {
729            a.max_scalar(b)
730        })
731    }
732
733    /// Elementwise population count (number of set bits).
734    ///
735    /// Default: scalar lane-by-lane scan using [`crate::scalar::NumericElement::count_ones`].
736    /// Target-specific intrinsics override this.
737    ///
738    /// # Safety
739    /// Processor must support the required target feature.
740    unsafe fn popcount(a: Self::Vector) -> Self::Vector {
741        crate::kernel_helpers::generic_unary_op::<T, Self, _>(a, |x| {
742            T::cast_from(x.count_ones() as i32)
743        })
744    }
745
746    /// Horizontal bitwise AND across all lanes.
747    ///
748    /// Default: scalar lane-by-lane scan using [`crate::scalar::NumericElement::bitand`].
749    /// Target-specific intrinsics override this.
750    ///
751    /// # Safety
752    /// Processor must support the required target feature.
753    unsafe fn horizontal_bitwise_and(v: Self::Vector) -> T {
754        crate::kernel_helpers::generic_horizontal_reduce::<T, Self>(v, T::ALL_ONES, |a, b| {
755            a.bitand(b)
756        })
757    }
758
759    /// Horizontal bitwise OR across all lanes.
760    ///
761    /// Default: scalar lane-by-lane scan using [`crate::scalar::NumericElement::bitor`].
762    /// Target-specific intrinsics override this.
763    ///
764    /// # Safety
765    /// Processor must support the required target feature.
766    unsafe fn horizontal_bitwise_or(v: Self::Vector) -> T {
767        crate::kernel_helpers::generic_horizontal_reduce::<T, Self>(v, T::ZERO, |a, b| a.bitor(b))
768    }
769
770    /// Horizontal bitwise XOR across all lanes.
771    ///
772    /// Default: scalar lane-by-lane scan using [`crate::scalar::NumericElement::bitxor`].
773    /// Target-specific intrinsics override this.
774    ///
775    /// # Safety
776    /// Processor must support the required target feature.
777    unsafe fn horizontal_bitwise_xor(v: Self::Vector) -> T {
778        crate::kernel_helpers::generic_horizontal_reduce::<T, Self>(v, T::ZERO, |a, b| a.bitxor(b))
779    }
780
781    // -------------------------------------------------------------------------
782    // Adjacent-Pair Shuffles & Alternating FMA (interleaved complex support)
783    // -------------------------------------------------------------------------
784    //
785    // These five methods are the minimal primitive set required to express
786    // interleaved complex arithmetic (`[re, im, re, im, ...]` lane order)
787    // entirely in vector registers:
788    //
789    //   a * b       = fmaddsub(dup_even(a), b, mul(dup_odd(a), swap_adjacent(b)))
790    //   a * conj(b) = fmsubadd(dup_odd(a), swap_adjacent(b), mul(dup_even(a), b))
791    //
792    // Pair semantics assume an even `LANE_COUNT`; on a backend with an odd
793    // lane count the last (unpaired) lane passes through unchanged.
794
795    /// Swap each adjacent lane pair: `[a0, a1, a2, a3, ...] -> [a1, a0, a3, a2, ...]`.
796    ///
797    /// Default: scalar emulation via store/swap/load. x86 backends override with
798    /// `_mm256_permute_ps(v, 0b1011_0001)` / `_mm256_permute_pd(v, 0b0101)` and
799    /// the AVX-512 equivalents.
800    ///
801    /// # Safety
802    /// Processor must support the required target feature.
803    #[inline(always)]
804    unsafe fn swap_adjacent(v: Self::Vector) -> Self::Vector {
805        const { Self::LANE_BOUND_CHECK };
806        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
807        let lanes = Self::LANE_COUNT;
808        Self::store_unaligned(buf.as_mut_ptr() as *mut T, v);
809        let mut i = 0usize;
810        while i + 1 < lanes {
811            buf.swap(i, i + 1);
812            i += 2;
813        }
814        Self::load_unaligned(buf.as_ptr() as *const T)
815    }
816
817    /// Duplicate even lanes into odd lanes: `[a0, a1, a2, a3, ...] -> [a0, a0, a2, a2, ...]`.
818    ///
819    /// Default: scalar emulation. x86 backends override with `moveldup_ps` /
820    /// `movedup_pd`.
821    ///
822    /// # Safety
823    /// Processor must support the required target feature.
824    #[inline(always)]
825    unsafe fn dup_even(v: Self::Vector) -> Self::Vector {
826        const { Self::LANE_BOUND_CHECK };
827        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
828        let lanes = Self::LANE_COUNT;
829        Self::store_unaligned(buf.as_mut_ptr() as *mut T, v);
830        let mut out = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
831        for i in 0..lanes {
832            let src_val = buf[i & !1].assume_init();
833            out[i].write(src_val);
834        }
835        Self::load_unaligned(out.as_ptr() as *const T)
836    }
837
838    /// Duplicate odd lanes into even lanes: `[a0, a1, a2, a3, ...] -> [a1, a1, a3, a3, ...]`.
839    ///
840    /// Default: scalar emulation. x86 backends override with `movehdup_ps` /
841    /// an odd-lane `permute_pd`. An unpaired trailing lane (odd `LANE_COUNT`)
842    /// passes through unchanged.
843    ///
844    /// # Safety
845    /// Processor must support the required target feature.
846    #[inline(always)]
847    unsafe fn dup_odd(v: Self::Vector) -> Self::Vector {
848        const { Self::LANE_BOUND_CHECK };
849        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
850        let lanes = Self::LANE_COUNT;
851        Self::store_unaligned(buf.as_mut_ptr() as *mut T, v);
852        let mut out = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
853        for i in 0..lanes {
854            let src_val = buf[(i | 1).min(lanes - 1)].assume_init();
855            out[i].write(src_val);
856        }
857        Self::load_unaligned(out.as_ptr() as *const T)
858    }
859
860    /// Alternating fused multiply: even lanes `a*b - c`, odd lanes `a*b + c`.
861    ///
862    /// Default: scalar emulation. x86 backends override with
863    /// `_mm256_fmaddsub_ps/pd` / `_mm512_fmaddsub_ps/pd`.
864    ///
865    /// # Safety
866    /// Processor must support the required target feature.
867    #[inline(always)]
868    unsafe fn fmaddsub(a: Self::Vector, b: Self::Vector, c: Self::Vector) -> Self::Vector {
869        crate::kernel_helpers::generic_alternating_fma::<T, Self, false>(a, b, c)
870    }
871
872    /// Alternating fused multiply: even lanes `a*b + c`, odd lanes `a*b - c`.
873    ///
874    /// Default: scalar emulation. x86 backends override with
875    /// `_mm256_fmsubadd_ps/pd` / `_mm512_fmsubadd_ps/pd`.
876    ///
877    /// # Safety
878    /// Processor must support the required target feature.
879    #[inline(always)]
880    unsafe fn fmsubadd(a: Self::Vector, b: Self::Vector, c: Self::Vector) -> Self::Vector {
881        crate::kernel_helpers::generic_alternating_fma::<T, Self, true>(a, b, c)
882    }
883}