Skip to main content

hermes_simd_core/view/
reduce.rs

1//! Horizontal and pairwise SIMD reductions over [`SimdView`](crate::view::SimdView).
2//!
3//! # Safety
4//!
5//! Every kernel call below is `#[target_feature]`-gated and is therefore sound
6//! only on a host implementing `Arch`. That holds by construction rather than by
7//! inspection: [`SimdView::new`](crate::view::SimdView::new) returns `None` for
8//! an architecture the host cannot execute, and the sparse and copy-on-write
9//! constructors assert the same condition, so possessing one of these
10//! arch-parameterized values *is* the proof. Per-site `SAFETY` comments record
11//! only the obligations that go beyond it — pointer provenance, bounds, and
12//! alignment.
13
14use crate::align::Alignment;
15use crate::arch::SimdArch;
16use crate::execution::ExecutionMode;
17use crate::kernel::SimdKernel;
18use crate::ops::ReductionOp;
19use crate::scalar::Scalar;
20use crate::view::{SimdError, SimdView};
21
22/// Periodic accumulator-flush interval for popcount-style horizontal reductions,
23/// sized by element width to bound intermediate-sum precision loss. 2-byte types
24/// (`f16`/`bf16`/`i16`) have a small exact-integer range (256/2048), so partials
25/// are flushed every 128 chunks; wider types tolerate 32768 chunks per flush.
26#[inline(always)]
27const fn flush_limit_for<T>() -> usize {
28    if core::mem::size_of::<T>() == 2 {
29        128
30    } else {
31        32768
32    }
33}
34
35impl<'a, T: 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode, Ref: 'a>
36    SimdView<'a, T, Arch, Align, Mode, Ref>
37where
38    T: Scalar,
39{
40    /// Generic SIMD horizontal reduction using a `ReductionOp<T>` strategy ZST.
41    ///
42    /// Processes `UNROLL_FACTOR × LANE_COUNT` elements per iteration using
43    /// `UNROLL_FACTOR` independent accumulators to saturate FMA throughput.
44    ///
45    /// The vector accumulator is initialized to `Op::identity_vector()` — the
46    /// identity element for this reduction (e.g. `+∞` for `Min`, `-∞` for `Max`,
47    /// `0` for `Sum`). This is required for correctness: starting from `Arch::zero()`
48    /// would produce wrong results for `Min`/`Max` on non-negative inputs.
49    ///
50    /// Zero-cost: `_op` is a ZST erased entirely by the compiler.
51    #[inline]
52    pub fn reduce<Op: ReductionOp<T>>(&self, _op: Op) -> T {
53        let data = self.as_slice();
54        let len = data.len();
55        if len == 0 {
56            return Op::identity_scalar();
57        }
58
59        let lane_count = Arch::LANE_COUNT;
60        let unroll_factor = Arch::UNROLL_FACTOR;
61        let chunk_size = lane_count * unroll_factor;
62        let unrolled_len = (len / chunk_size) * chunk_size;
63
64        // SAFETY: `Arch::load_*` is a target-feature kernel (module invariant).
65        // Every caller only ever passes a pointer whose `LANE_COUNT`-element read
66        // stays within `data` (offsets are bounded by `simd_len`/`unrolled_len`),
67        // and the aligned variant is selected only when `Align` proves the base
68        // is arch-aligned.
69        let load = |p: *const T| -> Arch::Vector {
70            if crate::align::is_aligned_for_arch::<Arch, Align>() {
71                unsafe { Arch::load_aligned(p) }
72            } else {
73                unsafe { Arch::load_unaligned(p) }
74            }
75        };
76
77        // SAFETY: the `Op::*` and `identity_vector` calls are target-feature
78        // kernels covered by the module invariant. `unrolled_len` is a multiple
79        // of `chunk_size = LANE_COUNT * UNROLL_FACTOR`, so each `ptr.add(k)` in
80        // the seeds/loop addresses a `LANE_COUNT` window fully within `data`
81        // (`ptr` advances by `chunk_size` per iteration while `i < unrolled_len`).
82        // Initialize with the identity vector so Min/Max start from the correct bound.
83        let mut acc = unsafe { Op::identity_vector::<Arch>() };
84        let mut i = 0usize;
85
86        if unrolled_len >= chunk_size {
87            // Seeds carry the per-element transform (identity for Sum/Min/Max,
88            // abs for AbsSum/AbsMax) — a raw-load seed would skip it for the
89            // first chunk. Cross-accumulator merges use combine_vectors, which
90            // never re-applies the transform to already-transformed partials.
91            let base = data.as_ptr();
92            acc = unsafe {
93                let mut acc0 = Op::transform_vector::<Arch>(load(base));
94                let mut acc1 = Op::transform_vector::<Arch>(load(base.add(lane_count)));
95                let mut acc2 = Op::transform_vector::<Arch>(load(base.add(lane_count * 2)));
96                let mut acc3 = Op::transform_vector::<Arch>(load(base.add(lane_count * 3)));
97                let mut ptr = base.add(chunk_size);
98                i = chunk_size;
99
100                while i < unrolled_len {
101                    acc0 = Op::accumulate::<Arch>(acc0, load(ptr));
102                    acc1 = Op::accumulate::<Arch>(acc1, load(ptr.add(lane_count)));
103                    acc2 = Op::accumulate::<Arch>(acc2, load(ptr.add(lane_count * 2)));
104                    acc3 = Op::accumulate::<Arch>(acc3, load(ptr.add(lane_count * 3)));
105                    ptr = ptr.add(chunk_size);
106                    i += chunk_size;
107                }
108
109                acc0 = Op::combine_vectors::<Arch>(acc0, acc1);
110                acc2 = Op::combine_vectors::<Arch>(acc2, acc3);
111                Op::combine_vectors::<Arch>(acc0, acc2)
112            };
113        }
114
115        // Remaining full SIMD vectors.
116        // SAFETY: `i < simd_len` and `simd_len = (len / LANE_COUNT) * LANE_COUNT`,
117        // so `ptr.add(i)` addresses a `LANE_COUNT` window within `data`;
118        // `Op::accumulate`/`finalize` are target-feature kernels (module invariant).
119        let simd_len = (len / lane_count) * lane_count;
120        let ptr = data.as_ptr();
121        let mut total = unsafe {
122            while i < simd_len {
123                acc = Op::accumulate::<Arch>(acc, load(ptr.add(i)));
124                i += lane_count;
125            }
126            Op::finalize::<Arch>(acc)
127        };
128
129        // Scalar tail — use Op::scalar_accumulate so per-element transforms (e.g. SquaredSum)
130        // apply correctly. For Sum/Min/Max the default delegates to scalar_combine.
131        while i < len {
132            total = Op::scalar_accumulate(total, data[i]);
133            i += 1;
134        }
135
136        total
137    }
138
139    /// Generic pairwise SIMD reduction: `reduce(Op, a ⊗ b)`.
140    ///
141    /// Computes `a[i] * b[i]` lane-wise, then applies `Op::accumulate` and `Op::finalize`.
142    /// For `Op=Dot` this is the standard dot product.
143    ///
144    /// # Errors
145    /// Returns [`SimdError::LengthMismatch`] if slice lengths differ.
146    #[inline]
147    pub fn zip_reduce<Op: ReductionOp<T>, ORef>(
148        &self,
149        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
150        _op: Op,
151    ) -> Result<T, SimdError>
152    where
153        ORef: 'a,
154    {
155        super::check_lengths_equal(self.len(), other.len())?;
156        let len = self.len();
157        let lane_count = Arch::LANE_COUNT;
158        let unroll_factor = Arch::UNROLL_FACTOR;
159        let chunk_size = lane_count * unroll_factor;
160        let unrolled_len = (len / chunk_size) * chunk_size;
161
162        // SAFETY: identical contract to `reduce`'s `load` — target-feature kernel
163        // (module invariant), and every call passes a pointer whose `LANE_COUNT`
164        // read stays within its slice (offsets bounded by `simd_len`).
165        let load = |p: *const T| -> Arch::Vector {
166            if crate::align::is_aligned_for_arch::<Arch, Align>() {
167                unsafe { Arch::load_aligned(p) }
168            } else {
169                unsafe { Arch::load_unaligned(p) }
170            }
171        };
172
173        let s = self.as_slice();
174        let o = other.as_slice();
175        // SAFETY: target-feature kernels (module invariant). `s` and `o` are
176        // equal length (checked above), and `unrolled_len` is a multiple of
177        // `chunk_size`, so every `pa.add(k)`/`pb.add(k)` addresses a `LANE_COUNT`
178        // window within its slice while `i < unrolled_len`.
179        let mut acc = unsafe { Op::identity_vector::<Arch>() };
180        let mut i = 0usize;
181
182        if unrolled_len >= chunk_size {
183            // Seed the four accumulators with the first pairwise products.
184            // (First chunk cannot use FMA into zero, so we use separate mul.)
185            acc = unsafe {
186                let pair =
187                    |pa: *const T, pb: *const T| -> Arch::Vector { Arch::mul(load(pa), load(pb)) };
188                let base_a = s.as_ptr();
189                let base_b = o.as_ptr();
190
191                let mut acc0 = pair(base_a, base_b);
192                let mut acc1 = pair(base_a.add(lane_count), base_b.add(lane_count));
193                let mut acc2 = pair(base_a.add(lane_count * 2), base_b.add(lane_count * 2));
194                let mut acc3 = pair(base_a.add(lane_count * 3), base_b.add(lane_count * 3));
195                let mut pa = base_a.add(chunk_size);
196                let mut pb = base_b.add(chunk_size);
197                i = chunk_size;
198
199                // Main unrolled loop — `fma_pair_accumulate` lets `Dot` emit a
200                // single `vfmadd` instead of a separate `mul` + `add`.
201                while i < unrolled_len {
202                    acc0 = Op::fma_pair_accumulate::<Arch>(acc0, load(pa), load(pb));
203                    acc1 = Op::fma_pair_accumulate::<Arch>(
204                        acc1,
205                        load(pa.add(lane_count)),
206                        load(pb.add(lane_count)),
207                    );
208                    acc2 = Op::fma_pair_accumulate::<Arch>(
209                        acc2,
210                        load(pa.add(lane_count * 2)),
211                        load(pb.add(lane_count * 2)),
212                    );
213                    acc3 = Op::fma_pair_accumulate::<Arch>(
214                        acc3,
215                        load(pa.add(lane_count * 3)),
216                        load(pb.add(lane_count * 3)),
217                    );
218                    pa = pa.add(chunk_size);
219                    pb = pb.add(chunk_size);
220                    i += chunk_size;
221                }
222
223                acc0 = Op::accumulate::<Arch>(acc0, acc1);
224                acc2 = Op::accumulate::<Arch>(acc2, acc3);
225                Op::accumulate::<Arch>(acc0, acc2)
226            };
227        }
228
229        // Remaining full SIMD vectors — use `fma_pair_accumulate` here too.
230        // SAFETY: `i < simd_len` bounds each `pa.add(i)`/`pb.add(i)` to a
231        // `LANE_COUNT` window within the equal-length slices; kernels covered by
232        // the module invariant.
233        let simd_len = (len / lane_count) * lane_count;
234        let pa = s.as_ptr();
235        let pb = o.as_ptr();
236        let mut total = unsafe {
237            while i < simd_len {
238                acc = Op::fma_pair_accumulate::<Arch>(acc, load(pa.add(i)), load(pb.add(i)));
239                i += lane_count;
240            }
241            Op::finalize::<Arch>(acc)
242        };
243
244        // Scalar tail — use scalar_combine for correctness with Min/Max.
245        while i < len {
246            total = Op::scalar_combine(total, s[i] * o[i]);
247            i += 1;
248        }
249
250        Ok(total)
251    }
252
253    /// Computes the horizontal sum of population counts of all elements.
254    #[inline]
255    pub fn reduce_popcount(&self) -> usize {
256        let data = self.as_slice();
257        let len = data.len();
258        let lane_count = Arch::LANE_COUNT;
259        let unroll_factor = Arch::UNROLL_FACTOR;
260        let chunk_size = lane_count * unroll_factor;
261        let unrolled_simd_len = (len / chunk_size) * chunk_size;
262        let simd_len = (len / lane_count) * lane_count;
263        let mut total: usize = 0;
264        let mut i = 0usize;
265
266        // SAFETY: `Arch::load_*` is a target-feature kernel (module invariant),
267        // and every call site passes a pointer whose `LANE_COUNT` read stays
268        // within the source slice (offsets bounded by `simd_len`).
269        let load = |p: *const T| -> Arch::Vector {
270            if crate::align::is_aligned_for_arch::<Arch, Align>() {
271                unsafe { Arch::load_aligned(p) }
272            } else {
273                unsafe { Arch::load_unaligned(p) }
274            }
275        };
276
277        let flush_limit = flush_limit_for::<T>();
278
279        // Unrolled loop (4-way register accumulation)
280        if unrolled_simd_len > 0 {
281            let mut acc0 = unsafe { Arch::zero() };
282            let mut acc1 = unsafe { Arch::zero() };
283            let mut acc2 = unsafe { Arch::zero() };
284            let mut acc3 = unsafe { Arch::zero() };
285            let mut count = 0;
286
287            while i < unrolled_simd_len {
288                // SAFETY: `unrolled_simd_len` is a multiple of `chunk_size`, so
289                // `i + lane_count*3 + LANE_COUNT <= unrolled_simd_len <= len`; each
290                // load reads a `LANE_COUNT` window within `data`. Kernels covered
291                // by the module invariant.
292                unsafe {
293                    let v0 = load(data.as_ptr().add(i));
294                    let v1 = load(data.as_ptr().add(i + lane_count));
295                    let v2 = load(data.as_ptr().add(i + lane_count * 2));
296                    let v3 = load(data.as_ptr().add(i + lane_count * 3));
297
298                    acc0 = Arch::add(acc0, Arch::popcount(v0));
299                    acc1 = Arch::add(acc1, Arch::popcount(v1));
300                    acc2 = Arch::add(acc2, Arch::popcount(v2));
301                    acc3 = Arch::add(acc3, Arch::popcount(v3));
302                }
303                i += chunk_size;
304                count += 1;
305
306                if count == flush_limit {
307                    unsafe {
308                        let mut acc = Arch::add(acc0, acc1);
309                        acc = Arch::add(acc, acc2);
310                        acc = Arch::add(acc, acc3);
311                        total += Arch::sum_reduce(acc).to_f64() as usize;
312                        acc0 = Arch::zero();
313                        acc1 = Arch::zero();
314                        acc2 = Arch::zero();
315                        acc3 = Arch::zero();
316                    }
317                    count = 0;
318                }
319            }
320
321            unsafe {
322                let mut acc = Arch::add(acc0, acc1);
323                acc = Arch::add(acc, acc2);
324                acc = Arch::add(acc, acc3);
325                total += Arch::sum_reduce(acc).to_f64() as usize;
326            }
327        }
328
329        // Middle loop (single register accumulation)
330        if i < simd_len {
331            let mut acc = unsafe { Arch::zero() };
332            while i < simd_len {
333                // SAFETY: `i < simd_len = (len / LANE_COUNT) * LANE_COUNT`, so the
334                // load reads a `LANE_COUNT` window within `data`.
335                unsafe {
336                    let v = load(data.as_ptr().add(i));
337                    acc = Arch::add(acc, Arch::popcount(v));
338                }
339                i += lane_count;
340            }
341            total += unsafe { Arch::sum_reduce(acc) }.to_f64() as usize;
342        }
343
344        // Scalar tail loop
345        while i < len {
346            total += data[i].count_ones() as usize;
347            i += 1;
348        }
349
350        total
351    }
352
353    /// Horizontal sum of population counts of `op(self[i], other[i])` for a
354    /// bitwise [`ElementOp`] (`BitAnd`/`BitOr`/`BitXor`).
355    ///
356    /// One generic 4-accumulator popcount reduction shared by
357    /// [`reduce_popcount_and`](Self::reduce_popcount_and),
358    /// [`reduce_popcount_or`](Self::reduce_popcount_or) and
359    /// [`reduce_popcount_xor`](Self::reduce_popcount_xor). The combining op is a
360    /// ZST monomorphized away, so each wrapper compiles to exactly the code its
361    /// former hand-written body did — the three ~100-line bodies collapse to one.
362    ///
363    /// # Errors
364    /// Returns [`SimdError::LengthMismatch`] if slice lengths differ.
365    #[inline]
366    fn reduce_popcount_op<ORef, Op>(
367        &self,
368        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
369        op: Op,
370    ) -> Result<usize, SimdError>
371    where
372        ORef: 'a,
373        Op: crate::ops::ElementOp<T>,
374    {
375        super::check_lengths_equal(self.len(), other.len())?;
376        let s = self.as_slice();
377        let o = other.as_slice();
378        let len = s.len();
379        let lane_count = Arch::LANE_COUNT;
380        let unroll_factor = Arch::UNROLL_FACTOR;
381        let chunk_size = lane_count * unroll_factor;
382        let unrolled_simd_len = (len / chunk_size) * chunk_size;
383        let simd_len = (len / lane_count) * lane_count;
384        let mut total: usize = 0;
385        let mut i = 0usize;
386
387        // SAFETY: `Arch::load_*` is a target-feature kernel (module invariant),
388        // and every call site passes a pointer whose `LANE_COUNT` read stays
389        // within the source slice (offsets bounded by `simd_len`).
390        let load = |p: *const T| -> Arch::Vector {
391            if crate::align::is_aligned_for_arch::<Arch, Align>() {
392                unsafe { Arch::load_aligned(p) }
393            } else {
394                unsafe { Arch::load_unaligned(p) }
395            }
396        };
397
398        let flush_limit = flush_limit_for::<T>();
399
400        if unrolled_simd_len > 0 {
401            let mut acc0 = unsafe { Arch::zero() };
402            let mut acc1 = unsafe { Arch::zero() };
403            let mut acc2 = unsafe { Arch::zero() };
404            let mut acc3 = unsafe { Arch::zero() };
405            let mut count = 0;
406
407            while i < unrolled_simd_len {
408                // SAFETY: `unrolled_simd_len` is a multiple of `chunk_size` and
409                // `s`/`o` are equal length, so `i + lane_count*3 + LANE_COUNT`
410                // stays within both slices. Kernels covered by the module invariant.
411                unsafe {
412                    let va0 = load(s.as_ptr().add(i));
413                    let vb0 = load(o.as_ptr().add(i));
414                    let va1 = load(s.as_ptr().add(i + lane_count));
415                    let vb1 = load(o.as_ptr().add(i + lane_count));
416                    let va2 = load(s.as_ptr().add(i + lane_count * 2));
417                    let vb2 = load(o.as_ptr().add(i + lane_count * 2));
418                    let va3 = load(s.as_ptr().add(i + lane_count * 3));
419                    let vb3 = load(o.as_ptr().add(i + lane_count * 3));
420
421                    acc0 = Arch::add(acc0, Arch::popcount(op.apply::<Arch>(va0, vb0)));
422                    acc1 = Arch::add(acc1, Arch::popcount(op.apply::<Arch>(va1, vb1)));
423                    acc2 = Arch::add(acc2, Arch::popcount(op.apply::<Arch>(va2, vb2)));
424                    acc3 = Arch::add(acc3, Arch::popcount(op.apply::<Arch>(va3, vb3)));
425                }
426                i += chunk_size;
427                count += 1;
428
429                if count == flush_limit {
430                    unsafe {
431                        let mut acc = Arch::add(acc0, acc1);
432                        acc = Arch::add(acc, acc2);
433                        acc = Arch::add(acc, acc3);
434                        total += Arch::sum_reduce(acc).to_f64() as usize;
435                        acc0 = Arch::zero();
436                        acc1 = Arch::zero();
437                        acc2 = Arch::zero();
438                        acc3 = Arch::zero();
439                    }
440                    count = 0;
441                }
442            }
443
444            unsafe {
445                let mut acc = Arch::add(acc0, acc1);
446                acc = Arch::add(acc, acc2);
447                acc = Arch::add(acc, acc3);
448                total += Arch::sum_reduce(acc).to_f64() as usize;
449            }
450        }
451
452        if i < simd_len {
453            let mut acc = unsafe { Arch::zero() };
454            while i < simd_len {
455                // SAFETY: `i < simd_len` bounds both `s.add(i)`/`o.add(i)` loads
456                // to a `LANE_COUNT` window within the equal-length slices.
457                unsafe {
458                    let va = load(s.as_ptr().add(i));
459                    let vb = load(o.as_ptr().add(i));
460                    acc = Arch::add(acc, Arch::popcount(op.apply::<Arch>(va, vb)));
461                }
462                i += lane_count;
463            }
464            total += unsafe { Arch::sum_reduce(acc) }.to_f64() as usize;
465        }
466
467        while i < len {
468            total += op.apply_scalar(s[i], o[i]).count_ones() as usize;
469            i += 1;
470        }
471
472        Ok(total)
473    }
474
475    /// Computes the horizontal sum of population counts of `self[i] & other[i]`.
476    ///
477    /// # Errors
478    /// Returns [`SimdError::LengthMismatch`] if slice lengths differ.
479    #[inline]
480    pub fn reduce_popcount_and<ORef>(
481        &self,
482        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
483    ) -> Result<usize, SimdError>
484    where
485        ORef: 'a,
486    {
487        self.reduce_popcount_op(other, crate::ops::BitAnd)
488    }
489
490    /// Computes the horizontal sum of population counts of `self[i] | other[i]`.
491    ///
492    /// # Errors
493    /// Returns [`SimdError::LengthMismatch`] if slice lengths differ.
494    #[inline]
495    pub fn reduce_popcount_or<ORef>(
496        &self,
497        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
498    ) -> Result<usize, SimdError>
499    where
500        ORef: 'a,
501    {
502        self.reduce_popcount_op(other, crate::ops::BitOr)
503    }
504
505    /// Computes the horizontal sum of population counts of `self[i] ^ other[i]`.
506    ///
507    /// # Errors
508    /// Returns [`SimdError::LengthMismatch`] if slice lengths differ.
509    #[inline]
510    pub fn reduce_popcount_xor<ORef>(
511        &self,
512        other: &SimdView<'_, T, Arch, Align, Mode, ORef>,
513    ) -> Result<usize, SimdError>
514    where
515        ORef: 'a,
516    {
517        self.reduce_popcount_op(other, crate::ops::BitXor)
518    }
519}
520
521impl<
522        'a,
523        T: 'a,
524        Arch: crate::arch::SimdArch + crate::kernel::SimdKernel<T>,
525        Align: crate::align::Alignment,
526        Mode: crate::execution::ExecutionMode,
527        Ref: 'a,
528    > SimdView<'a, T, Arch, Align, Mode, Ref>
529where
530    T: crate::scalar::Scalar + crate::scalar::NumericElement,
531{
532    /// Returns `Some((index, value))` for the first minimum element.
533    ///
534    /// Correctness: a SIMD reduction pass finds the minimum value, then one
535    /// validation scan rejects NaNs while retaining its first occurrence.
536    ///
537    /// Returns `None` for an empty slice or when any element is NaN. The
538    /// validation scan rejects the whole unordered domain, so an intermediate
539    /// backend result never escapes. Equal extrema use the first slice element,
540    /// including its signed-zero representation.
541    #[inline]
542    pub fn argmin(&self) -> Option<(usize, T)> {
543        let data = self.as_slice();
544        if data.is_empty() {
545            return None;
546        }
547        let min_val = self.reduce(crate::ops::Min);
548        Self::locate_ordered_extremum(data, min_val)
549    }
550
551    /// Returns `Some((index, value))` for the first maximum element.
552    ///
553    /// Correctness: a SIMD reduction pass finds the maximum value, then one
554    /// validation scan rejects NaNs while retaining its first occurrence.
555    ///
556    /// Returns `None` for an empty slice or when any element is NaN. The
557    /// validation scan rejects the whole unordered domain, so an intermediate
558    /// backend result never escapes. Equal extrema use the first slice element,
559    /// including its signed-zero representation.
560    #[inline]
561    pub fn argmax(&self) -> Option<(usize, T)> {
562        let data = self.as_slice();
563        if data.is_empty() {
564            return None;
565        }
566        let max_val = self.reduce(crate::ops::Max);
567        Self::locate_ordered_extremum(data, max_val)
568    }
569
570    #[inline]
571    fn locate_ordered_extremum(data: &[T], extremum: T) -> Option<(usize, T)> {
572        let lane_count = Arch::LANE_COUNT;
573        // Shift-based construction avoids the `1 << 64` overflow a 64-lane
574        // backend would hit; `lane_count` never exceeds `u64::BITS`.
575        let lane_mask = u64::MAX >> (u64::BITS as usize - lane_count.min(64));
576        let vector_len = (data.len() / lane_count) * lane_count;
577        let mut first: Option<usize> = None;
578        let mut index = 0usize;
579
580        while index < vector_len {
581            // SAFETY: `index <= vector_len - lane_count`, so the load reads
582            // exactly `lane_count` in-bounds elements of `data`; the aligned
583            // variant is selected only when `Align` guarantees the view's base
584            // pointer is arch-aligned, and `index` is a multiple of `lane_count`.
585            // Constructing `Arch` already asserts its target features.
586            let (ordered, hits) = unsafe {
587                let ptr = data.as_ptr().add(index);
588                let v = if crate::align::is_aligned_for_arch::<Arch, Align>() {
589                    Arch::load_aligned(ptr)
590                } else {
591                    Arch::load_unaligned(ptr)
592                };
593                // `x == x` is false exactly for NaN, so a lane absent from
594                // `ordered` marks a NaN.
595                let ordered =
596                    Arch::mask_to_bitmask(Arch::vector_to_mask(Arch::cmp_eq(v, v))) & lane_mask;
597                let hits = if first.is_none() {
598                    let target = Arch::splat(extremum);
599                    Arch::mask_to_bitmask(Arch::vector_to_mask(Arch::cmp_eq(v, target))) & lane_mask
600                } else {
601                    0
602                };
603                (ordered, hits)
604            };
605
606            if ordered != lane_mask {
607                return None;
608            }
609            if hits != 0 {
610                first = Some(index + hits.trailing_zeros() as usize);
611            }
612            index += lane_count;
613        }
614
615        for (offset, value) in data[index..].iter().copied().enumerate() {
616            if value.is_nan() {
617                return None;
618            }
619            if first.is_none() && value.partial_cmp(&extremum) == Some(core::cmp::Ordering::Equal) {
620                first = Some(index + offset);
621            }
622        }
623
624        // Report the stored element rather than the reduced extremum so equal
625        // values keep their own representation, notably signed zero.
626        first.map(|at| (at, data[at]))
627    }
628}