|
| 1 | +//! SVE-accelerated `Vec::retain_mut` implementation. |
| 2 | +//! |
| 3 | +//! Two-phase algorithm per 64-element chunk: |
| 4 | +//! - Phase A (scalar): evaluates predicate exactly once, in order, into a bool mask. |
| 5 | +//! - Phase B (SVE): uses `COMPACT` instruction to compress retained elements. |
| 6 | +//! |
| 7 | +
|
| 8 | +use core::{cmp, mem, ptr}; |
| 9 | + |
| 10 | +use super::Vec; |
| 11 | +use crate::alloc::Allocator; |
| 12 | + |
| 13 | +const CHUNK_SIZE: usize = 64; |
| 14 | + |
| 15 | +pub(super) const MIN_SVE_SIZE_1: usize = 32; |
| 16 | +pub(super) const MIN_SVE_SIZE_2: usize = 32; |
| 17 | +pub(super) const MIN_SVE_SIZE_4: usize = 64; |
| 18 | +pub(super) const MIN_SVE_SIZE_8: usize = 64; |
| 19 | + |
| 20 | +/// Guard for the SVE retain path. Since `!needs_drop::<T>()`, no drop is needed |
| 21 | +/// for removed elements. On panic, this guard: |
| 22 | +/// 1. Scalar-compresses the already-decided prefix of the current chunk. |
| 23 | +/// 2. Copies the untouched tail forward. |
| 24 | +/// 3. Sets the Vec length. |
| 25 | +struct PanicGuard<'a, T, A: Allocator> { |
| 26 | + v: &'a mut Vec<T, A>, |
| 27 | + /// Start index of the current chunk within the Vec. |
| 28 | + read: usize, |
| 29 | + /// Write cursor (accumulated from previous chunks). |
| 30 | + write: usize, |
| 31 | + /// How many elements in the current chunk have been decided by the predicate. |
| 32 | + decided: usize, |
| 33 | + /// Pointer to the bool mask array for the current chunk. |
| 34 | + mask: &'a mut [bool; CHUNK_SIZE], |
| 35 | + /// Original length of the Vec. |
| 36 | + original_len: usize, |
| 37 | +} |
| 38 | + |
| 39 | +impl<T, A: Allocator> Drop for PanicGuard<'_, T, A> { |
| 40 | + #[cold] |
| 41 | + fn drop(&mut self) { |
| 42 | + // Scalar-compress the decided prefix: move kept elements to write position. |
| 43 | + let mut dst = self.write; |
| 44 | + for i in 0..self.decided { |
| 45 | + if self.mask[i] { |
| 46 | + // SAFETY: read + i < original_len (in-bounds). |
| 47 | + let src = unsafe { self.v.as_ptr().add(self.read + i) }; |
| 48 | + let dst_ptr = unsafe { self.v.as_mut_ptr().add(dst) }; |
| 49 | + // SAFETY: src and dst_ptr < original_len |
| 50 | + unsafe { ptr::copy(src, dst_ptr, 1) }; |
| 51 | + dst += 1; |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + // Copy the untouched tail. |
| 56 | + let untouched_start = self.read + self.decided; |
| 57 | + let untouched_len = self.original_len - untouched_start; |
| 58 | + if untouched_len > 0 { |
| 59 | + // SAFETY: untouched_start..original_len are valid; dst + untouched_len <= original_len. |
| 60 | + unsafe { |
| 61 | + ptr::copy( |
| 62 | + self.v.as_ptr().add(untouched_start), |
| 63 | + self.v.as_mut_ptr().add(dst), |
| 64 | + untouched_len, |
| 65 | + ); |
| 66 | + } |
| 67 | + dst += untouched_len; |
| 68 | + } |
| 69 | + |
| 70 | + // SAFETY: After filling holes, all items are in contiguous memory. |
| 71 | + unsafe { self.v.set_len(dst) }; |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +/// # Safety |
| 76 | +/// |
| 77 | +/// - `size_of::<T>() == 1/2/4/8` |
| 78 | +/// - `!needs_drop::<T>()` |
| 79 | +/// - Called on aarch64 + SVE target. |
| 80 | +pub(crate) unsafe fn chunked_retain<T, F, A: Allocator>(v: &mut Vec<T, A>, mut f: F) |
| 81 | +where |
| 82 | + F: FnMut(&mut T) -> bool, |
| 83 | +{ |
| 84 | + let data = v.as_mut_ptr(); |
| 85 | + let original_len = v.len(); |
| 86 | + let mut guard = PanicGuard { |
| 87 | + v, |
| 88 | + read: 0, |
| 89 | + write: 0, |
| 90 | + decided: 0, |
| 91 | + mask: &mut [false; CHUNK_SIZE], |
| 92 | + original_len, |
| 93 | + }; |
| 94 | + |
| 95 | + while guard.read < guard.original_len { |
| 96 | + let chunk_len = cmp::min(CHUNK_SIZE, guard.original_len - guard.read); |
| 97 | + |
| 98 | + guard.decided = 0; |
| 99 | + |
| 100 | + // Phase A: scalar predicate evaluation (exactly once, in order). |
| 101 | + for i in 0..chunk_len { |
| 102 | + // SAFETY: read + i < original_len |
| 103 | + let elem = unsafe { &mut *data.add(guard.read + i) }; |
| 104 | + guard.mask[i] = f(elem); |
| 105 | + guard.decided = i + 1; |
| 106 | + } |
| 107 | + |
| 108 | + // Phase B: SVE compress. |
| 109 | + // SAFETY: write <= read and the dispatch guarantees size_of::<T>() matches the kernel lane width. |
| 110 | + let kept = match mem::size_of::<T>() { |
| 111 | + 1 => unsafe { |
| 112 | + compact8_kernel( |
| 113 | + data.add(guard.read), |
| 114 | + data.add(guard.write), |
| 115 | + guard.mask.as_ptr(), |
| 116 | + chunk_len, |
| 117 | + ) |
| 118 | + }, |
| 119 | + 2 => unsafe { |
| 120 | + compact16_kernel( |
| 121 | + data.add(guard.read), |
| 122 | + data.add(guard.write), |
| 123 | + guard.mask.as_ptr(), |
| 124 | + chunk_len, |
| 125 | + ) |
| 126 | + }, |
| 127 | + 4 => unsafe { |
| 128 | + compact32_kernel( |
| 129 | + data.add(guard.read), |
| 130 | + data.add(guard.write), |
| 131 | + guard.mask.as_ptr(), |
| 132 | + chunk_len, |
| 133 | + ) |
| 134 | + }, |
| 135 | + 8 => unsafe { |
| 136 | + compact64_kernel( |
| 137 | + data.add(guard.read), |
| 138 | + data.add(guard.write), |
| 139 | + guard.mask.as_ptr(), |
| 140 | + chunk_len, |
| 141 | + ) |
| 142 | + }, |
| 143 | + _ => unreachable!(), |
| 144 | + }; |
| 145 | + |
| 146 | + guard.write += kept; |
| 147 | + guard.read += chunk_len; |
| 148 | + } |
| 149 | + |
| 150 | + // SAFETY: write <= original_len, all retained elements are packed at the front. |
| 151 | + unsafe { guard.v.set_len(guard.write) }; |
| 152 | + mem::forget(guard); |
| 153 | +} |
| 154 | + |
| 155 | +macro_rules! sve_compact_kernel { |
| 156 | + ( |
| 157 | + $name:ident, |
| 158 | + size = $size:literal, |
| 159 | + lane = $lane:literal, |
| 160 | + mem_lane = $mem_lane:literal, |
| 161 | + shift = $shift:literal, |
| 162 | + inc = $inc:literal |
| 163 | + ) => { |
| 164 | + /// SVE compress kernel: pack retained elements from `src` to `dst` |
| 165 | + /// according to `mask`. Returns the number of retained elements. |
| 166 | + /// |
| 167 | + /// # Safety |
| 168 | + /// |
| 169 | + /// - `src` is valid for `chunk_len` reads of `T`, `dst` for `chunk_len` |
| 170 | + /// writes, `mask` for `chunk_len` bool reads. |
| 171 | + /// - `size_of::<T>()` equals this kernel's lane width in bytes. |
| 172 | + #[target_feature(enable = "sve")] |
| 173 | + #[inline] |
| 174 | + unsafe fn $name<T>( |
| 175 | + src: *const T, |
| 176 | + dst: *mut T, |
| 177 | + mask: *const bool, |
| 178 | + chunk_len: usize, |
| 179 | + ) -> usize { |
| 180 | + debug_assert_eq!(mem::size_of::<T>(), $size); |
| 181 | + |
| 182 | + let idx_in = 0usize; |
| 183 | + let mut idx_out = 0usize; |
| 184 | + // SAFETY: whilelo predicates every load/store to the remaining elements. |
| 185 | + unsafe { |
| 186 | + core::arch::asm!( |
| 187 | + concat!("whilelo p0.", $lane, ", xzr, {len}"), |
| 188 | + "2:", |
| 189 | + concat!("ld1b {{ z0.", $lane, " }}, p0/z, [{mask}, {idx_in}]"), |
| 190 | + concat!("cmpne p1.", $lane, ", p0/z, z0.", $lane, ", #0"), |
| 191 | + concat!("ld1", $mem_lane, " {{ z1.", $lane, " }}, p0/z, [{src}, {idx_in}", $shift, "]"), |
| 192 | + concat!("compact z1.", $lane, ", p1, z1.", $lane), |
| 193 | + concat!("cntp {kept}, p0, p1.", $lane), |
| 194 | + concat!("whilelo p2.", $lane, ", xzr, {kept}"), |
| 195 | + concat!("st1", $mem_lane, " {{ z1.", $lane, " }}, p2, [{dst}, {idx_out}", $shift, "]"), |
| 196 | + concat!("add {idx_out}, {idx_out}, {kept}"), |
| 197 | + concat!("inc", $inc, " {idx_in}"), |
| 198 | + concat!("whilelo p0.", $lane, ", {idx_in}, {len}"), |
| 199 | + "b.first 2b", |
| 200 | + src = in(reg) src, |
| 201 | + dst = in(reg) dst, |
| 202 | + mask = in(reg) mask, |
| 203 | + len = in(reg) chunk_len, |
| 204 | + idx_in = inout(reg) idx_in => _, |
| 205 | + idx_out = inout(reg) idx_out, |
| 206 | + kept = out(reg) _, |
| 207 | + out("p0") _, |
| 208 | + out("p1") _, |
| 209 | + out("p2") _, |
| 210 | + out("z0") _, |
| 211 | + out("z1") _, |
| 212 | + options(nostack), |
| 213 | + ); |
| 214 | + } |
| 215 | + idx_out |
| 216 | + } |
| 217 | + }; |
| 218 | +} |
| 219 | + |
| 220 | +#[cfg(target_feature = "sve2p1")] |
| 221 | +sve_compact_kernel!(compact8_kernel, size = 1, lane = "b", mem_lane = "b", shift = "", inc = "b"); |
| 222 | + |
| 223 | +#[cfg(not(target_feature = "sve2p1"))] |
| 224 | +sve_compact_kernel!(compact8_kernel, size = 1, lane = "s", mem_lane = "b", shift = "", inc = "w"); |
| 225 | + |
| 226 | +#[cfg(target_feature = "sve2p1")] |
| 227 | +sve_compact_kernel!( |
| 228 | + compact16_kernel, |
| 229 | + size = 2, |
| 230 | + lane = "h", |
| 231 | + mem_lane = "h", |
| 232 | + shift = ", lsl #1", |
| 233 | + inc = "h" |
| 234 | +); |
| 235 | + |
| 236 | +#[cfg(not(target_feature = "sve2p1"))] |
| 237 | +sve_compact_kernel!( |
| 238 | + compact16_kernel, |
| 239 | + size = 2, |
| 240 | + lane = "s", |
| 241 | + mem_lane = "h", |
| 242 | + shift = ", lsl #1", |
| 243 | + inc = "w" |
| 244 | +); |
| 245 | + |
| 246 | +sve_compact_kernel!( |
| 247 | + compact32_kernel, |
| 248 | + size = 4, |
| 249 | + lane = "s", |
| 250 | + mem_lane = "w", |
| 251 | + shift = ", lsl #2", |
| 252 | + inc = "w" |
| 253 | +); |
| 254 | + |
| 255 | +sve_compact_kernel!( |
| 256 | + compact64_kernel, |
| 257 | + size = 8, |
| 258 | + lane = "d", |
| 259 | + mem_lane = "d", |
| 260 | + shift = ", lsl #3", |
| 261 | + inc = "d" |
| 262 | +); |
0 commit comments