[PATCH v4 1/3] rust: kvec: implement shrink_to for KVVec
From: Shivam Kalra via B4 Relay
Date: Thu Feb 12 2026 - 03:26:17 EST
From: Shivam Kalra <shivamkalra98@xxxxxxxxxxx>
Implement shrink_to method specifically for `KVVec` (i.e., `Vec<T, KVmalloc>`).
`shrink_to` reduces the vector's capacity to a specified minimum, but
only performs shrinking when it would free at least one page of memory.
This prevents unnecessary allocations while allowing reclamation of
unused memory.
The implementation uses explicit alloc+copy+free because `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@xxxxxxxxxx>
Suggested-by: Danilo Krummrich <dakr@xxxxxxxxxx>
Signed-off-by: Shivam Kalra <shivamkalra98@xxxxxxxxxxx>
---
rust/kernel/alloc/kvec.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index ac8d6f763ae81..8524f9f3dff0a 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -9,7 +9,7 @@
};
use crate::{
fmt,
- page::AsPageIter, //
+ page::{AsPageIter, PAGE_SIZE},
};
use core::{
borrow::{Borrow, BorrowMut},
@@ -734,6 +734,82 @@ 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.
+ ///
+ /// Shrinking only occurs if the operation would free at least one page of memory.
+ ///
+ /// # 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(());
+ }
+
+ // 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 allocated with `KVmalloc`, layout matches.
+ 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) };
+
+ // TODO: Once vrealloc supports in-place shrinking (mm/vmalloc.c:4316), this
+ // explicit alloc+copy+free can potentially be replaced with realloc.
+ let new_ptr = KVmalloc::alloc(new_layout.into(), flags, NumaNode::NO_NODE)?;
+
+ // SAFETY: Both pointers are valid, non-overlapping, and properly aligned.
+ unsafe {
+ ptr::copy_nonoverlapping(self.as_ptr(), new_ptr.as_ptr().cast::<T>(), self.len);
+ }
+
+ // SAFETY: `self.ptr` was allocated with `KVmalloc`, layout matches.
+ unsafe { KVmalloc::free(self.ptr.cast(), self.layout.into()) };
+
+ // SAFETY: `new_ptr` is non-null because `KVmalloc::alloc` succeeded.
+ self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr().cast::<T>()) };
+ self.layout = new_layout;
+
+ Ok(())
+ }
+}
impl<T: Clone, A: Allocator> Vec<T, A> {
/// Extend the vector by `n` clones of `value`.
--
2.43.0