Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`.
  • Loading branch information
Dunqing committed Mar 13, 2025
commit 65d966276f349d229b628b734ec3eb0e6dfa46ca
121 changes: 121 additions & 0 deletions crates/oxc_allocator/src/vec2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,127 @@ impl<'bump, T: 'bump> Vec<'bump, T> {
self.drain_filter(|x| !f(x));
}

/// Retains only the elements specified by the predicate, passing a mutable reference to it.
///
/// In other words, remove all elements `e` such that `f(&mut e)` returns `false`.
/// This method operates in place, visiting each element exactly once in the
/// original order, and preserves the order of the retained elements.
///
/// # Examples
///
/// ```
/// let mut vec = vec![1, 2, 3, 4];
/// vec.retain_mut(|x| if *x <= 3 {
/// *x += 1;
/// true
/// } else {
/// false
/// });
/// assert_eq!(vec, [2, 3, 4]);
/// ```
// The implementation is based on the [`std::vec::Vec::retain_mut`].
//
// Allowing the following clippy rules just to make the code same as the original implementation.
#[expect(clippy::items_after_statements, clippy::redundant_else)]
pub fn retain_mut<F>(&mut self, mut f: F)
where
F: FnMut(&mut T) -> bool,
{
let original_len = self.len();

if original_len == 0 {
// Empty case: explicit return allows better optimization, vs letting compiler infer it
return;
}

// Avoid double drop if the drop guard is not executed,
// since we may make some holes during the process.
unsafe { self.set_len(0) };

// Vec: [Kept, Kept, Hole, Hole, Hole, Hole, Unchecked, Unchecked]
// |<- processed len ->| ^- next to check
// |<- deleted cnt ->|
// |<- original_len ->|
// Kept: Elements which predicate returns true on.
// Hole: Moved or dropped element slot.
// Unchecked: Unchecked valid elements.
//
// This drop guard will be invoked when predicate or `drop` of element panicked.
// It shifts unchecked elements to cover holes and `set_len` to the correct length.
// In cases when predicate and `drop` never panick, it will be optimized out.
struct BackshiftOnDrop<'bump, 'a, T> {
v: &'a mut Vec<'bump, T>,
processed_len: usize,
deleted_cnt: usize,
original_len: usize,
}

impl<T> Drop for BackshiftOnDrop<'_, '_, T> {
fn drop(&mut self) {
if self.deleted_cnt > 0 {
// SAFETY: Trailing unchecked items must be valid since we never touch them.
unsafe {
ptr::copy(
self.v.as_ptr().add(self.processed_len),
self.v.as_mut_ptr().add(self.processed_len - self.deleted_cnt),
self.original_len - self.processed_len,
);
}
}
// SAFETY: After filling holes, all items are in contiguous memory.
unsafe {
self.v.set_len(self.original_len - self.deleted_cnt);
}
}
}

let mut g = BackshiftOnDrop { v: self, processed_len: 0, deleted_cnt: 0, original_len };

fn process_loop<F, T, const DELETED: bool>(
original_len: usize,
f: &mut F,
g: &mut BackshiftOnDrop<'_, '_, T>,
) where
F: FnMut(&mut T) -> bool,
{
while g.processed_len != original_len {
// SAFETY: Unchecked element must be valid.
let cur = unsafe { &mut *g.v.as_mut_ptr().add(g.processed_len) };
if !f(cur) {
// Advance early to avoid double drop if `drop_in_place` panicked.
g.processed_len += 1;
g.deleted_cnt += 1;
// SAFETY: We never touch this element again after dropped.
unsafe { ptr::drop_in_place(cur) };
// We already advanced the counter.
if DELETED {
continue;
} else {
break;
}
}
if DELETED {
// SAFETY: `deleted_cnt` > 0, so the hole slot must not overlap with current element.
// We use copy for move, and never touch this element again.
unsafe {
let hole_slot = g.v.as_mut_ptr().add(g.processed_len - g.deleted_cnt);
ptr::copy_nonoverlapping(cur, hole_slot, 1);
}
}
g.processed_len += 1;
}
}

// Stage 1: Nothing was deleted.
process_loop::<F, T, false>(original_len, &mut f, &mut g);

// Stage 2: Some elements were deleted.
process_loop::<F, T, true>(original_len, &mut f, &mut g);

// All item are processed. This can be optimized to `set_len` by LLVM.
drop(g);
}

/// Creates an iterator that removes the elements in the vector
/// for which the predicate returns `true` and yields the removed items.
///
Expand Down
Loading