From 47ac2a4b5cd8f0cb826f6368c2fc0eeb97e5d55f Mon Sep 17 00:00:00 2001 From: Shivam Kalra Date: Mon, 16 Feb 2026 19:39:55 +0530 Subject: rust: kvec: implement shrink_to for KVVec Implement shrink_to method specifically for `KVVec` (i.e., `Vec`). `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 Suggested-by: Danilo Krummrich Reviewed-by: Alice Ryhl Acked-by: Danilo Krummrich Signed-off-by: Shivam Kalra Link: https://patch.msgid.link/20260216-binder-shrink-vec-v3-v6-1-ece8e8593e53@zohomail.in Signed-off-by: Greg Kroah-Hartman --- rust/kernel/alloc/kvec.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) (limited to 'rust/kernel/alloc') diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index ac8d6f763ae8..e7bc439538e4 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -9,7 +9,10 @@ use super::{ }; use crate::{ fmt, - page::AsPageIter, // + page::{ + AsPageIter, + PAGE_SIZE, // + }, }; use core::{ borrow::{Borrow, BorrowMut}, @@ -734,6 +737,115 @@ where self.truncate(num_kept); } } +// TODO: This is a temporary KVVec-specific implementation. It should be replaced with a generic +// `shrink_to()` for `impl Vec` that uses `A::realloc()` once the +// underlying allocators properly support shrinking via realloc. +impl Vec { + /// 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::(); + /// 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::::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::(); + let target_size = target_cap * core::mem::size_of::(); + 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::::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::(), 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::(); + self.layout = new_layout; + + Ok(()) + } +} impl Vec { /// Extend the vector by `n` clones of `value`. -- cgit v1.2.3 From fbfc0d615368ddf71899dbea2205e741c79b23e8 Mon Sep 17 00:00:00 2001 From: Shivam Kalra Date: Mon, 16 Feb 2026 19:39:56 +0530 Subject: rust: alloc: add KUnit tests for KVVec shrink_to Add comprehensive KUnit tests for the shrink_to method for KVVec. The tests verify: - Basic shrinking from multiple pages to fewer pages with data integrity preservation - Empty vector shrinking to zero capacity - No-op behavior when shrinking to a larger capacity than current - Respect for min_capacity parameter when larger than vector length These tests ensure that the shrinking logic correctly identifies when memory can be reclaimed (by freeing at least one page) and that data integrity is maintained throughout shrink operations. Reviewed-by: Alice Ryhl Acked-by: Danilo Krummrich Signed-off-by: Shivam Kalra Link: https://patch.msgid.link/20260216-binder-shrink-vec-v3-v6-2-ece8e8593e53@zohomail.in Signed-off-by: Greg Kroah-Hartman --- rust/kernel/alloc/kvec.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) (limited to 'rust/kernel/alloc') diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs index e7bc439538e4..6438385e4322 100644 --- a/rust/kernel/alloc/kvec.rs +++ b/rust/kernel/alloc/kvec.rs @@ -1510,4 +1510,106 @@ mod tests { func.push_within_capacity(false).unwrap(); } } + + #[test] + fn test_kvvec_shrink_to() { + use crate::page::PAGE_SIZE; + + // Create a vector with capacity spanning multiple pages. + let mut v = KVVec::::with_capacity(PAGE_SIZE * 4, GFP_KERNEL).unwrap(); + + // Add a few elements. + v.push(1, GFP_KERNEL).unwrap(); + v.push(2, GFP_KERNEL).unwrap(); + v.push(3, GFP_KERNEL).unwrap(); + + let initial_capacity = v.capacity(); + assert!(initial_capacity >= PAGE_SIZE * 4); + + // Shrink to a capacity that would free at least one page. + v.shrink_to(PAGE_SIZE, GFP_KERNEL).unwrap(); + + // Capacity should have been reduced. + assert!(v.capacity() < initial_capacity); + assert!(v.capacity() >= PAGE_SIZE); + + // Elements should be preserved. + assert_eq!(v.len(), 3); + assert_eq!(v[0], 1); + assert_eq!(v[1], 2); + assert_eq!(v[2], 3); + + // Shrink to zero (should shrink to len). + v.shrink_to(0, GFP_KERNEL).unwrap(); + + // Capacity should be at least the length. + assert!(v.capacity() >= v.len()); + + // Elements should still be preserved. + assert_eq!(v.len(), 3); + assert_eq!(v[0], 1); + assert_eq!(v[1], 2); + assert_eq!(v[2], 3); + } + + #[test] + fn test_kvvec_shrink_to_empty() { + use crate::page::PAGE_SIZE; + + // Create a vector with large capacity but no elements. + let mut v = KVVec::::with_capacity(PAGE_SIZE * 4, GFP_KERNEL).unwrap(); + + assert!(v.is_empty()); + + // Shrink empty vector to zero. + v.shrink_to(0, GFP_KERNEL).unwrap(); + + // Should have freed the allocation. + assert_eq!(v.capacity(), 0); + assert!(v.is_empty()); + } + + #[test] + fn test_kvvec_shrink_to_no_op() { + use crate::page::PAGE_SIZE; + + // Create a small vector. + let mut v = KVVec::::with_capacity(PAGE_SIZE, GFP_KERNEL).unwrap(); + v.push(1, GFP_KERNEL).unwrap(); + + let capacity_before = v.capacity(); + + // Try to shrink to a capacity larger than current - should be no-op. + v.shrink_to(capacity_before + 100, GFP_KERNEL).unwrap(); + + assert_eq!(v.capacity(), capacity_before); + assert_eq!(v.len(), 1); + assert_eq!(v[0], 1); + } + + #[test] + fn test_kvvec_shrink_to_respects_min_capacity() { + use crate::page::PAGE_SIZE; + + // Create a vector with large capacity. + let mut v = KVVec::::with_capacity(PAGE_SIZE * 4, GFP_KERNEL).unwrap(); + + // Add some elements. + for i in 0..10u8 { + v.push(i, GFP_KERNEL).unwrap(); + } + + // Shrink to a min_capacity larger than length. + let min_cap = PAGE_SIZE * 2; + v.shrink_to(min_cap, GFP_KERNEL).unwrap(); + + // Capacity should be at least min_capacity. + assert!(v.capacity() >= min_cap); + + // All elements preserved. + assert_eq!(v.len(), 10); + for i in 0..10u8 { + assert_eq!(v[i as usize], i); + } + } } -- cgit v1.2.3 From 4f13c93497e366cd8e41561a8e30ad4da887cb82 Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Thu, 26 Mar 2026 12:04:06 +1000 Subject: rust: kernel: mark as `#[inline]` all `From::from()`s for `Error` There was a recent request [1] to mark as `#[inline]` the simple `From::from()` functions implemented for `Error`. Thus mark all of the existing impl From<...> for Error { fn from(err: ...) -> Self { ... } } functions in the `kernel` crate as `#[inline]`. Suggested-by: Gary Guo Link: https://lore.kernel.org/all/8403c8b7a832b5274743816eb77abfa4@garyguo.net/ [1] Signed-off-by: Alistair Francis Acked-by: Danilo Krummrich Acked-by: Andreas Hindborg Link: https://patch.msgid.link/20260326020406.1438210-1-alistair.francis@wdc.com [ Dropped `projection.rs` since it is in another tree and already marked as `inline(always)` and reworded accordingly. Changed Link tag to Gary's original message and added Suggested-by. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/alloc/kvec/errors.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rust/kernel/alloc') diff --git a/rust/kernel/alloc/kvec/errors.rs b/rust/kernel/alloc/kvec/errors.rs index e7de5049ee47..985c5f2c3962 100644 --- a/rust/kernel/alloc/kvec/errors.rs +++ b/rust/kernel/alloc/kvec/errors.rs @@ -15,6 +15,7 @@ impl fmt::Debug for PushError { } impl From> for Error { + #[inline] fn from(_: PushError) -> Error { // Returning ENOMEM isn't appropriate because the system is not out of memory. The vector // is just full and we are refusing to resize it. @@ -32,6 +33,7 @@ impl fmt::Debug for RemoveError { } impl From for Error { + #[inline] fn from(_: RemoveError) -> Error { EINVAL } @@ -55,6 +57,7 @@ impl fmt::Debug for InsertError { } impl From> for Error { + #[inline] fn from(_: InsertError) -> Error { EINVAL } -- cgit v1.2.3 From 4ab22c543f18cfbcc2f8ae691dc5ec5cc0ac35fb Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Mon, 6 Apr 2026 01:52:46 +0200 Subject: rust: remove `RUSTC_HAS_COERCE_POINTEE` and simplify code With the Rust version bump in place, the `RUSTC_HAS_COERCE_POINTEE` Kconfig (automatic) option is always true. Thus remove the option and simplify the code. In particular, this includes removing our use of the predecessor unstable features we used with Rust < 1.84.0 (`coerce_unsized`, `dispatch_from_dyn` and `unsize`). Reviewed-by: Tamir Duberstein Acked-by: Danilo Krummrich Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260405235309.418950-11-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/kernel/alloc/kbox.rs | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) (limited to 'rust/kernel/alloc') diff --git a/rust/kernel/alloc/kbox.rs b/rust/kernel/alloc/kbox.rs index 622b3529edfc..bd6da02c7ab8 100644 --- a/rust/kernel/alloc/kbox.rs +++ b/rust/kernel/alloc/kbox.rs @@ -77,33 +77,8 @@ use pin_init::{InPlaceWrite, Init, PinInit, ZeroableOption}; /// `self.0` is always properly aligned and either points to memory allocated with `A` or, for /// zero-sized types, is a dangling, well aligned pointer. #[repr(transparent)] -#[cfg_attr(CONFIG_RUSTC_HAS_COERCE_POINTEE, derive(core::marker::CoercePointee))] -pub struct Box<#[cfg_attr(CONFIG_RUSTC_HAS_COERCE_POINTEE, pointee)] T: ?Sized, A: Allocator>( - NonNull, - PhantomData, -); - -// This is to allow coercion from `Box` to `Box` if `T` can be converted to the -// dynamically-sized type (DST) `U`. -#[cfg(not(CONFIG_RUSTC_HAS_COERCE_POINTEE))] -impl core::ops::CoerceUnsized> for Box -where - T: ?Sized + core::marker::Unsize, - U: ?Sized, - A: Allocator, -{ -} - -// This is to allow `Box` to be dispatched on when `Box` can be coerced into `Box`. -#[cfg(not(CONFIG_RUSTC_HAS_COERCE_POINTEE))] -impl core::ops::DispatchFromDyn> for Box -where - T: ?Sized + core::marker::Unsize, - U: ?Sized, - A: Allocator, -{ -} +#[derive(core::marker::CoercePointee)] +pub struct Box<#[pointee] T: ?Sized, A: Allocator>(NonNull, PhantomData); /// Type alias for [`Box`] with a [`Kmalloc`] allocator. /// -- cgit v1.2.3 From 161dd7b51e9699a5d1bb2cb24a7eaca23e0facad Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Mon, 6 Apr 2026 01:52:50 +0200 Subject: rust: alloc: simplify with `NonNull::add()` now that it is stable Currently, we need to go through raw pointers and then re-create the `NonNull` from the result of offsetting the raw pointer. `feature(non_null_convenience)` [1] has been stabilized in Rust 1.80.0 [2], which is older than our new minimum Rust version (Rust 1.85.0). Thus, now that we bump the Rust minimum version, simplify using `NonNull::add()` and clean the TODO note. Link: https://github.com/rust-lang/rust/issues/117691 [1] Link: https://github.com/rust-lang/rust/pull/124498 [2] Reviewed-by: Tamir Duberstein Reviewed-by: Gary Guo Acked-by: Danilo Krummrich Link: https://patch.msgid.link/20260405235309.418950-15-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/kernel/alloc/allocator/iter.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'rust/kernel/alloc') diff --git a/rust/kernel/alloc/allocator/iter.rs b/rust/kernel/alloc/allocator/iter.rs index 5759f86029b7..e0a70b7a744a 100644 --- a/rust/kernel/alloc/allocator/iter.rs +++ b/rust/kernel/alloc/allocator/iter.rs @@ -42,15 +42,9 @@ impl<'a> Iterator for VmallocPageIter<'a> { return None; } - // TODO: Use `NonNull::add()` instead, once the minimum supported compiler version is - // bumped to 1.80 or later. - // // SAFETY: `offset` is in the interval `[0, (self.page_count() - 1) * page::PAGE_SIZE]`, // hence the resulting pointer is guaranteed to be within the same allocation. - let ptr = unsafe { self.buf.as_ptr().add(offset) }; - - // SAFETY: `ptr` is guaranteed to be non-null given that it is derived from `self.buf`. - let ptr = unsafe { NonNull::new_unchecked(ptr) }; + let ptr = unsafe { self.buf.add(offset) }; // SAFETY: // - `ptr` is a valid pointer to a `Vmalloc` allocation. -- cgit v1.2.3