Skip to content

Commit 65d9662

Browse files
committed
feat(allocator): add Vec2::retain_mut method (#9655)
OXC has a few places using this API, so we need to add this method before replacing allocator-api2's `Vec`. The implementation is copied from the https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#2121-2123, and modified a little to make it fit the `Vec2`.
1 parent a61a50b commit 65d9662

File tree

1 file changed

+121
-0
lines changed
  • crates/oxc_allocator/src/vec2

1 file changed

+121
-0
lines changed

crates/oxc_allocator/src/vec2/mod.rs

+121
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,127 @@ impl<'bump, T: 'bump> Vec<'bump, T> {
13191319
self.drain_filter(|x| !f(x));
13201320
}
13211321

1322+
/// Retains only the elements specified by the predicate, passing a mutable reference to it.
1323+
///
1324+
/// In other words, remove all elements `e` such that `f(&mut e)` returns `false`.
1325+
/// This method operates in place, visiting each element exactly once in the
1326+
/// original order, and preserves the order of the retained elements.
1327+
///
1328+
/// # Examples
1329+
///
1330+
/// ```
1331+
/// let mut vec = vec![1, 2, 3, 4];
1332+
/// vec.retain_mut(|x| if *x <= 3 {
1333+
/// *x += 1;
1334+
/// true
1335+
/// } else {
1336+
/// false
1337+
/// });
1338+
/// assert_eq!(vec, [2, 3, 4]);
1339+
/// ```
1340+
// The implementation is based on the [`std::vec::Vec::retain_mut`].
1341+
//
1342+
// Allowing the following clippy rules just to make the code same as the original implementation.
1343+
#[expect(clippy::items_after_statements, clippy::redundant_else)]
1344+
pub fn retain_mut<F>(&mut self, mut f: F)
1345+
where
1346+
F: FnMut(&mut T) -> bool,
1347+
{
1348+
let original_len = self.len();
1349+
1350+
if original_len == 0 {
1351+
// Empty case: explicit return allows better optimization, vs letting compiler infer it
1352+
return;
1353+
}
1354+
1355+
// Avoid double drop if the drop guard is not executed,
1356+
// since we may make some holes during the process.
1357+
unsafe { self.set_len(0) };
1358+
1359+
// Vec: [Kept, Kept, Hole, Hole, Hole, Hole, Unchecked, Unchecked]
1360+
// |<- processed len ->| ^- next to check
1361+
// |<- deleted cnt ->|
1362+
// |<- original_len ->|
1363+
// Kept: Elements which predicate returns true on.
1364+
// Hole: Moved or dropped element slot.
1365+
// Unchecked: Unchecked valid elements.
1366+
//
1367+
// This drop guard will be invoked when predicate or `drop` of element panicked.
1368+
// It shifts unchecked elements to cover holes and `set_len` to the correct length.
1369+
// In cases when predicate and `drop` never panick, it will be optimized out.
1370+
struct BackshiftOnDrop<'bump, 'a, T> {
1371+
v: &'a mut Vec<'bump, T>,
1372+
processed_len: usize,
1373+
deleted_cnt: usize,
1374+
original_len: usize,
1375+
}
1376+
1377+
impl<T> Drop for BackshiftOnDrop<'_, '_, T> {
1378+
fn drop(&mut self) {
1379+
if self.deleted_cnt > 0 {
1380+
// SAFETY: Trailing unchecked items must be valid since we never touch them.
1381+
unsafe {
1382+
ptr::copy(
1383+
self.v.as_ptr().add(self.processed_len),
1384+
self.v.as_mut_ptr().add(self.processed_len - self.deleted_cnt),
1385+
self.original_len - self.processed_len,
1386+
);
1387+
}
1388+
}
1389+
// SAFETY: After filling holes, all items are in contiguous memory.
1390+
unsafe {
1391+
self.v.set_len(self.original_len - self.deleted_cnt);
1392+
}
1393+
}
1394+
}
1395+
1396+
let mut g = BackshiftOnDrop { v: self, processed_len: 0, deleted_cnt: 0, original_len };
1397+
1398+
fn process_loop<F, T, const DELETED: bool>(
1399+
original_len: usize,
1400+
f: &mut F,
1401+
g: &mut BackshiftOnDrop<'_, '_, T>,
1402+
) where
1403+
F: FnMut(&mut T) -> bool,
1404+
{
1405+
while g.processed_len != original_len {
1406+
// SAFETY: Unchecked element must be valid.
1407+
let cur = unsafe { &mut *g.v.as_mut_ptr().add(g.processed_len) };
1408+
if !f(cur) {
1409+
// Advance early to avoid double drop if `drop_in_place` panicked.
1410+
g.processed_len += 1;
1411+
g.deleted_cnt += 1;
1412+
// SAFETY: We never touch this element again after dropped.
1413+
unsafe { ptr::drop_in_place(cur) };
1414+
// We already advanced the counter.
1415+
if DELETED {
1416+
continue;
1417+
} else {
1418+
break;
1419+
}
1420+
}
1421+
if DELETED {
1422+
// SAFETY: `deleted_cnt` > 0, so the hole slot must not overlap with current element.
1423+
// We use copy for move, and never touch this element again.
1424+
unsafe {
1425+
let hole_slot = g.v.as_mut_ptr().add(g.processed_len - g.deleted_cnt);
1426+
ptr::copy_nonoverlapping(cur, hole_slot, 1);
1427+
}
1428+
}
1429+
g.processed_len += 1;
1430+
}
1431+
}
1432+
1433+
// Stage 1: Nothing was deleted.
1434+
process_loop::<F, T, false>(original_len, &mut f, &mut g);
1435+
1436+
// Stage 2: Some elements were deleted.
1437+
process_loop::<F, T, true>(original_len, &mut f, &mut g);
1438+
1439+
// All item are processed. This can be optimized to `set_len` by LLVM.
1440+
drop(g);
1441+
}
1442+
13221443
/// Creates an iterator that removes the elements in the vector
13231444
/// for which the predicate returns `true` and yields the removed items.
13241445
///

0 commit comments

Comments
 (0)