[PATCH v5 1/3] rust: kvec: implement shrink_to for KVVec
From: Shivam Kalra via B4 Relay
Date: Sat Feb 14 2026 - 15:36:10 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.
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@xxxxxxxxxx>
Suggested-by: Danilo Krummrich <dakr@xxxxxxxxxx>
Signed-off-by: Shivam Kalra <shivamkalra98@xxxxxxxxxxx>
---
rust/kernel/alloc/kvec.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 113 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
index ac8d6f763ae81..e7bc439538e49 100644
--- a/rust/kernel/alloc/kvec.rs
+++ b/rust/kernel/alloc/kvec.rs
@@ -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`.
--
2.43.0