blob: 85d86ae127b0d9a4a2b1e662b1eb02b39f3fa474 [file]
//! 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"
);