rust: kvec: implement shrink_to for KVVec

Implement shrink_to method specifically for `KVVec` (i.e.,
`Vec<T, KVmalloc>`). `shrink_to` reduces the vector's capacity to a
specified minimum.

For kmalloc-backed allocations, the method delegates to realloc(),
letting the allocator decide whether shrinking is worthwhile. For
vmalloc-backed allocations (detected via is_vmalloc_addr), shrinking
only occurs if at least one page of memory can be freed, using an
explicit alloc+copy+free since vrealloc does not yet support in-place
shrinking.

A TODO note marks this for future replacement with a generic shrink_to
for all allocators that uses A::realloc() once the underlying allocators
properly support shrinking via realloc.

Suggested-by: Alice Ryhl <aliceryhl@google.com>
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Shivam Kalra <shivamkalra98@zohomail.in>
Link: https://patch.msgid.link/20260216-binder-shrink-vec-v3-v6-1-ece8e8593e53@zohomail.in
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Shivam Kalra
2026-02-16 19:39:55 +05:30
committed by Greg Kroah-Hartman
parent d31ed22a06
commit 47ac2a4b5c

View File

@@ -9,7 +9,10 @@
};
use crate::{
fmt,
page::AsPageIter, //
page::{
AsPageIter,
PAGE_SIZE, //
},
};
use core::{
borrow::{Borrow, BorrowMut},
@@ -734,6 +737,115 @@ pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
self.truncate(num_kept);
}
}
// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic
// `shrink_to()` for `impl<T, A: Allocator> Vec<T, A>` that uses `A::realloc()` once the
// underlying allocators properly support shrinking via realloc.
impl<T> Vec<T, KVmalloc> {
/// Shrinks the capacity of the vector with a lower bound.
///
/// The capacity will remain at least as large as both the length and the supplied value.
/// If the current capacity is less than the lower limit, this is a no-op.
///
/// For `kmalloc` allocations, this delegates to `realloc()`, which decides whether
/// shrinking is worthwhile. For `vmalloc` allocations, shrinking only occurs if the
/// operation would free at least one page of memory, and performs a deep copy since
/// `vrealloc` does not yet support in-place shrinking.
///
/// # Examples
///
/// ```
/// // Allocate enough capacity to span multiple pages.
/// let elements_per_page = kernel::page::PAGE_SIZE / core::mem::size_of::<u32>();
/// let mut v = KVVec::with_capacity(elements_per_page * 4, GFP_KERNEL)?;
/// v.push(1, GFP_KERNEL)?;
/// v.push(2, GFP_KERNEL)?;
///
/// v.shrink_to(0, GFP_KERNEL)?;
/// # Ok::<(), Error>(())
/// ```
pub fn shrink_to(&mut self, min_capacity: usize, flags: Flags) -> Result<(), AllocError> {
let target_cap = core::cmp::max(self.len(), min_capacity);
if self.capacity() <= target_cap {
return Ok(());
}
if Self::is_zst() {
return Ok(());
}
// For kmalloc allocations, delegate to realloc() and let the allocator decide
// whether shrinking is worthwhile.
//
// SAFETY: `self.ptr` points to a valid `KVmalloc` allocation.
if !unsafe { bindings::is_vmalloc_addr(self.ptr.as_ptr().cast()) } {
let new_layout = ArrayLayout::<T>::new(target_cap).map_err(|_| AllocError)?;
// SAFETY:
// - `self.ptr` is valid and was previously allocated with `KVmalloc`.
// - `self.layout` matches the `ArrayLayout` of the preceding allocation.
let ptr = unsafe {
KVmalloc::realloc(
Some(self.ptr.cast()),
new_layout.into(),
self.layout.into(),
flags,
NumaNode::NO_NODE,
)?
};
self.ptr = ptr.cast();
self.layout = new_layout;
return Ok(());
}
// Only shrink if we would free at least one page.
let current_size = self.capacity() * core::mem::size_of::<T>();
let target_size = target_cap * core::mem::size_of::<T>();
let current_pages = current_size.div_ceil(PAGE_SIZE);
let target_pages = target_size.div_ceil(PAGE_SIZE);
if current_pages <= target_pages {
return Ok(());
}
if target_cap == 0 {
if !self.layout.is_empty() {
// SAFETY:
// - `self.ptr` was previously allocated with `KVmalloc`.
// - `self.layout` matches the `ArrayLayout` of the preceding allocation.
unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
}
self.ptr = NonNull::dangling();
self.layout = ArrayLayout::empty();
return Ok(());
}
// SAFETY: `target_cap <= self.capacity()` and original capacity was valid.
let new_layout = unsafe { ArrayLayout::<T>::new_unchecked(target_cap) };
let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
// SAFETY:
// - `self.as_ptr()` is valid for reads of `self.len()` elements of `T`.
// - `new_ptr` is valid for writes of at least `target_cap >= self.len()` elements.
// - The two allocations do not overlap since `new_ptr` is freshly allocated.
// - Both pointers are properly aligned for `T`.
unsafe {
ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len())
};
// SAFETY:
// - `self.ptr` was previously allocated with `KVmalloc`.
// - `self.layout` matches the `ArrayLayout` of the preceding allocation.
unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
self.ptr = new_ptr.cast::<T>();
self.layout = new_layout;
Ok(())
}
}
impl<T: Clone, A: Allocator> Vec<T, A> {
/// Extend the vector by `n` clones of `value`.