Skip to content

Commit bfdfd18

Browse files
committed
Add SVE-accelerated Vec::retain_mut for aarch64
1 parent 34baba5 commit bfdfd18

4 files changed

Lines changed: 417 additions & 0 deletions

File tree

library/alloc/src/vec/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ use self::spec_extend::SpecExtend;
170170
#[cfg(not(no_global_oom_handling))]
171171
mod spec_extend;
172172

173+
#[cfg(all(target_arch = "aarch64", target_feature = "sve"))]
174+
mod sve_retain;
175+
173176
/// A contiguous growable array type, written as `Vec<T>`, short for 'vector'.
174177
///
175178
/// # Examples
@@ -2515,6 +2518,22 @@ impl<T, A: Allocator> Vec<T, A> {
25152518
return;
25162519
}
25172520

2521+
#[cfg(all(target_arch = "aarch64", target_feature = "sve"))]
2522+
{
2523+
let long_enough = match mem::size_of::<T>() {
2524+
1 => original_len >= sve_retain::MIN_SVE_SIZE_1,
2525+
2 => original_len >= sve_retain::MIN_SVE_SIZE_2,
2526+
4 => original_len >= sve_retain::MIN_SVE_SIZE_4,
2527+
8 => original_len >= sve_retain::MIN_SVE_SIZE_8,
2528+
_ => false,
2529+
};
2530+
if long_enough && !mem::needs_drop::<T>() {
2531+
// SAFETY: size_of::<T>() is 1, 2, 4 or 8, matching
2532+
// the kernel lane widths.
2533+
return unsafe { sve_retain::chunked_retain(self, f) };
2534+
}
2535+
}
2536+
25182537
// Vec: [Kept, Kept, Hole, Hole, Hole, Hole, Unchecked, Unchecked]
25192538
// | ^- write ^- read |
25202539
// |<- original_len ->|
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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+
);

library/alloctests/benches/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![cfg(not(miri))]
33
#![allow(internal_features)]
44
#![feature(iter_next_chunk)]
5+
#![feature(macro_metavar_expr_concat)]
56
#![feature(repr_simd)]
67
#![feature(slice_partition_dedup)]
78
#![feature(strict_provenance_lints)]

0 commit comments

Comments
 (0)