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}