Skip to main content

hermes_simd_core/view/
gather.rs

1use crate::align::Alignment;
2use crate::arch::SimdArch;
3use crate::execution::ExecutionMode;
4use crate::kernel::SimdKernel;
5use crate::scalar::Scalar;
6use crate::view::{SimdError, SimdView};
7use core::mem::MaybeUninit;
8
9impl<'a, T: 'a, Arch: SimdArch + SimdKernel<T>, Align: Alignment, Mode: ExecutionMode, Ref: 'a>
10    SimdView<'a, T, Arch, Align, Mode, Ref>
11where
12    T: Scalar,
13{
14    /// Indirectly load (gather) elements from this view using indices, writing them contiguous to `out`.
15    ///
16    /// # Errors
17    /// Returns `SimdError::InsufficientOutputLength` if `out.len() < indices.len()`.
18    /// Returns `SimdError::IndexOutOfBounds` if any index in `indices` is out of bounds (negative or >= view len).
19    #[inline(always)]
20    pub fn gather(&self, indices: &[i32], out: &mut [T]) -> Result<(), SimdError> {
21        // SAFETY: an initialized `[T]` is a valid `[MaybeUninit<T>]` — the cast
22        // only widens the permitted state, never narrows it — and `T: Scalar` is
23        // `Copy`, so overwriting slots with `MaybeUninit::write` drops nothing.
24        let out_uninit = unsafe {
25            core::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut MaybeUninit<T>, out.len())
26        };
27        self.gather_into_uninit(indices, out_uninit)?;
28        Ok(())
29    }
30
31    /// Gather into a possibly-uninitialized buffer, returning the initialized prefix.
32    ///
33    /// This is the single gather implementation; [`gather`](Self::gather) is the
34    /// initialized-slice wrapper over it. On `Ok` exactly the first
35    /// `indices.len()` elements of `out` are initialized (and returned as an
36    /// initialized slice); on `Err` nothing is written, since indices are fully
37    /// validated before any store. Filling an `AlignedVec`'s
38    /// [`spare_capacity_mut`](crate::vec::AlignedVec::spare_capacity_mut) through
39    /// this method and then advancing its length avoids the zero-fill that an
40    /// initialized-slice API would otherwise force.
41    ///
42    /// # Errors
43    /// Returns `SimdError::InsufficientOutputLength` if `out.len() < indices.len()`.
44    /// Returns `SimdError::IndexOutOfBounds` if any index is negative or `>=` the view length.
45    #[inline]
46    pub fn gather_into_uninit<'o>(
47        &self,
48        indices: &[i32],
49        out: &'o mut [MaybeUninit<T>],
50    ) -> Result<&'o mut [T], SimdError> {
51        let len = self.len();
52        if out.len() < indices.len() {
53            return Err(SimdError::InsufficientOutputLength);
54        }
55        let max_idx = len as i32;
56        // Validate all indices first: no element is written unless every index
57        // is in range, so the `Err` path leaves `out` untouched.
58        for &idx in indices {
59            if idx < 0 || idx >= max_idx {
60                return Err(SimdError::IndexOutOfBounds);
61            }
62        }
63
64        let indices_len = indices.len();
65        let lane_count = Arch::LANE_COUNT;
66        let simd_len = (indices_len / lane_count) * lane_count;
67        let base_ptr = self.as_slice().as_ptr();
68        let slice = self.as_slice();
69        // Derive the output pointer once and write exclusively through it: mixing
70        // it with `out[i]` slice reborrows would invalidate its provenance under
71        // Stacked Borrows.
72        let out_ptr = out.as_mut_ptr().cast::<T>();
73
74        // SAFETY: every gathered index was validated in range, so each load
75        // reads a live element of the view. `out` holds at least `indices_len`
76        // slots (checked above); `MaybeUninit<T>` shares `T`'s layout, so the
77        // vector and scalar stores below fill `[0, indices_len)` through
78        // `out_ptr` without reading any slot first. No slot is written twice.
79        unsafe {
80            for i in (0..simd_len).step_by(lane_count) {
81                let idx_slice = &indices[i..i + lane_count];
82                let idx_vec = crate::sparse::spmv::build_index_vector::<T, Arch>(idx_slice);
83                let v = Arch::gather(base_ptr, idx_vec);
84                Arch::store_unaligned(out_ptr.add(i), v);
85            }
86            for i in simd_len..indices_len {
87                core::ptr::write(out_ptr.add(i), slice[indices[i] as usize]);
88            }
89            // Every element of `[0, indices_len)` is now initialized.
90            Ok(core::slice::from_raw_parts_mut(out_ptr, indices_len))
91        }
92    }
93}