Skip to main content

hermes_simd_core/view/
vector_reg.rs

1//! Monomorphized SIMD vector register wrapper.
2//!
3//! # Safety
4//!
5//! Every operation here ultimately calls a `#[target_feature]`-gated
6//! [`SimdKernel`](crate::kernel::SimdKernel) method, sound only on a host that
7//! implements `Arch`. Two disciplines discharge that obligation:
8//!
9//! - **Safe methods** call `assert_runtime_supported` (or
10//!   `runtime_support_result` for the `try_*` forms) before their `unsafe`
11//!   kernel call, so the check immediately above each block is its target-feature
12//!   proof. Those blocks therefore carry a per-site `SAFETY` comment only when
13//!   they add a further obligation — a raw-pointer bound, a lane-index range, or
14//!   a `MaybeUninit` initialization.
15//! - The `pub unsafe fn` register loads/stores (`load_aligned` and friends) push
16//!   *both* the target-feature requirement and pointer validity to the caller;
17//!   each states both in its `# Safety` section.
18//!
19//! Lane-count and lane-index preconditions (`from_array`, `extract`, `cast`, …)
20//! are proven at compile time by the `AssertLaneCount`/`AssertLaneIndex` const
21//! guards, so a mismatch fails the build rather than reading out of bounds.
22
23use super::mask_reg::Mask;
24use super::SimdError;
25use crate::arch::SimdArch;
26use crate::kernel::{SimdKernel, MAX_SIMD_LANES};
27use crate::mask::BitMask;
28use crate::scalar::{CastFrom, Scalar};
29use core::marker::PhantomData;
30
31/// A monomorphized vector register type wrapping the architecture-native raw register.
32#[repr(transparent)]
33pub struct Vector<T, Arch>
34where
35    Arch: SimdArch + SimdKernel<T>,
36    T: Scalar,
37{
38    /// The underlying raw vector register.
39    pub raw: Arch::Vector,
40    _marker: PhantomData<T>,
41}
42
43impl<T, Arch> Clone for Vector<T, Arch>
44where
45    Arch: SimdArch + SimdKernel<T>,
46    T: Scalar,
47{
48    #[inline(always)]
49    fn clone(&self) -> Self {
50        *self
51    }
52}
53
54impl<T, Arch> Copy for Vector<T, Arch>
55where
56    Arch: SimdArch + SimdKernel<T>,
57    T: Scalar,
58{
59}
60
61impl<T, Arch> core::fmt::Debug for Vector<T, Arch>
62where
63    Arch: SimdArch + SimdKernel<T>,
64    T: Scalar + core::fmt::Debug,
65{
66    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67        assert_runtime_supported::<T, Arch>();
68        const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
69        let lane_count = Arch::LANE_COUNT;
70        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
71        // SAFETY: target feature checked above. The store writes exactly
72        // `lane_count` elements into the `MAX_SIMD_LANES`-slot buffer (bounded by
73        // `LANE_BOUND_CHECK`), so the `lane_count`-length slice reads only
74        // initialized elements.
75        unsafe {
76            Arch::store_unaligned(buf.as_mut_ptr() as *mut T, self.raw);
77            let init_slice = core::slice::from_raw_parts(buf.as_ptr() as *const T, lane_count);
78            f.debug_list().entries(init_slice).finish()
79        }
80    }
81}
82
83impl<T, Arch> PartialEq for Vector<T, Arch>
84where
85    Arch: SimdArch + SimdKernel<T>,
86    T: Scalar + PartialEq,
87{
88    #[inline]
89    fn eq(&self, other: &Self) -> bool {
90        assert_runtime_supported::<T, Arch>();
91        const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
92        let lane_count = Arch::LANE_COUNT;
93        let mut buf_self = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
94        let mut buf_other = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
95        // SAFETY: target feature checked above. Each store writes `lane_count`
96        // elements into its buffer, so both `lane_count`-length slices read only
97        // initialized elements.
98        unsafe {
99            Arch::store_unaligned(buf_self.as_mut_ptr() as *mut T, self.raw);
100            Arch::store_unaligned(buf_other.as_mut_ptr() as *mut T, other.raw);
101            let slice_self = core::slice::from_raw_parts(buf_self.as_ptr() as *const T, lane_count);
102            let slice_other =
103                core::slice::from_raw_parts(buf_other.as_ptr() as *const T, lane_count);
104            slice_self == slice_other
105        }
106    }
107}
108
109impl<T, Arch> Eq for Vector<T, Arch>
110where
111    Arch: SimdArch + SimdKernel<T>,
112    T: Scalar + Eq,
113{
114}
115
116impl<T, Arch> Vector<T, Arch>
117where
118    Arch: SimdArch + SimdKernel<T>,
119    T: Scalar,
120{
121    /// Create a new Vector wrapping a raw vector register.
122    #[inline(always)]
123    pub const fn new(raw: Arch::Vector) -> Self {
124        Self {
125            raw,
126            _marker: PhantomData,
127        }
128    }
129
130    /// Construct a Vector with all lanes set to zero.
131    #[inline(always)]
132    pub fn zero() -> Self {
133        Self::try_zero().expect("SIMD target is not supported or enabled on this host")
134    }
135
136    /// Try to construct a Vector with all lanes set to zero.
137    #[inline(always)]
138    pub fn try_zero() -> Result<Self, SimdError> {
139        runtime_support_result::<T, Arch>()?;
140        Ok(Self::new(unsafe { Arch::zero() }))
141    }
142
143    /// Construct a Vector by broadcasting a scalar value to all lanes.
144    #[inline(always)]
145    pub fn splat(val: T) -> Self {
146        Self::try_splat(val).expect("SIMD target is not supported or enabled on this host")
147    }
148
149    /// Try to construct a Vector by broadcasting a scalar value to all lanes.
150    #[inline(always)]
151    pub fn try_splat(val: T) -> Result<Self, SimdError> {
152        runtime_support_result::<T, Arch>()?;
153        Ok(Self::new(unsafe { Arch::splat(val) }))
154    }
155
156    /// Load a Vector from an aligned pointer.
157    ///
158    /// # Safety
159    /// The host must support `Arch`'s target features, and `ptr` must be valid
160    /// for reads and aligned to `Arch::LANE_COUNT * size_of::<T>()` bytes.
161    #[inline(always)]
162    pub unsafe fn load_aligned(ptr: *const T) -> Self {
163        Self::new(Arch::load_aligned(ptr))
164    }
165
166    /// Load a Vector from an unaligned pointer.
167    ///
168    /// # Safety
169    /// The host must support `Arch`'s target features, and `ptr` must be valid
170    /// for reads.
171    #[inline(always)]
172    pub unsafe fn load_unaligned(ptr: *const T) -> Self {
173        Self::new(Arch::load_unaligned(ptr))
174    }
175
176    /// Store the Vector elements to an aligned pointer.
177    ///
178    /// # Safety
179    /// The host must support `Arch`'s target features, and `ptr` must be valid
180    /// for writes and aligned to `Arch::LANE_COUNT * size_of::<T>()` bytes.
181    #[inline(always)]
182    pub unsafe fn store_aligned(self, ptr: *mut T) {
183        Arch::store_aligned(ptr, self.raw);
184    }
185
186    /// Store the Vector elements to an unaligned pointer.
187    ///
188    /// # Safety
189    /// The host must support `Arch`'s target features, and `ptr` must be valid
190    /// for writes.
191    #[inline(always)]
192    pub unsafe fn store_unaligned(self, ptr: *mut T) {
193        Arch::store_unaligned(ptr, self.raw);
194    }
195
196    /// Masked load from an unaligned pointer: active lanes loaded from `ptr`, inactive lanes from `src`.
197    ///
198    /// # Safety
199    /// The host must support `Arch`'s target features, and `ptr` must be valid
200    /// for reads of `Arch::LANE_COUNT` elements.
201    #[inline(always)]
202    pub unsafe fn masked_load_unaligned(ptr: *const T, mask: Mask<T, Arch>, src: Self) -> Self {
203        Self::new(Arch::masked_load_unaligned(ptr, mask.raw, src.raw))
204    }
205
206    /// Masked store to an unaligned pointer: active lanes written to `ptr`, inactive lanes left unchanged.
207    ///
208    /// # Safety
209    /// The host must support `Arch`'s target features, and `ptr` must be valid
210    /// for writes of `Arch::LANE_COUNT` elements.
211    #[inline(always)]
212    pub unsafe fn masked_store_unaligned(self, ptr: *mut T, mask: Mask<T, Arch>) {
213        Arch::masked_store_unaligned(ptr, mask.raw, self.raw);
214    }
215
216    /// Load one vector from the start of a slice using the unaligned kernel load.
217    ///
218    /// Returns [`SimdError::InsufficientInputLength`] when `data` has fewer
219    /// elements than `Arch::LANE_COUNT`.
220    #[inline(always)]
221    pub fn load_unaligned_from_slice(data: &[T]) -> Result<Self, SimdError> {
222        runtime_support_result::<T, Arch>()?;
223        if data.len() < Arch::LANE_COUNT {
224            return Err(SimdError::InsufficientInputLength);
225        }
226        // SAFETY: length was checked for one complete vector; unaligned load
227        // has no alignment precondition.
228        unsafe { Ok(Self::load_unaligned(data.as_ptr())) }
229    }
230
231    /// Load one vector from the start of a slice using the aligned kernel load.
232    ///
233    /// Returns [`SimdError::InsufficientInputLength`] when `data` has fewer
234    /// elements than `Arch::LANE_COUNT`, and [`SimdError::UnalignedAddress`]
235    /// when the slice start is not aligned to the vector byte width.
236    #[inline(always)]
237    pub fn load_aligned_from_slice(data: &[T]) -> Result<Self, SimdError> {
238        runtime_support_result::<T, Arch>()?;
239        if data.len() < Arch::LANE_COUNT {
240            return Err(SimdError::InsufficientInputLength);
241        }
242        if !is_vector_aligned::<T, Arch>(data.as_ptr()) {
243            return Err(SimdError::UnalignedAddress);
244        }
245        // SAFETY: length and vector-width alignment were checked above.
246        unsafe { Ok(Self::load_aligned(data.as_ptr())) }
247    }
248
249    /// Store this vector to the start of a slice using the unaligned kernel store.
250    ///
251    /// Returns [`SimdError::InsufficientOutputLength`] when `out` has fewer
252    /// elements than `Arch::LANE_COUNT`.
253    #[inline(always)]
254    pub fn store_unaligned_to_slice(self, out: &mut [T]) -> Result<(), SimdError> {
255        runtime_support_result::<T, Arch>()?;
256        if out.len() < Arch::LANE_COUNT {
257            return Err(SimdError::InsufficientOutputLength);
258        }
259        // SAFETY: length was checked for one complete vector; unaligned store
260        // has no alignment precondition.
261        unsafe {
262            self.store_unaligned(out.as_mut_ptr());
263        }
264        Ok(())
265    }
266
267    /// Store this vector to the start of a slice using the aligned kernel store.
268    ///
269    /// Returns [`SimdError::InsufficientOutputLength`] when `out` has fewer
270    /// elements than `Arch::LANE_COUNT`, and [`SimdError::UnalignedAddress`]
271    /// when the slice start is not aligned to the vector byte width.
272    #[inline(always)]
273    pub fn store_aligned_to_slice(self, out: &mut [T]) -> Result<(), SimdError> {
274        runtime_support_result::<T, Arch>()?;
275        if out.len() < Arch::LANE_COUNT {
276            return Err(SimdError::InsufficientOutputLength);
277        }
278        if !is_vector_aligned::<T, Arch>(out.as_ptr()) {
279            return Err(SimdError::UnalignedAddress);
280        }
281        // SAFETY: length and vector-width alignment were checked above.
282        unsafe {
283            self.store_aligned(out.as_mut_ptr());
284        }
285        Ok(())
286    }
287
288    /// Safe masked load from a slice.
289    ///
290    /// Active lanes (according to `mask`) must reside within the bounds of `data`.
291    /// Inactive lanes are populated from the corresponding lanes of `src`.
292    #[inline]
293    pub fn masked_load_from_slice(
294        data: &[T],
295        mask: Mask<T, Arch>,
296        src: Self,
297    ) -> Result<Self, SimdError> {
298        runtime_support_result::<T, Arch>()?;
299        let len = data.len();
300        let bm = unsafe { mask.to_bitmask().0 };
301        let is_out_of_bounds = if len < u64::BITS as usize {
302            (bm >> len) != 0
303        } else {
304            false
305        };
306        if is_out_of_bounds {
307            return Err(SimdError::IndexOutOfBounds);
308        }
309
310        if len >= Arch::LANE_COUNT {
311            // SAFETY: data has at least LANE_COUNT elements, and we verified that no active lane index
312            // is beyond the slice bounds (since len >= LANE_COUNT).
313            // Hence, it is safe to load directly.
314            unsafe { Ok(Self::masked_load_unaligned(data.as_ptr(), mask, src)) }
315        } else {
316            // Short slice path to prevent page faults: copy to a stack-aligned
317            // `MAX_SIMD_LANES`-lane buffer.
318            // The buffer holds `LANE_COUNT` lanes; `LANE_BOUND_CHECK` proves
319            // `LANE_COUNT <= MAX_SIMD_LANES` at compile time per backend.
320            const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
321            #[repr(C, align(64))]
322            struct AlignedBuf<T>([core::mem::MaybeUninit<T>; MAX_SIMD_LANES]);
323
324            let mut buf = AlignedBuf([core::mem::MaybeUninit::uninit(); MAX_SIMD_LANES]);
325            for i in 0..len {
326                buf.0[i].write(data[i]);
327            }
328            for i in len..Arch::LANE_COUNT {
329                buf.0[i].write(T::ZERO);
330            }
331
332            unsafe {
333                Ok(Self::masked_load_unaligned(
334                    buf.0.as_ptr() as *const T,
335                    mask,
336                    src,
337                ))
338            }
339        }
340    }
341
342    /// Safe masked store to a slice.
343    ///
344    /// Active lanes (according to `mask`) must reside within the bounds of `data`.
345    /// Inactive lanes in the slice are left unchanged.
346    #[inline]
347    pub fn masked_store_to_slice(
348        self,
349        data: &mut [T],
350        mask: Mask<T, Arch>,
351    ) -> Result<(), SimdError> {
352        runtime_support_result::<T, Arch>()?;
353        let len = data.len();
354        let bm = unsafe { mask.to_bitmask().0 };
355        let is_out_of_bounds = if len < u64::BITS as usize {
356            (bm >> len) != 0
357        } else {
358            false
359        };
360        if is_out_of_bounds {
361            return Err(SimdError::IndexOutOfBounds);
362        }
363
364        if len >= Arch::LANE_COUNT {
365            // SAFETY: data has at least LANE_COUNT elements, and we verified that no active lane index
366            // is beyond the slice bounds (since len >= LANE_COUNT).
367            // Hence, it is safe to store directly.
368            unsafe {
369                self.masked_store_unaligned(data.as_mut_ptr(), mask);
370            }
371        } else {
372            // Short slice path to prevent page faults: copy to stack-aligned buffer, perform masked store,
373            // then copy active elements back.
374            // The buffer holds `LANE_COUNT` lanes; `LANE_BOUND_CHECK` proves
375            // `LANE_COUNT <= MAX_SIMD_LANES` at compile time per backend.
376            const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
377            #[repr(C, align(64))]
378            struct AlignedBuf<T>([core::mem::MaybeUninit<T>; MAX_SIMD_LANES]);
379
380            let mut buf = AlignedBuf([core::mem::MaybeUninit::uninit(); MAX_SIMD_LANES]);
381            for i in 0..len {
382                buf.0[i].write(data[i]);
383            }
384
385            unsafe {
386                self.masked_store_unaligned(buf.0.as_mut_ptr() as *mut T, mask);
387            }
388
389            unsafe {
390                let init_slice = core::slice::from_raw_parts(buf.0.as_ptr() as *const T, len);
391                data.copy_from_slice(init_slice);
392            }
393        }
394        Ok(())
395    }
396
397    /// Horizontal sum reduction of all lanes in the Vector.
398    #[inline(always)]
399    pub fn sum_reduce(self) -> T {
400        assert_runtime_supported::<T, Arch>();
401        unsafe { Arch::sum_reduce(self.raw) }
402    }
403
404    /// Elementwise population count (number of set bits).
405    #[inline(always)]
406    pub fn popcount(self) -> Self {
407        assert_runtime_supported::<T, Arch>();
408        Self::new(unsafe { Arch::popcount(self.raw) })
409    }
410
411    /// Horizontal bitwise AND reduction across all lanes.
412    #[inline(always)]
413    pub fn horizontal_bitwise_and(self) -> T {
414        assert_runtime_supported::<T, Arch>();
415        unsafe { Arch::horizontal_bitwise_and(self.raw) }
416    }
417
418    /// Horizontal bitwise OR reduction across all lanes.
419    #[inline(always)]
420    pub fn horizontal_bitwise_or(self) -> T {
421        assert_runtime_supported::<T, Arch>();
422        unsafe { Arch::horizontal_bitwise_or(self.raw) }
423    }
424
425    /// Horizontal bitwise XOR reduction across all lanes.
426    #[inline(always)]
427    pub fn horizontal_bitwise_xor(self) -> T {
428        assert_runtime_supported::<T, Arch>();
429        unsafe { Arch::horizontal_bitwise_xor(self.raw) }
430    }
431
432    /// Elementwise absolute value.
433    #[inline(always)]
434    pub fn abs(self) -> Self {
435        assert_runtime_supported::<T, Arch>();
436        Self::new(unsafe { Arch::abs(self.raw) })
437    }
438
439    /// Elementwise minimum of `self` and `other`.
440    #[inline(always)]
441    pub fn min(self, other: Self) -> Self {
442        assert_runtime_supported::<T, Arch>();
443        Self::new(unsafe { Arch::min(self.raw, other.raw) })
444    }
445
446    /// Elementwise maximum of `self` and `other`.
447    #[inline(always)]
448    pub fn max(self, other: Self) -> Self {
449        assert_runtime_supported::<T, Arch>();
450        Self::new(unsafe { Arch::max(self.raw, other.raw) })
451    }
452
453    /// Elementwise square root.
454    #[inline(always)]
455    pub fn sqrt(self) -> Self {
456        assert_runtime_supported::<T, Arch>();
457        Self::new(unsafe { Arch::sqrt(self.raw) })
458    }
459
460    /// Elementwise equal comparison (`self == other`).
461    #[inline(always)]
462    pub fn cmp_eq(self, other: Self) -> Self {
463        assert_runtime_supported::<T, Arch>();
464        Self::new(unsafe { Arch::cmp_eq(self.raw, other.raw) })
465    }
466
467    /// Elementwise not-equal comparison (`self != other`).
468    #[inline(always)]
469    pub fn cmp_ne(self, other: Self) -> Self {
470        assert_runtime_supported::<T, Arch>();
471        Self::new(unsafe { Arch::cmp_ne(self.raw, other.raw) })
472    }
473
474    /// Elementwise less-than comparison (`self < other`).
475    #[inline(always)]
476    pub fn cmp_lt(self, other: Self) -> Self {
477        assert_runtime_supported::<T, Arch>();
478        Self::new(unsafe { Arch::cmp_lt(self.raw, other.raw) })
479    }
480
481    /// Elementwise less-than-or-equal comparison (`self <= other`).
482    #[inline(always)]
483    pub fn cmp_le(self, other: Self) -> Self {
484        assert_runtime_supported::<T, Arch>();
485        Self::new(unsafe { Arch::cmp_le(self.raw, other.raw) })
486    }
487
488    /// Elementwise greater-than comparison (`self > other`).
489    #[inline(always)]
490    pub fn cmp_gt(self, other: Self) -> Self {
491        assert_runtime_supported::<T, Arch>();
492        Self::new(unsafe { Arch::cmp_gt(self.raw, other.raw) })
493    }
494
495    /// Elementwise greater-than-or-equal comparison (`self >= other`).
496    #[inline(always)]
497    pub fn cmp_ge(self, other: Self) -> Self {
498        assert_runtime_supported::<T, Arch>();
499        Self::new(unsafe { Arch::cmp_ge(self.raw, other.raw) })
500    }
501
502    /// Conditional blend: select lanes from `true_val` where the mask lane in `self` is active (sign bit set), and from `false_val` otherwise.
503    #[inline(always)]
504    pub fn blend(self, true_val: Self, false_val: Self) -> Self {
505        assert_runtime_supported::<T, Arch>();
506        Self::new(unsafe { Arch::blend(self.raw, true_val.raw, false_val.raw) })
507    }
508
509    /// Create a Vector from an array of size `N`, where `N` must equal `Arch::LANE_COUNT`.
510    #[inline(always)]
511    pub fn from_array<const N: usize>(arr: [T; N]) -> Self {
512        assert_runtime_supported::<T, Arch>();
513        let _ = AssertLaneCount::<T, Arch, N>::OK;
514        // SAFETY: target feature checked above; `AssertLaneCount` proved
515        // `N == LANE_COUNT`, so `arr` holds a full vector's worth of elements for
516        // the unaligned load.
517        unsafe { Self::load_unaligned(arr.as_ptr()) }
518    }
519
520    /// Try to create a Vector from an array of size `N`, where `N` must equal
521    /// `Arch::LANE_COUNT`.
522    #[inline(always)]
523    pub fn try_from_array<const N: usize>(arr: [T; N]) -> Result<Self, SimdError> {
524        runtime_support_result::<T, Arch>()?;
525        let _ = AssertLaneCount::<T, Arch, N>::OK;
526        // SAFETY: as `from_array` — `N == LANE_COUNT`, so `arr` covers the load.
527        unsafe { Ok(Self::load_unaligned(arr.as_ptr())) }
528    }
529
530    /// Convert the vector to an array of size `N`, where `N` must equal `Arch::LANE_COUNT`.
531    #[inline(always)]
532    pub fn to_array<const N: usize>(self) -> [T; N] {
533        assert_runtime_supported::<T, Arch>();
534        let _ = AssertLaneCount::<T, Arch, N>::OK;
535        let mut arr = [core::mem::MaybeUninit::<T>::uninit(); N];
536        // SAFETY: target feature checked above; `AssertLaneCount` proved
537        // `N == LANE_COUNT`, so the store initializes all `N` slots before the
538        // `[T; N]` is read out.
539        unsafe {
540            self.store_unaligned(arr.as_mut_ptr() as *mut T);
541            core::ptr::read(arr.as_ptr() as *const [T; N])
542        }
543    }
544
545    /// Convert this vector mask representation (sign bits) into a portable `BitMask`.
546    #[inline(always)]
547    pub fn to_bitmask(self) -> BitMask<64> {
548        assert_runtime_supported::<T, Arch>();
549        const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
550        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
551        let lanes = <Arch as SimdKernel<T>>::LANE_COUNT;
552        // SAFETY: target feature checked above; the store writes `lanes` elements
553        // into the `MAX_SIMD_LANES`-slot buffer (bounded by `LANE_BOUND_CHECK`),
554        // so `assume_init` reads only those initialized lanes.
555        unsafe {
556            self.store_unaligned(buf.as_mut_ptr() as *mut T);
557            let mut m = 0u64;
558            for i in 0..lanes {
559                let val = buf[i].assume_init();
560                if val.to_f64() != 0.0 || val.is_nan() {
561                    m |= 1u64 << i;
562                }
563            }
564            BitMask(m)
565        }
566    }
567
568    /// Elementwise equal comparison returning a native `Mask`.
569    #[inline(always)]
570    pub fn cmp_eq_mask(self, other: Self) -> Mask<T, Arch> {
571        // SAFETY: `from_bitmask` requires only that the host support `Arch`,
572        // which the inner `cmp_eq`/`to_bitmask` calls already assert.
573        unsafe { Mask::from_bitmask(self.cmp_eq(other).to_bitmask()) }
574    }
575
576    /// Elementwise not-equal comparison returning a native `Mask`.
577    #[inline(always)]
578    pub fn cmp_ne_mask(self, other: Self) -> Mask<T, Arch> {
579        // SAFETY: `from_bitmask` requires only that the host support `Arch`,
580        // which the inner `cmp_ne`/`to_bitmask` calls already assert.
581        unsafe { Mask::from_bitmask(self.cmp_ne(other).to_bitmask()) }
582    }
583
584    /// Elementwise less-than comparison returning a native `Mask`.
585    #[inline(always)]
586    pub fn cmp_lt_mask(self, other: Self) -> Mask<T, Arch> {
587        // SAFETY: `from_bitmask` requires only that the host support `Arch`,
588        // which the inner `cmp_lt`/`to_bitmask` calls already assert.
589        unsafe { Mask::from_bitmask(self.cmp_lt(other).to_bitmask()) }
590    }
591
592    /// Elementwise less-than-or-equal comparison returning a native `Mask`.
593    #[inline(always)]
594    pub fn cmp_le_mask(self, other: Self) -> Mask<T, Arch> {
595        // SAFETY: `from_bitmask` requires only that the host support `Arch`,
596        // which the inner `cmp_le`/`to_bitmask` calls already assert.
597        unsafe { Mask::from_bitmask(self.cmp_le(other).to_bitmask()) }
598    }
599
600    /// Elementwise greater-than comparison returning a native `Mask`.
601    #[inline(always)]
602    pub fn cmp_gt_mask(self, other: Self) -> Mask<T, Arch> {
603        // SAFETY: `from_bitmask` requires only that the host support `Arch`,
604        // which the inner `cmp_gt`/`to_bitmask` calls already assert.
605        unsafe { Mask::from_bitmask(self.cmp_gt(other).to_bitmask()) }
606    }
607
608    /// Elementwise greater-than-or-equal comparison returning a native `Mask`.
609    #[inline(always)]
610    pub fn cmp_ge_mask(self, other: Self) -> Mask<T, Arch> {
611        // SAFETY: `from_bitmask` requires only that the host support `Arch`,
612        // which the inner `cmp_ge`/`to_bitmask` calls already assert.
613        unsafe { Mask::from_bitmask(self.cmp_ge(other).to_bitmask()) }
614    }
615
616    /// Cast the vector elements to another scalar type `U` where the lane counts match.
617    #[inline(always)]
618    pub fn cast<U>(self) -> Vector<U, Arch>
619    where
620        Arch: SimdKernel<U>,
621        U: Scalar,
622        U: CastFrom<T>,
623    {
624        assert_runtime_supported::<T, Arch>();
625        assert_runtime_supported::<U, Arch>();
626        let _ = AssertLaneCountSame::<T, U, Arch>::OK;
627        const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
628        let mut buf_t = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
629        let mut buf_u = [core::mem::MaybeUninit::<U>::uninit(); MAX_SIMD_LANES];
630        let lanes = <Arch as SimdKernel<T>>::LANE_COUNT;
631        // SAFETY: target features for both `T` and `U` checked above;
632        // `AssertLaneCountSame` and `LANE_BOUND_CHECK` bound `lanes` within both
633        // buffers. The `T` store initializes `buf_t[..lanes]` before `assume_init`
634        // reads it, the loop initializes `buf_u[..lanes]`, and the `U` load reads
635        // exactly those `lanes` lanes.
636        unsafe {
637            self.store_unaligned(buf_t.as_mut_ptr() as *mut T);
638            for i in 0..lanes {
639                let val_t = buf_t[i].assume_init();
640                buf_u[i].write(U::cast_from(val_t));
641            }
642            Vector::<U, Arch>::new(Arch::load_unaligned(buf_u.as_ptr() as *const U))
643        }
644    }
645
646    /// Extract a single lane element by index at compile-time.
647    #[inline(always)]
648    pub fn extract<const I: usize>(self) -> T {
649        assert_runtime_supported::<T, Arch>();
650        let _ = AssertLaneIndex::<T, Arch, I>::OK;
651        const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
652        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
653        // SAFETY: target feature checked above; `AssertLaneIndex` proved
654        // `I < LANE_COUNT`, and the store initializes `buf[..LANE_COUNT]`, so
655        // `buf[I]` is initialized.
656        unsafe {
657            self.store_unaligned(buf.as_mut_ptr() as *mut T);
658            buf[I].assume_init()
659        }
660    }
661
662    /// Insert a value into a single lane by index at compile-time.
663    #[inline(always)]
664    pub fn insert<const I: usize>(self, val: T) -> Self {
665        assert_runtime_supported::<T, Arch>();
666        let _ = AssertLaneIndex::<T, Arch, I>::OK;
667        const { <Arch as SimdKernel<T>>::LANE_BOUND_CHECK };
668        let mut buf = [core::mem::MaybeUninit::<T>::uninit(); MAX_SIMD_LANES];
669        // SAFETY: target feature checked above; `AssertLaneIndex` proved
670        // `I < LANE_COUNT`. The store initializes `buf[..LANE_COUNT]`, `buf[I]` is
671        // then overwritten, and the reload reads all `LANE_COUNT` initialized
672        // lanes.
673        unsafe {
674            self.store_unaligned(buf.as_mut_ptr() as *mut T);
675            buf[I].write(val);
676            Self::load_unaligned(buf.as_ptr() as *const T)
677        }
678    }
679
680    /// Load a Vector from a chunk index of a `SimdView`.
681    #[inline(always)]
682    pub fn from_view_chunk<Align, Mode, Ref>(
683        view: &super::SimdView<'_, T, Arch, Align, Mode, Ref>,
684        chunk_idx: usize,
685    ) -> Self
686    where
687        Align: crate::align::Alignment,
688        Mode: crate::execution::ExecutionMode,
689        Ref: core::ops::Deref<Target = [T]>,
690    {
691        assert_runtime_supported::<T, Arch>();
692        let offset = chunk_idx * Arch::LANE_COUNT;
693        let slice = view.as_slice();
694        assert!(
695            offset + Arch::LANE_COUNT <= slice.len(),
696            "Chunk index out of bounds"
697        );
698        // SAFETY: target feature checked above; the assert guarantees
699        // `offset + LANE_COUNT <= slice.len()`, so the load reads a full vector in
700        // bounds. The aligned variant is taken only when `Align` proves the base
701        // pointer is arch-aligned and `offset` is a lane-count multiple.
702        unsafe {
703            if crate::align::is_aligned_for_arch::<Arch, Align>() {
704                Self::load_aligned(slice.as_ptr().add(offset))
705            } else {
706                Self::load_unaligned(slice.as_ptr().add(offset))
707            }
708        }
709    }
710
711    /// Store this Vector into a mutable chunk of a mutable `SimdView`.
712    #[inline(always)]
713    pub fn store_to_view_chunk<'a, Align, Mode>(
714        self,
715        view: &mut super::SimdView<'a, T, Arch, Align, Mode, &'a mut [T]>,
716        chunk_idx: usize,
717    ) where
718        Align: crate::align::Alignment,
719        Mode: crate::execution::ExecutionMode,
720    {
721        assert_runtime_supported::<T, Arch>();
722        let offset = chunk_idx * Arch::LANE_COUNT;
723        let slice = view.as_slice_mut();
724        assert!(
725            offset + Arch::LANE_COUNT <= slice.len(),
726            "Chunk index out of bounds"
727        );
728        // SAFETY: as `from_view_chunk` — the assert guarantees
729        // `offset + LANE_COUNT <= slice.len()`, so the store writes a full vector
730        // in bounds; the aligned variant is gated on `Align`.
731        unsafe {
732            if crate::align::is_aligned_for_arch::<Arch, Align>() {
733                self.store_aligned(slice.as_mut_ptr().add(offset));
734            } else {
735                self.store_unaligned(slice.as_mut_ptr().add(offset));
736            }
737        }
738    }
739}
740
741#[inline(always)]
742fn is_vector_aligned<T, Arch>(ptr: *const T) -> bool
743where
744    Arch: SimdArch + SimdKernel<T>,
745    T: Scalar,
746{
747    let alignment = Arch::LANE_COUNT * core::mem::size_of::<T>();
748    alignment != 0 && (ptr as usize).is_multiple_of(alignment)
749}
750
751#[inline(always)]
752pub(crate) fn runtime_support_result<T, Arch>() -> Result<(), SimdError>
753where
754    Arch: SimdArch + SimdKernel<T>,
755    T: Scalar,
756{
757    if Arch::is_runtime_supported() {
758        Ok(())
759    } else {
760        Err(SimdError::UnsupportedTarget)
761    }
762}
763
764#[inline(always)]
765pub(crate) fn assert_runtime_supported<T, Arch>()
766where
767    Arch: SimdArch + SimdKernel<T>,
768    T: Scalar,
769{
770    assert!(
771        Arch::is_runtime_supported(),
772        "SIMD target is not supported or enabled on this host"
773    );
774}
775
776struct AssertLaneIndex<T, Arch, const I: usize>(PhantomData<(T, Arch)>);
777impl<T, Arch, const I: usize> AssertLaneIndex<T, Arch, I>
778where
779    Arch: SimdArch + SimdKernel<T>,
780    T: Scalar,
781{
782    const OK: () = {
783        assert!(
784            I < <Arch as SimdKernel<T>>::LANE_COUNT,
785            "Lane index out of bounds"
786        );
787    };
788}
789
790struct AssertLaneCountSame<T, U, Arch>(PhantomData<(T, U, Arch)>);
791impl<T, U, Arch> AssertLaneCountSame<T, U, Arch>
792where
793    Arch: SimdArch + SimdKernel<T> + SimdKernel<U>,
794    T: Scalar,
795    U: Scalar,
796{
797    const OK: () = {
798        assert!(
799            <Arch as SimdKernel<T>>::LANE_COUNT == <Arch as SimdKernel<U>>::LANE_COUNT,
800            "Source and destination vectors must have the same lane count"
801        );
802    };
803}
804
805struct AssertLaneCount<T, Arch, const N: usize>(PhantomData<(T, Arch)>);
806impl<T, Arch, const N: usize> AssertLaneCount<T, Arch, N>
807where
808    Arch: SimdArch + SimdKernel<T>,
809    T: Scalar,
810{
811    const OK: () = {
812        assert!(
813            N == Arch::LANE_COUNT,
814            "Array size must match Vector lane count"
815        );
816    };
817}