| //! SVE-accelerated `Vec::retain_mut` implementation. | |
| //! | |
| //! Two-phase algorithm per 64-element chunk: | |
| //! - Phase A (scalar): evaluates predicate exactly once, in order, into a bool mask. | |
| //! - Phase B (SVE): uses `COMPACT` instruction to compress retained elements. | |
| use core::{cmp, mem, ptr}; | |
| use super::Vec; | |
| use crate::alloc::Allocator; | |
| const CHUNK_SIZE: usize = 64; | |
| pub(super) const MIN_SVE_SIZE_1: usize = 32; | |
| pub(super) const MIN_SVE_SIZE_2: usize = 32; | |
| pub(super) const MIN_SVE_SIZE_4: usize = 64; | |
| pub(super) const MIN_SVE_SIZE_8: usize = 64; | |
| /// Guard for the SVE retain path. On panic, this guard: | |
| /// 1. Scalar-compresses the already-decided prefix of the current chunk. | |
| /// 2. Copies the untouched tail forward. | |
| /// 3. Sets the Vec length. | |
| struct PanicGuard<'a, T, A: Allocator> { | |
| v: &'a mut Vec<T, A>, | |
| /// Start index of the current chunk within the Vec. | |
| read: usize, | |
| /// Write cursor (accumulated from previous chunks). | |
| write: usize, | |
| /// How many elements in the current chunk have been decided by the predicate. | |
| decided: usize, | |
| mask: &'a mut [bool; CHUNK_SIZE], | |
| /// Original length of the Vec. | |
| original_len: usize, | |
| } | |
| impl<T, A: Allocator> Drop for PanicGuard<'_, T, A> { | |
| #[cold] | |
| fn drop(&mut self) { | |
| // Scalar-compress the decided prefix: move kept elements to write position. | |
| let mut dst = self.write; | |
| for i in 0..self.decided { | |
| if self.mask[i] { | |
| // SAFETY: read + i < original_len (in-bounds). | |
| let src = unsafe { self.v.as_ptr().add(self.read + i) }; | |
| // SAFETY: Continued from above. | |
| let dst_ptr = unsafe { self.v.as_mut_ptr().add(dst) }; | |
| // SAFETY: src and dst_ptr < original_len | |
| unsafe { ptr::copy(src, dst_ptr, 1) }; | |
| dst += 1; | |
| } | |
| } | |
| // Copy the untouched tail. | |
| let untouched_start = self.read + self.decided; | |
| let untouched_len = self.original_len - untouched_start; | |
| if untouched_len > 0 { | |
| // SAFETY: untouched_start..original_len are valid; dst + untouched_len <= original_len. | |
| unsafe { | |
| ptr::copy( | |
| self.v.as_ptr().add(untouched_start), | |
| self.v.as_mut_ptr().add(dst), | |
| untouched_len, | |
| ); | |
| } | |
| dst += untouched_len; | |
| } | |
| // SAFETY: After filling holes, all items are in contiguous memory. | |
| unsafe { self.v.set_len(dst) }; | |
| } | |
| } | |
| /// # Safety | |
| /// | |
| /// - `size_of::<T>() == 1/2/4/8` | |
| /// - Called on aarch64 + SVE target. | |
| pub(crate) unsafe fn chunked_retain<T, F, A: Allocator>(v: &mut Vec<T, A>, mut f: F) | |
| where | |
| F: FnMut(&mut T) -> bool, | |
| { | |
| let original_len = v.len(); | |
| let mut guard = PanicGuard { | |
| v, | |
| read: 0, | |
| write: 0, | |
| decided: 0, | |
| mask: &mut [false; CHUNK_SIZE], | |
| original_len, | |
| }; | |
| while guard.read < guard.original_len { | |
| let chunk_len = cmp::min(CHUNK_SIZE, guard.original_len - guard.read); | |
| guard.decided = 0; | |
| // Phase A: scalar predicate evaluation (exactly once, in order). | |
| for i in 0..chunk_len { | |
| // SAFETY: read + i < original_len | |
| let cur = unsafe { &mut *guard.v.as_mut_ptr().add(guard.read + i) }; | |
| guard.mask[i] = f(cur); | |
| guard.decided = i + 1; | |
| if !guard.mask[i] { | |
| // SAFETY: read + i < original_len, and after marking `mask` and `decided`, | |
| // the guard can properly handles the case where drop_in_place panics. | |
| unsafe { ptr::drop_in_place(cur) }; | |
| } | |
| } | |
| // Phase B: SVE compress. | |
| let kept = match mem::size_of::<T>() { | |
| // SAFETY: write <= read and the dispatch guarantees size_of::<T>() matches | |
| // the kernel lane width. | |
| 1 => unsafe { | |
| compact8_kernel( | |
| guard.v.as_mut_ptr().add(guard.read), | |
| guard.v.as_mut_ptr().add(guard.write), | |
| guard.mask.as_ptr(), | |
| chunk_len, | |
| ) | |
| }, | |
| // SAFETY: Same as above. | |
| 2 => unsafe { | |
| compact16_kernel( | |
| guard.v.as_mut_ptr().add(guard.read), | |
| guard.v.as_mut_ptr().add(guard.write), | |
| guard.mask.as_ptr(), | |
| chunk_len, | |
| ) | |
| }, | |
| // SAFETY: Same as above. | |
| 4 => unsafe { | |
| compact32_kernel( | |
| guard.v.as_mut_ptr().add(guard.read), | |
| guard.v.as_mut_ptr().add(guard.write), | |
| guard.mask.as_ptr(), | |
| chunk_len, | |
| ) | |
| }, | |
| // SAFETY: Same as above. | |
| 8 => unsafe { | |
| compact64_kernel( | |
| guard.v.as_mut_ptr().add(guard.read), | |
| guard.v.as_mut_ptr().add(guard.write), | |
| guard.mask.as_ptr(), | |
| chunk_len, | |
| ) | |
| }, | |
| _ => unreachable!(), | |
| }; | |
| guard.write += kept; | |
| guard.read += chunk_len; | |
| } | |
| // SAFETY: write <= original_len, all retained elements are packed at the front. | |
| unsafe { guard.v.set_len(guard.write) }; | |
| mem::forget(guard); | |
| } | |
| macro_rules! sve_compact_kernel { | |
| ( | |
| $name:ident, | |
| size = $size:literal, | |
| lane = $lane:literal, | |
| mem_lane = $mem_lane:literal, | |
| shift = $shift:literal, | |
| inc = $inc:literal | |
| ) => { | |
| /// SVE compress kernel: pack retained elements from `src` to `dst` | |
| /// according to `mask`. Returns the number of retained elements. | |
| /// | |
| /// # Safety | |
| /// | |
| /// - `src` is valid for `chunk_len` reads of `T`, `dst` for `chunk_len` | |
| /// writes, `mask` for `chunk_len` bool reads. | |
| /// - `size_of::<T>()` equals this kernel's lane width in bytes. | |
| #[target_feature(enable = "sve")] | |
| #[inline] | |
| unsafe fn $name<T>( | |
| src: *const T, | |
| dst: *mut T, | |
| mask: *const bool, | |
| chunk_len: usize, | |
| ) -> usize { | |
| debug_assert_eq!(mem::size_of::<T>(), $size); | |
| let idx_in = 0usize; | |
| let mut idx_out = 0usize; | |
| // SVE intrinsics require treating the data as integers, which doesn't | |
| // correctly handle provenance and uninitialized padding. | |
| // | |
| // SAFETY: whilelo predicates every load/store to the remaining elements. | |
| unsafe { | |
| core::arch::asm!( | |
| concat!("whilelo p0.", $lane, ", xzr, {len}"), | |
| "2:", | |
| concat!("ld1b {{ z0.", $lane, " }}, p0/z, [{mask}, {idx_in}]"), | |
| concat!("cmpne p1.", $lane, ", p0/z, z0.", $lane, ", #0"), | |
| concat!("ld1", $mem_lane, " {{ z1.", $lane, " }}, p1/z, [{src}, {idx_in}", $shift, "]"), | |
| concat!("compact z1.", $lane, ", p1, z1.", $lane), | |
| concat!("cntp {kept}, p0, p1.", $lane), | |
| concat!("whilelo p2.", $lane, ", xzr, {kept}"), | |
| concat!("st1", $mem_lane, " {{ z1.", $lane, " }}, p2, [{dst}, {idx_out}", $shift, "]"), | |
| concat!("add {idx_out}, {idx_out}, {kept}"), | |
| concat!("inc", $inc, " {idx_in}"), | |
| concat!("whilelo p0.", $lane, ", {idx_in}, {len}"), | |
| "b.first 2b", | |
| src = in(reg) src, | |
| dst = in(reg) dst, | |
| mask = in(reg) mask, | |
| len = in(reg) chunk_len, | |
| idx_in = inout(reg) idx_in => _, | |
| idx_out = inout(reg) idx_out, | |
| kept = out(reg) _, | |
| out("p0") _, | |
| out("p1") _, | |
| out("p2") _, | |
| out("z0") _, | |
| out("z1") _, | |
| options(nostack), | |
| ); | |
| } | |
| idx_out | |
| } | |
| }; | |
| } | |
| sve_compact_kernel!(compact8_kernel, size = 1, lane = "s", mem_lane = "b", shift = "", inc = "w"); | |
| sve_compact_kernel!( | |
| compact16_kernel, | |
| size = 2, | |
| lane = "s", | |
| mem_lane = "h", | |
| shift = ", lsl #1", | |
| inc = "w" | |
| ); | |
| sve_compact_kernel!( | |
| compact32_kernel, | |
| size = 4, | |
| lane = "s", | |
| mem_lane = "w", | |
| shift = ", lsl #2", | |
| inc = "w" | |
| ); | |
| sve_compact_kernel!( | |
| compact64_kernel, | |
| size = 8, | |
| lane = "d", | |
| mem_lane = "d", | |
| shift = ", lsl #3", | |
| inc = "d" | |
| ); |