Generic `Allocator` support for Rust
Subsystems (9)
Danilo Krummrich
maintainerv7 cover Generic `Allocator` support for Rust
Hi,
This patch series adds generic kernel allocator support for Rust, which so far is limited to kmalloc allocations.
In order to abstain from (re-)adding unstable Rust features to the kernel, this patch series does not extend the Allocator trait from Rust's alloc crate, nor does it extend the BoxExt and VecExt extensions.
Instead, this series introduces a kernel specific Allocator trait, which is implemented by the Kmalloc, Vmalloc and KVmalloc allocators, also implemented in the context of this series.
As a consequence we need our own kernel Box<T, A> and Vec<T, A> types. Additionally, this series adds the following type aliases:
``` pub type KBox<T> = Box<T, Kmalloc>; pub type VBox<T> = Box<T, Vmalloc>; pub type KVBox<T> = Box<T, KVmalloc>; pub type KVec<T> = Vec<T, Kmalloc>; pub type VVec<T> = Vec<T, Vmalloc>; pub type KVVec<T> = Vec<T, KVmalloc>; ```
With that, we can start using the kernel Box and Vec types throughout the tree and remove the now obolete extensions BoxExt and VecExt.
For a final cleanup, this series removes the last minor dependencies to Rust's alloc crate and removes it from the entire kernel build.
The series ensures not to break the rusttest make target by implementing the allocator_test module providing a stub implementation for all kernel Allocators.
This patch series passes all KUnit tests, including the ones added by this series. Additionally, the tests were run with kmemleak and KASAN enabled, without any issues.
This series is based on [1], which hit -mm/mm-stable, and is also available in [2].
[1] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=mm/krealloc
[2] https://git.kernel.org/pub/scm/linux/kernel/git/dakr/linux.git/log/?h=rust/mm
Changes in v7:
- rebase onto rust-next resolving recent conflicts (93dc3be19450447a3a7090bd1dfb9f3daac3e8d2)
- documentation / safety comment changes suggested by Benno and Boqun
- rename ptr to current in IntoIter::next (Alice)
- remove unnecessary braces in kvec! (Benno)
- add debug_assert! in Vec::set_len (Benno)
- remove unused args in impl_slice_eq! (Benno)
- simplify if statement in Cmalloc::realloc (Benno)
Changes in v6:
- rebase onto rust-dev
- keep compiler annotations for {k,v,kv}realloc()
- documentation changes suggested by Alice, Benno
- remove Box::into_pin
- fix typo in Send and Sync for Box and Vec
- kvec! changes suggested by Alice
- free src after copy in Cmalloc
- handle n == 0 in Vec::extend_with
Changes in v5:
- (safety) comment / documentation fixes suggested by Alice, Benno and Gary
- remove Unique<T> and implement Send and Sync for Box and Vec
- use KMALLOC_MAX_SIZE for KVmalloc test and add a Kmalloc test that expects to fail for KMALLOC_MAX_SIZE
- create use constants KREALLOC, VREALLOC and KVREALLOC for ReallocFuncs
- drop Box::drop_contents for now, will add it again, once I actually rebase on the original patch that introduces it
- improve usage of size_of_val in Box
- move InPlaceInit and ForeignOwnable impls into kbox.rs
- fix missing Box conversions in rnull.rs
- reworked Cmalloc to keep track of the size of memory allocations itself
- remove GlobalAlloc together with the alloc crate to avoid a linker error
- remove alloc from scripts/generate_rust_analyzer.py
Changes in v4:
- (safety) comment fixes suggested by Alice and Boqun
- remove Box::from_raw_alloc and Box::into_raw_alloc, we don't need them
- in Box::drop call size_of_val before drop_in_place
- implement ForeignOwnable for Pin<Box<T>> as suggested by Alice
- in Vec::extend_with, iterate over n instead of spare.len()
- for Vmalloc and KVmalloc fail allocation for alignments larger than PAGE_SIZE for now (will add support for larger alignments in a separate series)
- implement Cmalloc in allocator_test and type alias all kernel allocator types to it, such that we can use the kernel's Box and Vec types in userspace tests (rusttest)
- this makes patch "rust: str: test: replace alloc::format" rather trivial
Changes in v3:
- Box:
- minor documentation fixes
- removed unnecessary imports in doc tests
- dropeed self argument from some remaining Box methods
- implement InPlaceInit for Box<T, A> rather than specifically for KBox<T>
- Vec:
- minor documentation fixes
- removed useless Vec::allocator method
- in Vec::extend_with use Vec::spare_capacity_mut instead of raw pointer operations
- added a few missing safety comments
- pass GFP flags to Vec::collect
- fixed a rustdoc warning in alloc.rs
- fixed the allocator_test module to implement the Allocator trait correctly
- rebased to rust-next
Changes in v2:
- preserve impl GlobalAlloc for Kmalloc and remove it at the end (Benno)
- remove &self parameter from all Allocator functions (Benno)
- various documentation fixes for Allocator (Benno)
- use NonNull<u8> for Allocator::free and Option<NonNull<u8>> for Allocator::realloc (Benno)
- fix leak of IntoIter in Vec::collect (Boqun)
- always realloc (try to shrink) in Vec::collect, it's up the the Allocator to provide a heuristic whether it makes sense to actually shrink
- rename KBox<T, A> -> Box<T, A> and KVec<T, A> -> Vec<T, A> and provide type aliases KBox<T>, VBox<T>, KVBox<T>, etc.
- This allows for much cleaner code and, in combination with removing &self parameters from Allocators, gets us rid of the need for Box::new and Box::new_alloc and all other "_alloc" postfixed functions.
- Before: KBox::new_alloc(foo, Vmalloc)?
- After: VBox::new(foo)?, which resolves to
`Box::<Foo, Vmalloc>::new(foo)?;
Danilo Krummrich (26):
rust: alloc: add Allocator trait
rust: alloc: separate aligned_size from krealloc_aligned
rust: alloc: rename KernelAllocator to Kmalloc
rust: alloc: implement Allocator for Kmalloc
rust: alloc: add module allocator_test
rust: alloc: implement Vmalloc allocator
rust: alloc: implement KVmalloc allocator
rust: alloc: add __GFP_NOWARN to Flags
rust: alloc: implement kernel Box
rust: treewide: switch to our kernel Box type
rust: alloc: remove extension of std's Box
rust: alloc: add Box to prelude
rust: alloc: implement kernel Vec type
rust: alloc: implement IntoIterator for Vec
rust: alloc: implement collect for IntoIter
rust: treewide: switch to the kernel Vec type
rust: alloc: remove VecExt extension
rust: alloc: add Vec to prelude
rust: error: use core::alloc::LayoutError
rust: error: check for config test in Error::name
rust: alloc: implement contains for Flags
rust: alloc: implement Cmalloc in module allocator_test
rust: str: test: replace alloc::format
rust: alloc: update module comment of alloc.rs
kbuild: rust: remove the alloc crate and GlobalAlloc
MAINTAINERS: add entry for the Rust alloc module
MAINTAINERS | 7 + drivers/block/rnull.rs | 4 +- rust/Makefile | 43 +- rust/bindings/bindings_helper.h | 1 + rust/exports.c | 1 - rust/helpers/helpers.c | 1 + rust/helpers/slab.c | 6 + rust/helpers/vmalloc.c | 9 + rust/kernel/alloc.rs | 152 ++++- rust/kernel/alloc/allocator.rs | 198 ++++-- rust/kernel/alloc/allocator_test.rs | 200 ++++++ rust/kernel/alloc/box_ext.rs | 87 --- rust/kernel/alloc/kbox.rs | 465 ++++++++++++++ rust/kernel/alloc/kvec.rs | 905 ++++++++++++++++++++++++++++ rust/kernel/alloc/vec_ext.rs | 185 ------ rust/kernel/error.rs | 6 +- rust/kernel/init.rs | 95 +-- rust/kernel/init/__internal.rs | 2 +- rust/kernel/lib.rs | 1 - rust/kernel/prelude.rs | 5 +- rust/kernel/rbtree.rs | 49 +- rust/kernel/str.rs | 35 +- rust/kernel/sync/arc.rs | 17 +- rust/kernel/sync/condvar.rs | 4 +- rust/kernel/sync/lock/mutex.rs | 2 +- rust/kernel/sync/lock/spinlock.rs | 2 +- rust/kernel/sync/locked_by.rs | 2 +- rust/kernel/types.rs | 52 +- rust/kernel/uaccess.rs | 17 +- rust/kernel/workqueue.rs | 20 +- rust/macros/lib.rs | 12 +- samples/rust/rust_minimal.rs | 4 +- scripts/Makefile.build | 7 +- scripts/generate_rust_analyzer.py | 11 +- 34 files changed, 2029 insertions(+), 578 deletions(-) create mode 100644 rust/helpers/vmalloc.c create mode 100644 rust/kernel/alloc/allocator_test.rs delete mode 100644 rust/kernel/alloc/box_ext.rs create mode 100644 rust/kernel/alloc/kbox.rs create mode 100644 rust/kernel/alloc/kvec.rs delete mode 100644 rust/kernel/alloc/vec_ext.rs
base-commit: db8c7e4c95acc68f1e1cd51e0a7f1532c69a23c9
Signature · 1 line
2.46.0
Danilo Krummrich
maintainerPATCH v7 1/26 rust: alloc: add `Allocator` trait
Add a kernel specific Allocator trait, that in contrast to the one in Rust's core library doesn't require unstable features and supports GFP flags.
Subsequent patches add the following trait implementors: Kmalloc, Vmalloc and KVmalloc.
--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
rust/kernel/alloc.rs +112 -0
@@ -11,6 +11,7 @@/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ; +use core::{alloc::Layout , ptr::NonNull };/// Flags to be used when allocating memory. ///
@@ -86,3 +87,114 @@ pub mod flags {/// small allocations. pub const GFP_NOWAIT :Flags =Flags (bindings::GFP_NOWAIT ); } + +/// The kernel's [`Allocator`] trait. +/// +/// An implementation of [`Allocator`] can allocate, re-allocate and free memory buffers described +/// via [`Layout`]. +/// +/// [`Allocator`] is designed to be implemented as a ZST; [`Allocator`] functions do not operate on +/// an object instance. +/// +/// In order to be able to support `#[derive(SmartPointer)]` later on, we need to avoid a design +/// that requires an `Allocator` to be instantiated, hence its functions must not contain any kind +/// of `self` parameter. +/// +/// # Safety +/// +/// - A memory allocation returned from an allocator must remain valid until it is explicitly freed. +/// +/// - Any pointer to a valid memory allocation must be valid to be passed to any other [`Allocator`] +/// function of the same type. +/// +/// - Implementers must ensure that all trait functions abide by the guarantees documented in the +/// `# Guarantees` sections. +// +// Note that `Allocator::{realloc,free}` don't have an `old_layout` argument (like stdlib's +// corresponding `Allocator` trait functions have), since the implemented (kernel) allocators +// neither need nor honor such an argument. Thus, it would be misleading to make this API require it +// anyways. +// +// More generally, this trait isn't intended for implementers to encode a lot of semantics, but +// rather provide a thin generalization layer for the kernel's allocators. +// +// Depending on future requirements, the requirements for this trait may change as well and +// implementing allocators that need to encode more semantics may become desirable. +pub unsafe trait Allocator { +/// Allocate memory based on `layout` and `flags`. +/// +/// On success, returns a buffer represented as `NonNull<[u8]>` that satisfies the layout +/// constraints (i.e. minimum size and alignment as specified by `layout`). +/// +/// This function is equivalent to `realloc` when called with `None`. +/// +/// # Guarantees +/// +/// When the return value is `Ok(ptr)`, then `ptr` is +/// - valid for reads and writes for `layout.size()` bytes, until it is passed to +/// [`Allocator::free`] or [`Allocator::realloc`], +/// - aligned to `layout.align()`, +/// +/// Additionally, `Flags` are honored as documented in +/// <https://docs.kernel.org/core-api/mm-api.html#mm-api-gfp-flags>. +fn alloc (layout:Layout , flags:Flags ) ->Result <NonNull <[u8 ]>,AllocError > { +// SAFETY: Passing `None` to `realloc` is valid by it's safety requirements and asks for a +// new memory allocation. +unsafe {Self ::realloc (None , layout, flags) } + } + +/// Re-allocate an existing memory allocation to satisfy the requested `layout`. +/// +/// If the requested size is zero, `realloc` behaves equivalent to `free`. +/// +/// If the requested size is larger than the size of the existing allocation, a successful call +/// to `realloc` guarantees that the new or grown buffer has at least `Layout::size` bytes, but +/// may also be larger. +/// +/// If the requested size is smaller than the size of the existing allocation, `realloc` may or +/// may not shrink the buffer; this is implementation specific to the allocator. +/// +/// On allocation failure, the existing buffer, if any, remains valid. +/// +/// The buffer is represented as `NonNull<[u8]>`. +/// +/// # Safety +/// +/// If `ptr == Some(p)`, then `p` must point to an existing and valid memory allocation created +/// by this allocator. The alignment encoded in `layout` must be smaller than or equal to the +/// alignment requested in the previous `alloc` or `realloc` call of the same allocation. +/// +/// Additionally, `ptr` is allowed to be `None`; in this case a new memory allocation is +/// created. +/// +/// # Guarantees +/// +/// This function has the same guarantees as [`Allocator::alloc`]. When `ptr == Some(p)`, then +/// it additionally guarantees that: +/// - the contents of the memory pointed to by `p` are preserved up to the lesser of the new +/// and old size, +/// and old size, i.e. +/// `ret_ptr[0..min(layout.size(), old_size)] == p[0..min(layout.size(), old_size)]`, where +/// `old_size` is the size of the allocation that `p` points at. +/// - when the return value is `Err(AllocError)`, then `p` is still valid. +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError >; + +/// Free an existing memory allocation. +/// +/// # Safety +/// +/// `ptr` must point to an existing and valid memory allocation created by this `Allocator` and +/// must not be a dangling pointer. +/// +/// The memory allocation at `ptr` must never again be read from or written to. +unsafe fn free (ptr:NonNull <u8 >) { +// SAFETY: The caller guarantees that `ptr` points at a valid allocation created by this +// allocator. We are passing a `Layout` with the smallest possible alignment, so it is +// smaller than or equal to the alignment previously used with this allocation. +let _ =unsafe {Self ::realloc (Some (ptr),Layout ::new ::<()>(),Flags (0 )) }; + } +}
Signature · 1 line
2.46.0
Gary Guo
maintainerRe: [PATCH v7 01/26] rust: alloc: add `Allocator` trait
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:37 +0200 Danilo Krummrich <[email protected]> wrote:
Add a kernel specific
Allocatortrait, that in contrast to the one in Rust's core library doesn't require unstable features and supports GFP flags.Subsequent patches add the following trait implementors:
Kmalloc,VmallocandKVmalloc.
Hi Danilo,
I think the current design is unsound regarding ZST.
Let's say that Allocator::alloc gets called with a ZST type with alignment of 4096. Your implementation will call into krelloc with new_size of 0, which gets turned into of kfree of null pointer, which is no-op. Everything is fine so far. Krealloc returns ZERO_SIZE_PTR, and then implementation of <Kmalloc as Allocator>::realloc throws it away and returns NonNull::dangling.
Since NonNull::dangling is called with T=u8, this means the pointer returns is 1, and it's invalid for ZSTs with larger alignments.
And this is unfixable even if the realloc implementation is changed. Let's say the realloc now returns a dangling pointer that is suitable aligned. Now let's see what happens when the Allocator::free is called. kfree would be trying to free a Rust-side ZST pointer, but it has no way to know that it's ZST!
I can see 3 ways of fixing this:
1. Reject ZSTs that have larger alignment than 16 and fix the realloc implementation to return suitable aligned ZST pointer. I don't particularly like the idea of allocating ZST can fail though.
2. Say ZST must be handled by the caller, and make alloc function unsafe. This means that we essentially revert to the GlobalAlloc design of Rust, and all callers have to check for ZST.
3. Accept the old_layout and use it to check whether the allocation is ZST allocation.
My personal preference is 3.
Best,
Gary
Quoted 21 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)rust/kernel/alloc.rs +1
-0@@ -11,6 +11,7 @@/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ; +use core::{alloc::Layout , ptr::NonNull };/// Flags to be used when allocating memory. /// @@ -86,3 +87,114 @@ pub mod flags {/// small allocations.
rust/kernel/alloc.rs pub mod flags { · line 97
Quoted 10 lines
rust/kernel/alloc.rs +8
-0pub const GFP_NOWAIT :Flags =Flags (bindings::GFP_NOWAIT ); } + +/// The kernel's [`Allocator`] trait. +/// +/// An implementation of [`Allocator`] can allocate, re-allocate and free memory buffers described +/// via [`Layout`]. +/// +/// [`Allocator`] is designed to be implemented as a ZST; [`Allocator`] functions do not operate on +/// an object instance.
I think whether the Allocator is ZST or not doesn't matter anymore since we say that functions do not operate on an instance.
rust/kernel/alloc.rs pub mod flags { · line 101
Quoted 4 lines
rust/kernel/alloc.rs +4
-0+/// +/// In order to be able to support `#[derive(SmartPointer)]` later on, we need to avoid a design +/// that requires an `Allocator` to be instantiated, hence its functions must not contain any kind +/// of `self` parameter.
nit: this is the reason for Allocator to not have instances, so should be merged together with the preceding sentence into one paragraph.
Quoted 5 lines
rust/kernel/alloc.rs +5
-0+/// +/// # Safety +/// +/// - A memory allocation returned from an allocator must remain valid until it is explicitly freed. +///
rust/kernel/alloc.rs pub mod flags { · line 116
Quoted 10 lines
rust/kernel/alloc.rs +10
-0+/// - Any pointer to a valid memory allocation must be valid to be passed to any other [`Allocator`] +/// function of the same type. +/// +/// - Implementers must ensure that all trait functions abide by the guarantees documented in the +/// `# Guarantees` sections. +// +// Note that `Allocator::{realloc,free}` don't have an `old_layout` argument (like stdlib's +// corresponding `Allocator` trait functions have), since the implemented (kernel) allocators +// neither need nor honor such an argument. Thus, it would be misleading to make this API require it +// anyways.
I would drop the "honor" part, and drop the sentence saying it's misleading to require it. The documentation should say why we don't have the old_layout argument (because we don't need it for now), and shouldn't be trying to dissuade it from being added if it ends up being useful in the future.
rust/kernel/alloc.rs pub mod flags { · line 122
Quoted 6 lines
rust/kernel/alloc.rs +6
-0+// +// More generally, this trait isn't intended for implementers to encode a lot of semantics, but +// rather provide a thin generalization layer for the kernel's allocators. +// +// Depending on future requirements, the requirements for this trait may change as well and +// implementing allocators that need to encode more semantics may become desirable.
Not sure what's the purpose of these two paragraphs. They sound contradictory to each other.
Quoted 31 lines
rust/kernel/alloc.rs +31
-0+pub unsafe trait Allocator { +/// Allocate memory based on `layout` and `flags`. +/// +/// On success, returns a buffer represented as `NonNull<[u8]>` that satisfies the layout +/// constraints (i.e. minimum size and alignment as specified by `layout`). +/// +/// This function is equivalent to `realloc` when called with `None`. +/// +/// # Guarantees +/// +/// When the return value is `Ok(ptr)`, then `ptr` is +/// - valid for reads and writes for `layout.size()` bytes, until it is passed to +/// [`Allocator::free`] or [`Allocator::realloc`], +/// - aligned to `layout.align()`, +/// +/// Additionally, `Flags` are honored as documented in +/// <https://docs.kernel.org/core-api/mm-api.html#mm-api-gfp-flags>. +fn alloc (layout:Layout , flags:Flags ) ->Result <NonNull <[u8 ]>,AllocError > { +// SAFETY: Passing `None` to `realloc` is valid by it's safety requirements and asks for a +// new memory allocation. +unsafe {Self ::realloc (None , layout, flags) } + } + +/// Re-allocate an existing memory allocation to satisfy the requested `layout`. +/// +/// If the requested size is zero, `realloc` behaves equivalent to `free`. +/// +/// If the requested size is larger than the size of the existing allocation, a successful call +/// to `realloc` guarantees that the new or grown buffer has at least `Layout::size` bytes, but +/// may also be larger. +///
rust/kernel/alloc.rs pub mod flags { · line 163
Quoted 10 lines
rust/kernel/alloc.rs +10
-0+/// If the requested size is smaller than the size of the existing allocation, `realloc` may or +/// may not shrink the buffer; this is implementation specific to the allocator. +/// +/// On allocation failure, the existing buffer, if any, remains valid. +/// +/// The buffer is represented as `NonNull<[u8]>`. +/// +/// # Safety +/// +/// If `ptr == Some(p)`, then `p` must point to an existing and valid memory allocation created
nit: maybe
If `ptr` is `Some(p)`?
because ptr carries the provenance not only addresses.
Quoted 37 lines
rust/kernel/alloc.rs +37
-0+/// by this allocator. The alignment encoded in `layout` must be smaller than or equal to the +/// alignment requested in the previous `alloc` or `realloc` call of the same allocation. +/// +/// Additionally, `ptr` is allowed to be `None`; in this case a new memory allocation is +/// created. +/// +/// # Guarantees +/// +/// This function has the same guarantees as [`Allocator::alloc`]. When `ptr == Some(p)`, then +/// it additionally guarantees that: +/// - the contents of the memory pointed to by `p` are preserved up to the lesser of the new +/// and old size, +/// and old size, i.e. +/// `ret_ptr[0..min(layout.size(), old_size)] == p[0..min(layout.size(), old_size)]`, where +/// `old_size` is the size of the allocation that `p` points at. +/// - when the return value is `Err(AllocError)`, then `p` is still valid. +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError >; + +/// Free an existing memory allocation. +/// +/// # Safety +/// +/// `ptr` must point to an existing and valid memory allocation created by this `Allocator` and +/// must not be a dangling pointer. +/// +/// The memory allocation at `ptr` must never again be read from or written to. +unsafe fn free (ptr:NonNull <u8 >) { +// SAFETY: The caller guarantees that `ptr` points at a valid allocation created by this +// allocator. We are passing a `Layout` with the smallest possible alignment, so it is +// smaller than or equal to the alignment previously used with this allocation. +let _ =unsafe {Self ::realloc (Some (ptr),Layout ::new ::<()>(),Flags (0 )) }; + } +}
Danilo Krummrich
maintainerRe: [PATCH v7 01/26] rust: alloc: add `Allocator` trait
Hi Gary,
thanks for taking a look.
Quoted 21 lines · Gary Guo
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:37 +0200 Danilo Krummrich <[email protected]> wrote:
Add a kernel specific
Allocatortrait, that in contrast to the one in Rust's core library doesn't require unstable features and supports GFP flags.Subsequent patches add the following trait implementors:
Kmalloc,VmallocandKVmalloc.Hi Danilo,
I think the current design is unsound regarding ZST.
Let's say that
Allocator::allocgets called with a ZST type with alignment of 4096. Your implementation will call intokrellocwithnew_sizeof 0, which gets turned into ofkfreeof null pointer, which is no-op. Everything is fine so far. Krealloc returnsZERO_SIZE_PTR, and then implementation of<Kmalloc as Allocator>::reallocthrows it away and returnsNonNull::dangling.
Quoted 2 lines
Since
NonNull::danglingis called with T=u8, this means the pointer returns is 1, and it's invalid for ZSTs with larger alignments.
Right, this interface is not meant to handle "allocations" for ZSTs.
But you're right, since alloc is a safe function, we should return a properly aligned pointer.
Quoted 6 lines
And this is unfixable even if the realloc implementation is changed. Let's say the realloc now returns a dangling pointer that is suitable aligned. Now let's see what happens when the
Allocator::freeis called.kfreewould be trying to free a Rust-side ZST pointer, but it has no way to know that it's ZST!
Right, that's why it's not valid to call free with dangling pointers.
From the safety comment of free:
"ptr must point to an existing and valid memory allocation created by this Allocator and must not be a dangling pointer."
We still need the same in realloc though.
Quoted 11 lines
I can see 3 ways of fixing this:
1. Reject ZSTs that have larger alignment than 16 and fix the realloc implementation to return suitable aligned ZST pointer. I don't particularly like the idea of allocating ZST can fail though.
2. Say ZST must be handled by the caller, and make alloc function unsafe. This means that we essentially revert to theGlobalAllocdesign of Rust, and all callers have to check for ZST.
3. Accept theold_layoutand use it to check whether the allocation is ZST allocation.
Quoted 1 line
My personal preference is 3.
There is also 4.
Let alloc and realloc return a properly aligned dangling pointer for size == 0 and don't accept dangling pointers in realloc and free.
And 5.
Reject the combination of None and size == 0 entirely, as earlier proposed by Benno.
I'm fine with both, 4. and 5. with a slight preference for 4.
I'd also go along with 1., as a mix of 4. and 5.
I really don't like making alloc unsafe, and I really don't want to have old_layout in free. Please let's not discuss this again. :-)
Quoted 30 lines
Best,
GaryQuoted 26 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)rust/kernel/alloc.rs +4
-0@@ -11,6 +11,7 @@/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ; +use core::{alloc::Layout , ptr::NonNull };/// Flags to be used when allocating memory. /// @@ -86,3 +87,114 @@ pub mod flags {/// small allocations. pub const GFP_NOWAIT :Flags =Flags (bindings::GFP_NOWAIT ); } + +/// The kernel's [`Allocator`] trait. +///
Quoted 8 lines
rust/kernel/alloc.rs pub mod flags { · line 97
Quoted 5 lines
rust/kernel/alloc.rs +5
-0+/// An implementation of [`Allocator`] can allocate, re-allocate and free memory buffers described +/// via [`Layout`]. +/// +/// [`Allocator`] is designed to be implemented as a ZST; [`Allocator`] functions do not operate on +/// an object instance. I think whether the Allocator is ZST or not doesn't matter anymore since we say that functions do not operate on an instance.
IMO, It's still valid to say that it's designed to be implemented as ZST, especially since it's never instantiated.
Quoted 24 lines
rust/kernel/alloc.rs pub mod flags { · line 101
Quoted 5 lines
rust/kernel/alloc.rs +4
-0+/// +/// In order to be able to support `#[derive(SmartPointer)]` later on, we need to avoid a design +/// that requires an `Allocator` to be instantiated, hence its functions must not contain any kind +/// of `self` parameter. nit: this is the reason for
Allocatorto not have instances, so should be merged together with the preceding sentence into one paragraph.Quoted 13 lines
rust/kernel/alloc.rs +13
-0+/// +/// # Safety +/// +/// - A memory allocation returned from an allocator must remain valid until it is explicitly freed. +/// +/// - Any pointer to a valid memory allocation must be valid to be passed to any other [`Allocator`] +/// function of the same type. +/// +/// - Implementers must ensure that all trait functions abide by the guarantees documented in the +/// `# Guarantees` sections. +// +// Note that `Allocator::{realloc,free}` don't have an `old_layout` argument (like stdlib's +// corresponding `Allocator` trait functions have), since the implemented (kernel) allocators
Quoted 8 lines
rust/kernel/alloc.rs pub mod flags { · line 116
Quoted 2 lines
rust/kernel/alloc.rs +2
-0+// neither need nor honor such an argument. Thus, it would be misleading to make this API require it +// anyways. I would drop the "honor" part, and drop the sentence saying it's misleading to require it. The documentation should say why we don't have the
old_layoutargument (because we don't need it for now), and shouldn't be trying to dissuade it from being added if it ends up being useful in the future.
But the honor part is the crucial one for me. Just because it's not needed it could still be optional (and honored). But that's not the case. And because of that it would indeed be misleading to have the argument.
That's really just my reason, I'm not trying to dissuade anyone from adding it if it's actually needed.
If there's a good reason to add it, and it's honored by (other) implementations, it wouldn't be generally misleading anymore.
Quoted 10 lines
rust/kernel/alloc.rs pub mod flags { · line 122
Quoted 7 lines
rust/kernel/alloc.rs +6
-0+// +// More generally, this trait isn't intended for implementers to encode a lot of semantics, but +// rather provide a thin generalization layer for the kernel's allocators. +// +// Depending on future requirements, the requirements for this trait may change as well and +// implementing allocators that need to encode more semantics may become desirable. Not sure what's the purpose of these two paragraphs. They sound contradictory to each other.
I tried to articulate what it is currently intended for, but, depending on future requirements, this may change.
I was asked by Benno to somehow add that we're open for changes...
Quoted 45 lines
Quoted 32 lines
rust/kernel/alloc.rs +31
-0+pub unsafe trait Allocator { +/// Allocate memory based on `layout` and `flags`. +/// +/// On success, returns a buffer represented as `NonNull<[u8]>` that satisfies the layout +/// constraints (i.e. minimum size and alignment as specified by `layout`). +/// +/// This function is equivalent to `realloc` when called with `None`. +/// +/// # Guarantees +/// +/// When the return value is `Ok(ptr)`, then `ptr` is +/// - valid for reads and writes for `layout.size()` bytes, until it is passed to +/// [`Allocator::free`] or [`Allocator::realloc`], +/// - aligned to `layout.align()`, +/// +/// Additionally, `Flags` are honored as documented in +/// <https://docs.kernel.org/core-api/mm-api.html#mm-api-gfp-flags>. +fn alloc (layout:Layout , flags:Flags ) ->Result <NonNull <[u8 ]>,AllocError > { +// SAFETY: Passing `None` to `realloc` is valid by it's safety requirements and asks for a +// new memory allocation. +unsafe {Self ::realloc (None , layout, flags) } + } + +/// Re-allocate an existing memory allocation to satisfy the requested `layout`. +/// +/// If the requested size is zero, `realloc` behaves equivalent to `free`. +/// +/// If the requested size is larger than the size of the existing allocation, a successful call +/// to `realloc` guarantees that the new or grown buffer has at least `Layout::size` bytes, but +/// may also be larger. +/// rust/kernel/alloc.rs pub mod flags { · line 163
Quoted 10 lines
rust/kernel/alloc.rs +10
-0+/// If the requested size is smaller than the size of the existing allocation, `realloc` may or +/// may not shrink the buffer; this is implementation specific to the allocator. +/// +/// On allocation failure, the existing buffer, if any, remains valid. +/// +/// The buffer is represented as `NonNull<[u8]>`. +/// +/// # Safety +/// +/// If `ptr == Some(p)`, then `p` must point to an existing and valid memory allocation created nit: maybe
Quoted 3 lines
If `ptr` is `Some(p)`?because
ptrcarries the provenance not only addresses.
I don't mind either versions, this one was proposed by Benno.
Quoted 39 lines
Quoted 38 lines
rust/kernel/alloc.rs +37
-0+/// by this allocator. The alignment encoded in `layout` must be smaller than or equal to the +/// alignment requested in the previous `alloc` or `realloc` call of the same allocation. +/// +/// Additionally, `ptr` is allowed to be `None`; in this case a new memory allocation is +/// created. +/// +/// # Guarantees +/// +/// This function has the same guarantees as [`Allocator::alloc`]. When `ptr == Some(p)`, then +/// it additionally guarantees that: +/// - the contents of the memory pointed to by `p` are preserved up to the lesser of the new +/// and old size, +/// and old size, i.e. +/// `ret_ptr[0..min(layout.size(), old_size)] == p[0..min(layout.size(), old_size)]`, where +/// `old_size` is the size of the allocation that `p` points at. +/// - when the return value is `Err(AllocError)`, then `p` is still valid. +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError >; + +/// Free an existing memory allocation. +/// +/// # Safety +/// +/// `ptr` must point to an existing and valid memory allocation created by this `Allocator` and +/// must not be a dangling pointer. +/// +/// The memory allocation at `ptr` must never again be read from or written to. +unsafe fn free (ptr:NonNull <u8 >) { +// SAFETY: The caller guarantees that `ptr` points at a valid allocation created by this +// allocator. We are passing a `Layout` with the smallest possible alignment, so it is +// smaller than or equal to the alignment previously used with this allocation. +let _ =unsafe {Self ::realloc (Some (ptr),Layout ::new ::<()>(),Flags (0 )) }; + } +}
Gary Guo
maintainerRe: [PATCH v7 01/26] rust: alloc: add `Allocator` trait
Quoted 41 lines · On Sun, 15 Sep 2024 19:02:40 +0200 Danilo Krummrich <[email protected]> wrote:
Hi Gary,
thanks for taking a look.
Quoted 21 lines · Gary Guo
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:37 +0200 Danilo Krummrich <[email protected]> wrote:
Add a kernel specific
Allocatortrait, that in contrast to the one in Rust's core library doesn't require unstable features and supports GFP flags.Subsequent patches add the following trait implementors:
Kmalloc,VmallocandKVmalloc.Hi Danilo,
I think the current design is unsound regarding ZST.
Let's say that
Allocator::allocgets called with a ZST type with alignment of 4096. Your implementation will call intokrellocwithnew_sizeof 0, which gets turned into ofkfreeof null pointer, which is no-op. Everything is fine so far. Krealloc returnsZERO_SIZE_PTR, and then implementation of<Kmalloc as Allocator>::reallocthrows it away and returnsNonNull::dangling.Quoted 2 lines
Since
NonNull::danglingis called with T=u8, this means the pointer returns is 1, and it's invalid for ZSTs with larger alignments.Right, this interface is not meant to handle "allocations" for ZSTs.
But you're right, since
allocis a safe function, we should return a properly aligned pointer.Quoted 6 lines
And this is unfixable even if the realloc implementation is changed. Let's say the realloc now returns a dangling pointer that is suitable aligned. Now let's see what happens when the
Allocator::freeis called.kfreewould be trying to free a Rust-side ZST pointer, but it has no way to know that it's ZST!
Quoted 8 lines
Right, that's why it's not valid to call
freewith dangling pointers.From the safety comment of
free:"
ptrmust point to an existing and valid memory allocation created by thisAllocatorand must not be a dangling pointer."We still need the same in
reallocthough.
I don't agree with this reading. If you allocate something with alloc and it doesn't return an error then you should be able to feed it to free. Whether the allocator does actual allocation when size is zero or return a dangling pointer shouldn't matter to the caller.
The fact you Kmalloc returns a dangling pointer for ZST is an implementation detail and the caller shouldn't care (and it also couldn't check whether it's a dangling pointer). Nothing in your alloc doc mention about dangling pointer return for zero-sized alloc at all.
Quoted 12 lines
Quoted 12 lines
I can see 3 ways of fixing this:
1. Reject ZSTs that have larger alignment than 16 and fix the realloc implementation to return suitable aligned ZST pointer. I don't particularly like the idea of allocating ZST can fail though.
2. Say ZST must be handled by the caller, and make alloc function unsafe. This means that we essentially revert to theGlobalAllocdesign of Rust, and all callers have to check for ZST.
3. Accept theold_layoutand use it to check whether the allocation is ZST allocation.
Quoted 6 lines
Quoted 1 line
My personal preference is 3.
There is also 4.
Let
allocandreallocreturn a properly aligned dangling pointer forsize == 0and don't accept dangling pointers inreallocandfree.
I'll consider the API design to be bad if I can't pass allocated pointer to free. If caller needs to handle ZST specially then we might as well just ban it completely.
Quoted 11 lines
And 5.
Reject the combination of
Noneandsize == 0entirely, as earlier proposed by Benno.I'm fine with both, 4. and 5. with a slight preference for 4.
I'd also go along with 1., as a mix of 4. and 5.
I really don't like making
allocunsafe, and I really don't want to haveold_layoutinfree. Please let's not discuss this again. :-)
I don't buy it.
Your argument for having old_layout is so that the caller doesn't need to care about the size. But as demonstrated the caller *does* need to care about whether the size is zero.
Our previous discussion doesn't cover the particular case of ZST and you said that it reason arise that we need this extra parameter, then it could be added. It feels to me that sane behaviour when it comes to ZST allocation is a very good reason.
Quoted 11 lines
Quoted 11 lines
Best,
GaryQuoted 6 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
Gary Guo
maintainerRe: [PATCH v7 01/26] rust: alloc: add `Allocator` trait
Quoted 101 lines · On Sun, 15 Sep 2024 20:22:42 +0100 Gary Guo <[email protected]> wrote:
Quoted 41 lines · On Sun, 15 Sep 2024 19:02:40 +0200 Danilo Krummrich <[email protected]> wrote:
Hi Gary,
thanks for taking a look.
Quoted 21 lines · Gary Guo
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:37 +0200 Danilo Krummrich <[email protected]> wrote:
Add a kernel specific
Allocatortrait, that in contrast to the one in Rust's core library doesn't require unstable features and supports GFP flags.Subsequent patches add the following trait implementors:
Kmalloc,VmallocandKVmalloc.Hi Danilo,
I think the current design is unsound regarding ZST.
Let's say that
Allocator::allocgets called with a ZST type with alignment of 4096. Your implementation will call intokrellocwithnew_sizeof 0, which gets turned into ofkfreeof null pointer, which is no-op. Everything is fine so far. Krealloc returnsZERO_SIZE_PTR, and then implementation of<Kmalloc as Allocator>::reallocthrows it away and returnsNonNull::dangling.Quoted 2 lines
Since
NonNull::danglingis called with T=u8, this means the pointer returns is 1, and it's invalid for ZSTs with larger alignments.Right, this interface is not meant to handle "allocations" for ZSTs.
But you're right, since
allocis a safe function, we should return a properly aligned pointer.Quoted 6 lines
And this is unfixable even if the realloc implementation is changed. Let's say the realloc now returns a dangling pointer that is suitable aligned. Now let's see what happens when the
Allocator::freeis called.kfreewould be trying to free a Rust-side ZST pointer, but it has no way to know that it's ZST!Quoted 8 lines
Right, that's why it's not valid to call
freewith dangling pointers.From the safety comment of
free:"
ptrmust point to an existing and valid memory allocation created by thisAllocatorand must not be a dangling pointer."We still need the same in
reallocthough.I don't agree with this reading. If you allocate something with
allocand it doesn't return an error then you should be able to feed it tofree. Whether the allocator does actual allocation when size is zero or return a dangling pointer shouldn't matter to the caller.The fact you
Kmallocreturns a dangling pointer for ZST is an implementation detail and the caller shouldn't care (and it also couldn't check whether it's a dangling pointer). Nothing in yourallocdoc mention about dangling pointer return for zero-sized alloc at all.Quoted 12 lines
Quoted 12 lines
I can see 3 ways of fixing this:
1. Reject ZSTs that have larger alignment than 16 and fix the realloc implementation to return suitable aligned ZST pointer. I don't particularly like the idea of allocating ZST can fail though.
2. Say ZST must be handled by the caller, and make alloc function unsafe. This means that we essentially revert to theGlobalAllocdesign of Rust, and all callers have to check for ZST.
3. Accept theold_layoutand use it to check whether the allocation is ZST allocation.Quoted 6 lines
Quoted 1 line
My personal preference is 3.
There is also 4.
Let
allocandreallocreturn a properly aligned dangling pointer forsize == 0and don't accept dangling pointers inreallocandfree.I'll consider the API design to be bad if I can't pass allocated pointer to free. If caller needs to handle ZST specially then we might as well just ban it completely.
Quoted 11 lines
And 5.
Reject the combination of
Noneandsize == 0entirely, as earlier proposed by Benno.I'm fine with both, 4. and 5. with a slight preference for 4.
I'd also go along with 1., as a mix of 4. and 5.
I really don't like making
allocunsafe, and I really don't want to haveold_layoutinfree. Please let's not discuss this again. :-)I don't buy it.
Quoted 8 lines
Your argument for having
old_layoutis so that the caller doesn't need to care about the size. But as demonstrated the caller *does* need to care about whether the size is zero.Our previous discussion doesn't cover the particular case of ZST and you said that it reason arise that we need this extra parameter, then it could be added. It feels to me that sane behaviour when it comes to ZST allocation is a very good reason.
Just to add that if you *really* want to avoid the old layout param, another approach accceptable to me is to have a NonZeroLayout and have alloc only accept that.
Either Allocator trait handles ZST well or it refuses to handle them at all. No "it works for alloc but not for free" please.
Best,
Gary
Quoted 8 lines
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
Danilo Krummrich
maintainerRe: [PATCH v7 01/26] rust: alloc: add `Allocator` trait
Quoted 3 lines · Gary Guo
Just to add that if you *really* want to avoid the old layout param, another approach accceptable to me is to have a
NonZeroLayoutand havealloconly accept that.
That is a good idea, I like that.
Quoted 3 lines
Either
Allocatortrait handles ZST well or it refuses to handle them at all. No "it works for alloc but not for free" please.
I agree with that, then we shouldn't handle them at all.
Quoted 13 lines
Best,
GaryQuoted 8 lines
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
Danilo Krummrich
maintainerRe: [PATCH v7 01/26] rust: alloc: add `Allocator` trait
In reply to Gary GuoQuoted 10 lines · Gary Guo
Quoted 4 lines · On Sun, 15 Sep 2024 19:02:40 +0200 Danilo Krummrich <[email protected]> wrote:
There is also 4.
Let
allocandreallocreturn a properly aligned dangling pointer forsize == 0and don't accept dangling pointers inreallocandfree.I'll consider the API design to be bad if I can't pass allocated pointer to free. If caller needs to handle ZST specially then we might as well just ban it completely.
Fine for me -- I don't see a need to support ZSTs with Allocator.
I think its main purpose is to give us an interface to actually allocate memory. We probably don't need it to be a "dangling pointer generator".
Quoted 15 lines
Quoted 6 lines
And 5.
Reject the combination of
Noneandsize == 0entirely, as earlier proposed by Benno.Quoted 6 lines
I'm fine with both, 4. and 5. with a slight preference for 4.
I'd also go along with 1., as a mix of 4. and 5.
I really don't like making
allocunsafe, and I really don't want to haveold_layoutinfree. Please let's not discuss this again. :-)I don't buy it.
Quoted 8 lines
Your argument for having
old_layoutis so that the caller doesn't need to care about the size. But as demonstrated the caller *does* need to care about whether the size is zero.Our previous discussion doesn't cover the particular case of ZST and you said that it reason arise that we need this extra parameter, then it could be added. It feels to me that sane behaviour when it comes to ZST allocation is a very good reason.
I don't see why we should "optimize" the API for creating dangling pointers and be able to pass them to free (which does not serve any practical purpose).
I don't want to add arguments that are meaningless for the actual backing allocators (such as Kmalloc, Vmalloc, etc.), only to be able to generate and "free" pointers for ZSTs with arbitrary alignment.
Do we even have use cases for ZSTs with other alignments?
Quoted 13 lines
Quoted 12 lines
Quoted 12 lines
Best,
GaryQuoted 6 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
Danilo Krummrich
maintainerRFC PATCH 1/1 rust: alloc: pass `old_layout` to `Allocator`
In reply to Danilo KrummrichSince this came up a few times, this patch shows how the implementation looks like with an old_layout argument.
--- rust/kernel/alloc.rs | 32 ++++++++++++-------------- rust/kernel/alloc/allocator.rs | 28 +++++++++++++++++++---- rust/kernel/alloc/kbox.rs | 13 ++++++----- rust/kernel/alloc/kvec.rs | 42 +++++++++++++++++++++++++++------- 4 files changed, 78 insertions(+), 37 deletions(-)
rust/kernel/alloc.rs +14 -18
@@ -166,7 +166,7 @@ pub unsafe trait Allocator {fn alloc (layout:Layout , flags:Flags ) ->Result <NonNull <[u8 ]>,AllocError > {// SAFETY: Passing `None` to `realloc` is valid by it's safety requirements and asks for a // new memory allocation. -unsafe {Self ::realloc (None , layout, flags) } +unsafe {Self ::realloc (None , layout,Layout ::new ::<()>(), flags) } }/// Re-allocate an existing memory allocation to satisfy the requested `layout`.
@@ -186,26 +186,23 @@ fn alloc(layout: Layout, flags: Flags) -> Result<NonNull<[u8]>, AllocError> {/// /// # Safety /// -/// If `ptr == Some(p)`, then `p` must point to an existing and valid memory allocation created -/// by this allocator. The alignment encoded in `layout` must be smaller than or equal to the -/// alignment requested in the previous `alloc` or `realloc` call of the same allocation. -/// -/// Additionally, `ptr` is allowed to be `None`; in this case a new memory allocation is -/// created. +/// - If `ptr == Some(p)`, then `p` must point to an existing and valid memory allocation +/// created by this allocator. +/// - `ptr` is allowed to be `None`; in this case a new memory allocation is created. +/// - `old_layout` must match the `Layout` the allocation has been created with. /// /// # Guarantees /// /// This function has the same guarantees as [`Allocator::alloc`]. When `ptr == Some(p)`, then /// it additionally guarantees that: /// - the contents of the memory pointed to by `p` are preserved up to the lesser of the new -/// and old size, -/// and old size, i.e. -/// `ret_ptr[0..min(layout.size(), old_size)] == p[0..min(layout.size(), old_size)]`, where -/// `old_size` is the size of the allocation that `p` points at. -/// - when the return value is `Err(AllocError)`, then `p` is still valid. +/// and old size, i.e. `ret_ptr[0..min(layout.size(), old_layout.size())] == +/// p[0..min(layout.size(), old_layout.size())]`. +/// - when the return value is `Err(AllocError)`, then `ptr` is still valid. unsafe fn realloc ( ptr:Option <NonNull <u8 >>, layout:Layout , + old_layout:Layout , flags:Flags , ) ->Result <NonNull <[u8 ]>,AllocError >;
@@ -213,14 +210,13 @@ unsafe fn realloc(/// /// # Safety /// -/// `ptr` must point to an existing and valid memory allocation created by this `Allocator` and -/// must not be a dangling pointer. -/// -/// The memory allocation at `ptr` must never again be read from or written to. -unsafe fn free (ptr:NonNull <u8 >) { +/// - `ptr` must point to an existing and valid memory allocation created by this `Allocator`. +/// - `layout` must match the `Layout` the allocation has been created with. +/// - The memory allocation at `ptr` must never again be read from or written to. +unsafe fn free (ptr:NonNull <u8 >, layout:Layout ) {// SAFETY: The caller guarantees that `ptr` points at a valid allocation created by this // allocator. We are passing a `Layout` with the smallest possible alignment, so it is // smaller than or equal to the alignment previously used with this allocation. -let _ =unsafe {Self ::realloc (Some (ptr),Layout ::new ::<()>(),Flags (0 )) }; +let _ =unsafe {Self ::realloc (Some (ptr),Layout ::new ::<()>(), layout,Flags (0 )) }; } }
rust/kernel/alloc/allocator.rs +23 -5
@@ -54,6 +54,14 @@ fn aligned_size(new_layout: Layout) -> usize { layout.size () } +/// Returns a properly aligned dangling pointer from the given `layout`. +fn zst_realloc (layout:Layout ) ->NonNull <u8 > { +let ptr = layout.align ()as *mut u8 ; + +// SAFETY: `layout.align()` (and hence `ptr`) is guaranteed to be non-zero. +unsafe {NonNull ::new_unchecked (ptr) } +} +/// # Invariants /// /// One of the following `krealloc`, `vrealloc`, `kvrealloc`.
@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {let size =aligned_size (layout);let ptr =match ptr { -Some (ptr) => ptr.as_ptr (), +Some (ptr) => { +if old_layout.size () ==0 { + ptr::null () + }else { + ptr.as_ptr () + } + }None => ptr::null (), };
@@ -106,7 +121,7 @@ unsafe fn call( };let ptr =if size ==0 { -NonNull ::dangling () +zst_realloc (layout) }else {NonNull ::new (raw_ptr).ok_or (AllocError )? };
@@ -124,10 +139,11 @@ unsafe impl Allocator for Kmalloc {unsafe fn realloc ( ptr:Option <NonNull <u8 >>, layout:Layout , + old_layout:Layout , flags:Flags , ) ->Result <NonNull <[u8 ]>,AllocError > {// SAFETY: `ReallocFunc::call` has the same safety requirements as `Allocator::realloc`. -unsafe {ReallocFunc ::KREALLOC .call (ptr, layout, flags) } +unsafe {ReallocFunc ::KREALLOC .call (ptr, layout, old_layout, flags) } } }
@@ -140,6 +156,7 @@ unsafe impl Allocator for Vmalloc {unsafe fn realloc ( ptr:Option <NonNull <u8 >>, layout:Layout , + old_layout:Layout , flags:Flags , ) ->Result <NonNull <[u8 ]>,AllocError > {// TODO: Support alignments larger than PAGE_SIZE.
@@ -150,7 +167,7 @@ unsafe fn realloc(// SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously // allocated with this `Allocator`. -unsafe {ReallocFunc ::VREALLOC .call (ptr, layout, flags) } +unsafe {ReallocFunc ::VREALLOC .call (ptr, layout, old_layout, flags) } } }
@@ -163,6 +180,7 @@ unsafe impl Allocator for KVmalloc {unsafe fn realloc ( ptr:Option <NonNull <u8 >>, layout:Layout , + old_layout:Layout , flags:Flags , ) ->Result <NonNull <[u8 ]>,AllocError > {// TODO: Support alignments larger than PAGE_SIZE.
@@ -173,6 +191,6 @@ unsafe fn realloc(// SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously // allocated with this `Allocator`. -unsafe {ReallocFunc ::KVREALLOC .call (ptr, layout, flags) } +unsafe {ReallocFunc ::KVREALLOC .call (ptr, layout, old_layout, flags) } } }
rust/kernel/alloc/kbox.rs +7 -6
@@ -5,6 +5,7 @@#[allow(unused_imports)] // Used in doc comments. use super ::allocator::{KVmalloc ,Kmalloc ,Vmalloc };use super ::{AllocError ,Allocator ,Flags }; +use core::alloc::Layout ;use core::fmt;use core::marker::PhantomData ;use core::mem::ManuallyDrop ;
@@ -233,7 +234,7 @@ pub fn new_uninit(flags: Flags) -> Result<Box<MaybeUninit<T>, A>, AllocError> {let ptr =if Self ::is_zst () {NonNull ::dangling () }else { -let layout = core::alloc::Layout ::new ::<MaybeUninit <T >>(); +let layout =Layout ::new ::<MaybeUninit <T >>();let ptr =A ::alloc (layout, flags)?; ptr.cast ()
@@ -452,14 +453,14 @@ impl<T, A> Drop for Box<T, A> A: Allocator, { fn drop(&mut self) { -let size = core::mem::size_of_val ::<T >(self); +let layout =Layout ::for_value ::<T >(self);// SAFETY: The pointer in `self.0` is guaranteed to be valid by the type invariant. unsafe { core::ptr::drop_in_place ::<T >(self.deref_mut ()) }; -if size !=0 { -// SAFETY: As `size` is not zero, `self.0` was previously allocated with `A`. -unsafe {A ::free (self.0 .cast ()) }; - } +// SAFETY: +// - `self.0` was previously allocated with `A`. +// - `layout` is equal to the `Layout´ `self.0` was allocated with. +unsafe {A ::free (self.0 .cast (), layout) }; } }
rust/kernel/alloc/kvec.rs +34 -8
92 lines not on this page · raw message
Signature · 1 line
2.46.1
Alice Ryhl
maintainerRe: [RFC PATCH] rust: alloc: pass `old_layout` to `Allocator`
Quoted 7 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +1
-0@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {
rust/kernel/alloc/allocator.rs unsafe fn call( · line 106
Quoted 10 lines
rust/kernel/alloc/allocator.rs +7
-1let size =aligned_size (layout); let ptr = match ptr { -Some (ptr) => ptr.as_ptr (), + Some(ptr) => { + if old_layout.size() == 0 { + ptr::null() + } else { + ptr.as_ptr() + } + }
This is making Allocator work with zero-sized types, which deviates from std. We should not do that without a reason. What is the reason?
Alice
Re: [RFC PATCH] rust: alloc: pass `old_layout` to `Allocator`
Quoted 13 lines · Alice Ryhl
Quoted 12 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +3
-1@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {let size =aligned_size (layout);let ptr =match ptr { - Some(ptr) => ptr.as_ptr(), +Some (ptr) => { +if old_layout.size () ==0 {
Quoted 8 lines
rust/kernel/alloc/allocator.rs unsafe fn call( · line 106
Quoted 5 lines
rust/kernel/alloc/allocator.rs +5
-0+ ptr::null () + }else { + ptr.as_ptr () + } + }This is making Allocator work with zero-sized types, which deviates from std. We should not do that without a reason. What is the reason?
The global allocator doesn't support it, but the Allocator trait from std handles zero-sized allocations. For example, this code runs as expected:
#![feature(allocator_api)]
use std::alloc::{self, Allocator, Layout};
fn main() {
let alloc: &dyn Allocator = &alloc::Global;
let ptr = alloc.allocate(Layout::new::<()>()).unwrap().cast::<u8>();
unsafe { alloc.deallocate(ptr, Layout::new::<()>()) };
}---
Cheers,
Benno
Gary Guo
maintainerRe: [RFC PATCH] rust: alloc: pass `old_layout` to `Allocator`
In reply to Alice RyhlQuoted 22 lines · On Mon, 23 Sep 2024 15:56:28 +0200 Alice Ryhl <[email protected]> wrote:
Quoted 7 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +1
-0@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {rust/kernel/alloc/allocator.rs unsafe fn call( · line 106
Quoted 10 lines
rust/kernel/alloc/allocator.rs +7
-1let size =aligned_size (layout); let ptr = match ptr { -Some (ptr) => ptr.as_ptr (), + Some(ptr) => { + if old_layout.size() == 0 { + ptr::null() + } else { + ptr.as_ptr() + } + }This is making Allocator work with zero-sized types, which deviates from std. We should not do that without a reason. What is the reason?
Quoted 1 line
Alice
As Benno said, this makes the API closer to Rust allocator_api Allocator trait as opposed to deviation.
There's one benefit of doing this (discussed with Danilo off-list), which is it removes ZST special casing from caller. This RFC patch simplifies Box handling, and if we add this line to the safety doc
ptr does not need to be a pointer returned by this allocator if the layout is zero-sized.
then the Vec can also be simplified, removing all logics handling ZST specially, except for Vec::new() which it forges a well-aligned dangling pointer from nowhere.
Best,
Gary
Danilo Krummrich
maintainerRe: [RFC PATCH] rust: alloc: pass `old_layout` to `Allocator`
Quoted 34 lines · Gary Guo
Quoted 22 lines · On Mon, 23 Sep 2024 15:56:28 +0200 Alice Ryhl <[email protected]> wrote:
Quoted 7 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +1
-0@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {rust/kernel/alloc/allocator.rs unsafe fn call( · line 106
Quoted 10 lines
rust/kernel/alloc/allocator.rs +7
-1let size =aligned_size (layout); let ptr = match ptr { -Some (ptr) => ptr.as_ptr (), + Some(ptr) => { + if old_layout.size() == 0 { + ptr::null() + } else { + ptr.as_ptr() + } + }This is making Allocator work with zero-sized types, which deviates from std. We should not do that without a reason. What is the reason?
Quoted 1 line
Alice
As Benno said, this makes the API closer to Rust
allocator_apiAllocator trait as opposed to deviation.There's one benefit of doing this (discussed with Danilo off-list), which is it removes ZST special casing from caller. This RFC patch simplifies
Boxhandling, and if we add this line to the safety doc
Quoted 6 lines
ptrdoes not need to be a pointer returned by this allocator if the layout is zero-sized.then the
Veccan also be simplified, removing all logics handling ZST specially, except forVec::new()which it forges a well-aligned dangling pointer from nowhere.
Partially, we still need the additional Layout for Allocator::free, which in Vec::drop and IntoIter::drop looks like this:
let layout = Layout::array::<T>(self.cap).unwrap();
I really dislike that this can potentially transform into BUG(), but that's probably unrelated to this patch series.
Quoted 4 lines
Best,
Gary
Danilo Krummrich
maintainerRe: [RFC PATCH] rust: alloc: pass `old_layout` to `Allocator`
Quoted 38 lines · Danilo Krummrich
Quoted 37 lines · Gary Guo
Quoted 22 lines · On Mon, 23 Sep 2024 15:56:28 +0200 Alice Ryhl <[email protected]> wrote:
Quoted 7 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +1
-0@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {rust/kernel/alloc/allocator.rs unsafe fn call( · line 106
Quoted 10 lines
rust/kernel/alloc/allocator.rs +7
-1let size =aligned_size (layout); let ptr = match ptr { -Some (ptr) => ptr.as_ptr (), + Some(ptr) => { + if old_layout.size() == 0 { + ptr::null() + } else { + ptr.as_ptr() + } + }This is making Allocator work with zero-sized types, which deviates from std. We should not do that without a reason. What is the reason?
Quoted 1 line
Alice
As Benno said, this makes the API closer to Rust
allocator_apiAllocator trait as opposed to deviation.There's one benefit of doing this (discussed with Danilo off-list), which is it removes ZST special casing from caller. This RFC patch simplifies
Boxhandling, and if we add this line to the safety doc
ptrdoes not need to be a pointer returned by this allocator if the layout is zero-sized.
Quoted 8 lines
Quoted 3 lines
then the
Veccan also be simplified, removing all logics handling ZST specially, except forVec::new()which it forges a well-aligned dangling pointer from nowhere.Partially, we still need the additional
LayoutforAllocator::free, which inVec::dropandIntoIter::droplooks like this:
let layout = Layout::array::<T>(self.cap).unwrap();
Adding in the diff:
- // If cap == 0 we never allocated any memory in the first place.
- if self.cap != 0 {
- // SAFETY: self.ptr was previously allocated with A.
- unsafe { A::free(self.ptr.cast()) };
- } + // This can never fail; since this `Layout` is equivalent to the one of the original + // allocation. + let layout = Layout::array::<T>(self.cap).unwrap();
+
+ // SAFETY: self.ptr was previously allocated with A.
+ unsafe { A::free(self.ptr.cast(), layout) };
Quoted 8 lines
I really dislike that this can potentially transform into
BUG(), but that's probably unrelated to this patch series.Quoted 4 lines
Best,
Gary
Gary Guo
maintainerRe: [RFC PATCH] rust: alloc: pass `old_layout` to `Allocator`
In reply to Danilo KrummrichQuoted 38 lines · On Tue, 24 Sep 2024 15:31:47 +0200 Danilo Krummrich <[email protected]> wrote:
Quoted 37 lines · Gary Guo
Quoted 22 lines · On Mon, 23 Sep 2024 15:56:28 +0200 Alice Ryhl <[email protected]> wrote:
Quoted 7 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +1
-0@@ -84,11 +92,18 @@ unsafe fn call( &self, ptr: Option<NonNull<u8>>, layout: Layout, + old_layout: Layout, flags: Flags, ) -> Result<NonNull<[u8]>, AllocError> {rust/kernel/alloc/allocator.rs unsafe fn call( · line 106
Quoted 10 lines
rust/kernel/alloc/allocator.rs +7
-1let size =aligned_size (layout); let ptr = match ptr { -Some (ptr) => ptr.as_ptr (), + Some(ptr) => { + if old_layout.size() == 0 { + ptr::null() + } else { + ptr.as_ptr() + } + }This is making Allocator work with zero-sized types, which deviates from std. We should not do that without a reason. What is the reason?
Quoted 1 line
Alice
As Benno said, this makes the API closer to Rust
allocator_apiAllocator trait as opposed to deviation.There's one benefit of doing this (discussed with Danilo off-list), which is it removes ZST special casing from caller. This RFC patch simplifies
Boxhandling, and if we add this line to the safety doc
ptrdoes not need to be a pointer returned by this allocator if the layout is zero-sized.
Quoted 9 lines
Quoted 3 lines
then the
Veccan also be simplified, removing all logics handling ZST specially, except forVec::new()which it forges a well-aligned dangling pointer from nowhere.Partially, we still need the additional
LayoutforAllocator::free, which inVec::dropandIntoIter::droplooks like this:
let layout = Layout::array::<T>(self.cap).unwrap();
You can add an invariant to Vec that the size in bytes does not exceed isize::MAX (which is already true, just not documented as invariant), and this can be changed to unwrap_unchecked.
Quoted 2 lines
I really dislike that this can potentially transform into
BUG(), but that's probably unrelated to this patch series.
Danilo Krummrich
maintainerPATCH v7 2/26 rust: alloc: separate `aligned_size` from `krealloc_aligned`
In reply to Danilo KrummrichSeparate aligned_size from krealloc_aligned.
Subsequent patches implement Allocator derivates, such as Kmalloc, that require aligned_size and replace the original krealloc_aligned.
--- rust/kernel/alloc/allocator.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-)
rust/kernel/alloc/allocator.rs +12 -7
@@ -8,6 +8,17 @@struct KernelAllocator ; +/// Returns a proper size to alloc a new object aligned to `new_layout`'s alignment. +fn aligned_size (new_layout:Layout ) ->usize { +// Customized layouts from `Layout::from_size_align()` can have size < align, so pad first. +let layout = new_layout.pad_to_align (); + +// Note that `layout.size()` (after padding) is guaranteed to be a multiple of `layout.align()` +// which together with the slab guarantees means the `krealloc` will return a properly aligned +// object (see comments in `kmalloc()` for more information). + layout.size () +} +/// Calls `krealloc` with a proper size to alloc a new object aligned to `new_layout`'s alignment. /// /// # Safety
@@ -15,13 +26,7 @@/// - `ptr` can be either null or a pointer which has been allocated by this allocator. /// - `new_layout` must have a non-zero size. pub (crate )unsafe fn krealloc_aligned (ptr: *mut u8 , new_layout:Layout , flags:Flags ) -> *mut u8 { -// Customized layouts from `Layout::from_size_align()` can have size < align, so pad first. -let layout = new_layout.pad_to_align (); - -// Note that `layout.size()` (after padding) is guaranteed to be a multiple of `layout.align()` -// which together with the slab guarantees means the `krealloc` will return a properly aligned -// object (see comments in `kmalloc()` for more information). -let size = layout.size (); +let size =aligned_size (new_layout);// SAFETY: // - `ptr` is either null or a pointer returned from a previous `k{re}alloc()` by the
Signature · 1 line
2.46.0
Danilo Krummrich
maintainerPATCH v7 3/26 rust: alloc: rename `KernelAllocator` to `Kmalloc`
In reply to Danilo KrummrichSubsequent patches implement Vmalloc and KVmalloc allocators, hence align KernelAllocator to this naming scheme.
--- rust/kernel/alloc/allocator.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
rust/kernel/alloc/allocator.rs +3 -3
@@ -6,7 +6,7 @@use core::alloc::{GlobalAlloc ,Layout };use core::ptr; -struct KernelAllocator ; +struct Kmalloc ;/// Returns a proper size to alloc a new object aligned to `new_layout`'s alignment. fn aligned_size (new_layout:Layout ) ->usize {
@@ -36,7 +36,7 @@ pub(crate) unsafe fn krealloc_aligned(ptr: *mut u8, new_layout: Layout, flags: Funsafe { bindings::krealloc (ptras *const core::ffi::c_void , size, flags.0 )as *mut u8 } } -unsafe impl GlobalAlloc for KernelAllocator { +unsafe impl GlobalAlloc for Kmalloc {unsafe fn alloc (&self, layout:Layout ) -> *mut u8 {// SAFETY: `ptr::null_mut()` is null and `layout` has a non-zero size by the function safety // requirement.
@@ -72,7 +72,7 @@ unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { }#[global_allocator] -static ALLOCATOR :KernelAllocator =KernelAllocator ; +static ALLOCATOR :Kmalloc =Kmalloc ;// See <https://github.com/rust-lang/rust/pull/86844>. #[no_mangle]
Signature · 1 line
2.46.0
Danilo Krummrich
maintainerPATCH v7 4/26 rust: alloc: implement `Allocator` for `Kmalloc`
In reply to Danilo KrummrichImplement Allocator for Kmalloc, the kernel's default allocator, typically used for objects smaller than page size.
All memory allocations made with Kmalloc end up in krealloc().
It serves as allocator for the subsequently introduced types KBox and KVec.
--- rust/kernel/alloc.rs | 2 +- rust/kernel/alloc/allocator.rs | 89 +++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-)
rust/kernel/alloc.rs +1 -1
@@ -4,7 +4,7 @@#[cfg(not(test))] #[cfg(not(testlib))] -mod allocator; +pub mod allocator;pub mod box_ext;pub mod vec_ext;
rust/kernel/alloc/allocator.rs +88 -1
106 lines not on this page · raw message
Signature · 1 line
2.46.0
Re: [PATCH v7 04/26] rust: alloc: implement `Allocator` for `Kmalloc`
Quoted 44 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +44
-0+/// # Invariants +/// +/// One of the following `krealloc`, `vrealloc`, `kvrealloc`. +struct ReallocFunc ( +unsafe extern "C" fn (*const core::ffi::c_void ,usize ,u32 ) -> *mut core::ffi::c_void , +); + +impl ReallocFunc { +// INVARIANT: `krealloc` satisfies the type invariants. +const KREALLOC :Self =Self (bindings::krealloc); + +/// # Safety +/// +/// This method has the same safety requirements as [`Allocator::realloc`]. +/// +/// # Guarantees +/// +/// This method has the same guarantees as `Allocator::realloc`. Additionally +/// - it accepts any pointer to a valid memory allocation allocated by this function. +/// - memory allocated by this function remains valid until it is passed to this function. +unsafe fn call ( + &self, + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +let size =aligned_size (layout); +let ptr =match ptr { +Some (ptr) => ptr.as_ptr (), +None => ptr::null (), + }; + +// SAFETY: +// - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc` and thus only requires that +// `ptr` is NULL or valid. +// - `ptr` is either NULL or valid by the safety requirements of this function. +// +// GUARANTEE: +// - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc`. +// - Those functions provide the guarantees of this function. +let raw_ptr =unsafe { +// If `size == 0` and `ptr != NULL` the memory behind the pointer is freed. + self.0 (ptr.cast (), size, flags.0 ).cast () + };
rust/kernel/alloc/allocator.rs pub(crate) unsafe fn krealloc_aligned(ptr: *mut u8, new_layout: Layout, flags: F · line 108
Quoted 10 lines
rust/kernel/alloc/allocator.rs +10
-0+ +let ptr =if size ==0 { +NonNull ::dangling () + }else { +NonNull ::new (raw_ptr).ok_or (AllocError )? + }; + +Ok (NonNull ::slice_from_raw_parts (ptr, size)) + } +}
I remember asking you to split this into a different commit. I think you argued that it would be better to keep it in the same commit when bisecting. I don't think that applies in this case, are there any other disadvantages?
---
Cheers,
Benno
Quoted 25 lines
rust/kernel/alloc/allocator.rs +17
-1+ +// SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that +// - memory remains valid until it is explicitly freed, +// - passing a pointer to a valid memory allocation is OK, +// - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same. +unsafe impl Allocator for Kmalloc { +#[inline] +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +// SAFETY: `ReallocFunc::call` has the same safety requirements as `Allocator::realloc`. +unsafe {ReallocFunc ::KREALLOC .call (ptr, layout, flags) } + } +} +unsafe impl GlobalAlloc for Kmalloc {unsafe fn alloc (&self, layout:Layout ) -> *mut u8 {// SAFETY: `ptr::null_mut()` is null and `layout` has a non-zero size by the function safety --2.46.0
Danilo Krummrich
maintainerRe: [PATCH v7 04/26] rust: alloc: implement `Allocator` for `Kmalloc`
Quoted 52 lines · Benno Lossin
Quoted 51 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +51
-0+/// # Invariants +/// +/// One of the following `krealloc`, `vrealloc`, `kvrealloc`. +struct ReallocFunc ( +unsafe extern "C" fn (*const core::ffi::c_void ,usize ,u32 ) -> *mut core::ffi::c_void , +); + +impl ReallocFunc { +// INVARIANT: `krealloc` satisfies the type invariants. +const KREALLOC :Self =Self (bindings::krealloc); + +/// # Safety +/// +/// This method has the same safety requirements as [`Allocator::realloc`]. +/// +/// # Guarantees +/// +/// This method has the same guarantees as `Allocator::realloc`. Additionally +/// - it accepts any pointer to a valid memory allocation allocated by this function. +/// - memory allocated by this function remains valid until it is passed to this function. +unsafe fn call ( + &self, + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +let size =aligned_size (layout); +let ptr =match ptr { +Some (ptr) => ptr.as_ptr (), +None => ptr::null (), + }; + +// SAFETY: +// - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc` and thus only requires that +// `ptr` is NULL or valid. +// - `ptr` is either NULL or valid by the safety requirements of this function. +// +// GUARANTEE: +// - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc`. +// - Those functions provide the guarantees of this function. +let raw_ptr =unsafe { +// If `size == 0` and `ptr != NULL` the memory behind the pointer is freed. + self.0 (ptr.cast (), size, flags.0 ).cast () + }; + +let ptr =if size ==0 { +NonNull ::dangling () + }else { +NonNull ::new (raw_ptr).ok_or (AllocError )? + }; +
Quoted 8 lines
rust/kernel/alloc/allocator.rs pub(crate) unsafe fn krealloc_aligned(ptr: *mut u8, new_layout: Layout, flags: F · line 108
Quoted 3 lines
rust/kernel/alloc/allocator.rs +3
-0+Ok (NonNull ::slice_from_raw_parts (ptr, size)) + } +}I remember asking you to split this into a different commit. I think you argued that it would be better to keep it in the same commit when bisecting. I don't think that applies in this case, are there any other disadvantages?
I don't really like the intermediate #[expect(dead_code)], plus it's additional work you didn't really give me a motivation for, i.e. you did not mention what would be the advantage.
But sure, I will change it for the next version.
Quoted 31 lines
---
Cheers,
BennoQuoted 25 lines
rust/kernel/alloc/allocator.rs +17
-1+ +// SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that +// - memory remains valid until it is explicitly freed, +// - passing a pointer to a valid memory allocation is OK, +// - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same. +unsafe impl Allocator for Kmalloc { +#[inline] +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +// SAFETY: `ReallocFunc::call` has the same safety requirements as `Allocator::realloc`. +unsafe {ReallocFunc ::KREALLOC .call (ptr, layout, flags) } + } +} +unsafe impl GlobalAlloc for Kmalloc {unsafe fn alloc (&self, layout:Layout ) -> *mut u8 {// SAFETY: `ptr::null_mut()` is null and `layout` has a non-zero size by the function safety --2.46.0
Re: [PATCH v7 04/26] rust: alloc: implement `Allocator` for `Kmalloc`
Quoted 62 lines · Danilo Krummrich
Quoted 52 lines · Benno Lossin
Quoted 51 lines · Danilo Krummrich
rust/kernel/alloc/allocator.rs +51
-0+/// # Invariants +/// +/// One of the following `krealloc`, `vrealloc`, `kvrealloc`. +struct ReallocFunc ( +unsafe extern "C" fn (*const core::ffi::c_void ,usize ,u32 ) -> *mut core::ffi::c_void , +); + +impl ReallocFunc { +// INVARIANT: `krealloc` satisfies the type invariants. +const KREALLOC :Self =Self (bindings::krealloc); + +/// # Safety +/// +/// This method has the same safety requirements as [`Allocator::realloc`]. +/// +/// # Guarantees +/// +/// This method has the same guarantees as `Allocator::realloc`. Additionally +/// - it accepts any pointer to a valid memory allocation allocated by this function. +/// - memory allocated by this function remains valid until it is passed to this function. +unsafe fn call ( + &self, + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +let size =aligned_size (layout); +let ptr =match ptr { +Some (ptr) => ptr.as_ptr (), +None => ptr::null (), + }; + +// SAFETY: +// - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc` and thus only requires that +// `ptr` is NULL or valid. +// - `ptr` is either NULL or valid by the safety requirements of this function. +// +// GUARANTEE: +// - `self.0` is one of `krealloc`, `vrealloc`, `kvrealloc`. +// - Those functions provide the guarantees of this function. +let raw_ptr =unsafe { +// If `size == 0` and `ptr != NULL` the memory behind the pointer is freed. + self.0 (ptr.cast (), size, flags.0 ).cast () + }; + +let ptr =if size ==0 { +NonNull ::dangling () + }else { +NonNull ::new (raw_ptr).ok_or (AllocError )? + }; +Quoted 8 lines
rust/kernel/alloc/allocator.rs pub(crate) unsafe fn krealloc_aligned(ptr: *mut u8, new_layout: Layout, flags: F · line 108
Quoted 3 lines
rust/kernel/alloc/allocator.rs +3
-0+Ok (NonNull ::slice_from_raw_parts (ptr, size)) + } +}I remember asking you to split this into a different commit. I think you argued that it would be better to keep it in the same commit when bisecting. I don't think that applies in this case, are there any other disadvantages?
Quoted 3 lines
I don't really like the intermediate
#[expect(dead_code)], plus it's additional work you didn't really give me a motivation for, i.e. you did not mention what would be the advantage.
The advantage would be that it's easier to review (granted it probably is a bit late for that). I got confused a couple of times (but that's probably on me).
Quoted 1 line
But sure, I will change it for the next version.
Thanks
---
Cheers,
Benno
Danilo Krummrich
maintainerPATCH v7 5/26 rust: alloc: add module `allocator_test`
In reply to Danilo KrummrichAllocators, such as Kmalloc, will be used by e.g. Box and Vec in subsequent patches, and hence this dependency propagates throughout the whole kernel.
Add the allocator_test module that provides an empty implementation for all Allocators in the kernel, such that we don't break the rusttest make target in subsequent patches.
--- rust/kernel/alloc.rs | 9 +++++++-- rust/kernel/alloc/allocator_test.rs | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 rust/kernel/alloc/allocator_test.rs
rust/kernel/alloc.rs +7 -2
@@ -2,12 +2,17 @@//! Extensions to the [`alloc`] crate. -#[cfg(not(test))] -#[cfg(not(testlib))] +#[cfg(not(any(test, testlib)))] pub mod allocator;pub mod box_ext;pub mod vec_ext; +#[cfg(any(test, testlib))] +pub mod allocator_test; + +#[cfg(any(test, testlib))] +pub use self::allocator_testas allocator; +/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ;
rust/kernel/alloc/allocator_test.rs new file +19 -0
@@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 + +#![allow(missing_docs)] + +use super ::{AllocError ,Allocator ,Flags }; +use core::alloc::Layout ; +use core::ptr::NonNull ; + +pub struct Kmalloc ; + +unsafe impl Allocator for Kmalloc { +unsafe fn realloc ( + _ptr:Option <NonNull <u8 >>, + _layout:Layout , + _flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +panic! (); + } +}
Signature · 1 line
2.46.0
Danilo Krummrich
maintainerPATCH v7 6/26 rust: alloc: implement `Vmalloc` allocator
In reply to Danilo KrummrichImplement Allocator for Vmalloc, the kernel's virtually contiguous allocator, typically used for larger objects, (much) larger than page size.
All memory allocations made with Vmalloc end up in vrealloc().
--- rust/helpers/helpers.c | 1 + rust/helpers/vmalloc.c | 9 ++++++++ rust/kernel/alloc/allocator.rs | 36 +++++++++++++++++++++++++++++ rust/kernel/alloc/allocator_test.rs | 1 + 4 files changed, 47 insertions(+) create mode 100644 rust/helpers/vmalloc.c
rust/helpers/helpers.c +1 -0
@@ -22,5 +22,6 @@#include "spinlock.c" #include "task.c" #include "uaccess.c" +#include "vmalloc.c" #include "wait.c" #include "workqueue.c"
rust/helpers/vmalloc.c new file +9 -0
@@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/vmalloc.h> + +void * __must_check __realloc_size(2) +rust_helper_vrealloc(const void *p, size_t size, gfp_t flags) +{ +return vrealloc (p, size, flags); +}
rust/kernel/alloc/allocator.rs +36 -0
@@ -15,6 +15,7 @@use crate ::alloc::{AllocError ,Allocator };use crate ::bindings; +use crate ::pr_warn;/// The contiguous kernel allocator. ///
@@ -24,6 +25,15 @@/// For more details see [self]. pub struct Kmalloc ; +/// The virtually contiguous kernel allocator. +/// +/// `Vmalloc` allocates pages from the page level allocator and maps them into the contiguous kernel +/// virtual space. It is typically used for large allocations. The memory allocated with this +/// allocator is not physically contiguous. +/// +/// For more details see [self]. +pub struct Vmalloc ; +/// Returns a proper size to alloc a new object aligned to `new_layout`'s alignment. fn aligned_size (new_layout:Layout ) ->usize {// Customized layouts from `Layout::from_size_align()` can have size < align, so pad first.
@@ -63,6 +73,9 @@ impl ReallocFunc {// INVARIANT: `krealloc` satisfies the type invariants. const KREALLOC :Self =Self (bindings::krealloc); +// INVARIANT: `vrealloc` satisfies the type invariants. +const VREALLOC :Self =Self (bindings::vrealloc); +/// # Safety /// /// This method has the same safety requirements as [`Allocator::realloc`].
@@ -158,6 +171,29 @@ unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { } } +// SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that +// - memory remains valid until it is explicitly freed, +// - passing a pointer to a valid memory allocation is OK, +// - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same. +unsafe impl Allocator for Vmalloc { +#[inline] +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +// TODO: Support alignments larger than PAGE_SIZE. +if layout.align () > bindings::PAGE_SIZE { +pr_warn! ("Vmalloc does not support alignments larger than PAGE_SIZE yet.\n" ); +return Err (AllocError ); + } + +// SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously +// allocated with this `Allocator`. +unsafe {ReallocFunc ::VREALLOC .call (ptr, layout, flags) } + } +} +#[global_allocator] static ALLOCATOR :Kmalloc =Kmalloc ;
rust/kernel/alloc/allocator_test.rs +1 -0
@@ -7,6 +7,7 @@use core::ptr::NonNull ;pub struct Kmalloc ; +pub type Vmalloc =Kmalloc ;unsafe impl Allocator for Kmalloc { unsafe fn realloc(
Signature · 1 line
2.46.0
Re: [PATCH v7 06/26] rust: alloc: implement `Vmalloc` allocator
Quoted 8 lines · Danilo Krummrich
Implement
AllocatorforVmalloc, the kernel's virtually contiguous allocator, typically used for larger objects, (much) larger than page size.All memory allocations made with
Vmallocend up invrealloc().Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 7 lines
--- rust/helpers/helpers.c | 1 + rust/helpers/vmalloc.c | 9 ++++++++ rust/kernel/alloc/allocator.rs | 36 +++++++++++++++++++++++++++++ rust/kernel/alloc/allocator_test.rs | 1 + 4 files changed, 47 insertions(+) create mode 100644 rust/helpers/vmalloc.c
---
Cheers,
Benno
Danilo Krummrich
maintainerPATCH v7 7/26 rust: alloc: implement `KVmalloc` allocator
In reply to Danilo KrummrichImplement Allocator for KVmalloc, an Allocator that tries to allocate memory wth kmalloc first and, on failure, falls back to vmalloc.
All memory allocations made with KVmalloc end up in kvrealloc_noprof(); all frees in kvfree().
--- rust/helpers/slab.c | 6 +++++ rust/kernel/alloc/allocator.rs | 35 +++++++++++++++++++++++++++++ rust/kernel/alloc/allocator_test.rs | 1 + 3 files changed, 42 insertions(+)
rust/helpers/slab.c +6 -0
@@ -7,3 +7,9 @@ rust_helper_krealloc(const void *objp, size_t new_size, gfp_t flags) {return krealloc (objp, new_size, flags); } + +void * __must_check __realloc_size(2) +rust_helper_kvrealloc(const void *p, size_t size, gfp_t flags) +{ +return kvrealloc (p, size, flags); +}
rust/kernel/alloc/allocator.rs +35 -0
@@ -34,6 +34,15 @@/// For more details see [self]. pub struct Vmalloc ; +/// The kvmalloc kernel allocator. +/// +/// `KVmalloc` attempts to allocate memory with `Kmalloc` first, but falls back to `Vmalloc` upon +/// failure. This allocator is typically used when the size for the requested allocation is not +/// known and may exceed the capabilities of `Kmalloc`. +/// +/// For more details see [self]. +pub struct KVmalloc ; +/// Returns a proper size to alloc a new object aligned to `new_layout`'s alignment. fn aligned_size (new_layout:Layout ) ->usize {// Customized layouts from `Layout::from_size_align()` can have size < align, so pad first.
@@ -76,6 +85,9 @@ impl ReallocFunc {// INVARIANT: `vrealloc` satisfies the type invariants. const VREALLOC :Self =Self (bindings::vrealloc); +// INVARIANT: `kvrealloc` satisfies the type invariants. +const KVREALLOC :Self =Self (bindings::kvrealloc); +/// # Safety /// /// This method has the same safety requirements as [`Allocator::realloc`].
@@ -194,6 +206,29 @@ unsafe fn realloc( } } +// SAFETY: `realloc` delegates to `ReallocFunc::call`, which guarantees that +// - memory remains valid until it is explicitly freed, +// - passing a pointer to a valid memory allocation is OK, +// - `realloc` satisfies the guarantees, since `ReallocFunc::call` has the same. +unsafe impl Allocator for KVmalloc { +#[inline] +unsafe fn realloc ( + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , + ) ->Result <NonNull <[u8 ]>,AllocError > { +// TODO: Support alignments larger than PAGE_SIZE. +if layout.align () > bindings::PAGE_SIZE { +pr_warn! ("KVmalloc does not support alignments larger than PAGE_SIZE yet.\n" ); +return Err (AllocError ); + } + +// SAFETY: If not `None`, `ptr` is guaranteed to point to valid memory, which was previously +// allocated with this `Allocator`. +unsafe {ReallocFunc ::KVREALLOC .call (ptr, layout, flags) } + } +} +#[global_allocator] static ALLOCATOR :Kmalloc =Kmalloc ;
rust/kernel/alloc/allocator_test.rs +1 -0
@@ -8,6 +8,7 @@pub struct Kmalloc ;pub type Vmalloc =Kmalloc ; +pub type KVmalloc =Kmalloc ;unsafe impl Allocator for Kmalloc { unsafe fn realloc(
Signature · 1 line
2.46.0
Re: [PATCH v7 07/26] rust: alloc: implement `KVmalloc` allocator
Quoted 7 lines · Danilo Krummrich
Implement
AllocatorforKVmalloc, anAllocatorthat tries to allocate memory wthkmallocfirst and, on failure, falls back tovmalloc.All memory allocations made with
KVmallocend up inkvrealloc_noprof(); all frees inkvfree().
Quoted 7 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/helpers/slab.c | 6 +++++ rust/kernel/alloc/allocator.rs | 35 +++++++++++++++++++++++++++++ rust/kernel/alloc/allocator_test.rs | 1 + 3 files changed, 42 insertions(+)
---
Cheers,
Benno
Danilo Krummrich
maintainerPATCH v7 8/26 rust: alloc: add __GFP_NOWARN to `Flags`
In reply to Danilo KrummrichSome test cases in subsequent patches provoke allocation failures. Add __GFP_NOWARN to enable test cases to silence unpleasant warnings.
--- rust/bindings/bindings_helper.h | 1 + rust/kernel/alloc.rs | 5 +++++ 2 files changed, 6 insertions(+)
rust/bindings/bindings_helper.h +1 -0
@@ -31,4 +31,5 @@ const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT;const gfp_t RUST_CONST_HELPER_GFP_NOWAIT =GFP_NOWAIT ;const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO;const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM; +const gfp_t RUST_CONST_HELPER___GFP_NOWARN = ___GFP_NOWARN;const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL =BLK_FEAT_ROTATIONAL ;
rust/kernel/alloc.rs +5 -0
@@ -91,6 +91,11 @@ pub mod flags {/// use any filesystem callback. It is very likely to fail to allocate memory, even for very /// small allocations. pub const GFP_NOWAIT :Flags =Flags (bindings::GFP_NOWAIT ); + +/// Suppresses allocation failure reports. +/// +/// This is normally or'd with other flags. +pub const __GFP_NOWARN:Flags =Flags (bindings::__GFP_NOWARN); }/// The kernel's [`Allocator`] trait.
Signature · 1 line
2.46.0
Re: [PATCH v7 08/26] rust: alloc: add __GFP_NOWARN to `Flags`
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:44 +0200 Danilo Krummrich <[email protected]> wrote:
Some test cases in subsequent patches provoke allocation failures. Add
__GFP_NOWARNto enable test cases to silence unpleasant warnings.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 31 lines
--- rust/bindings/bindings_helper.h | 1 + rust/kernel/alloc.rs | 5 +++++ 2 files changed, 6 insertions(+)rust/bindings/bindings_helper.h +1
-0@@ -31,4 +31,5 @@ const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT;const gfp_t RUST_CONST_HELPER_GFP_NOWAIT =GFP_NOWAIT ;const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO;const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM; +const gfp_t RUST_CONST_HELPER___GFP_NOWARN = ___GFP_NOWARN;const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL =BLK_FEAT_ROTATIONAL ;rust/kernel/alloc.rs +5
-0@@ -91,6 +91,11 @@ pub mod flags {/// use any filesystem callback. It is very likely to fail to allocate memory, even for very /// small allocations. pub const GFP_NOWAIT :Flags =Flags (bindings::GFP_NOWAIT ); + +/// Suppresses allocation failure reports. +/// +/// This is normally or'd with other flags. +pub const __GFP_NOWARN:Flags =Flags (bindings::__GFP_NOWARN); }/// The kernel's [`Allocator`] trait.
Danilo Krummrich
maintainerPATCH v7 9/26 rust: alloc: implement kernel `Box`
In reply to Danilo KrummrichBox provides the simplest way to allocate memory for a generic type with one of the kernel's allocators, e.g. Kmalloc, Vmalloc or KVmalloc.
In contrast to Rust's Box type, the kernel Box type considers the kernel's GFP flags for all appropriate functions, always reports allocation failures through Result<_, AllocError> and remains independent from unstable features.
--- rust/kernel/alloc.rs | 6 + rust/kernel/alloc/kbox.rs | 465 ++++++++++++++++++++++++++++++++++++++ rust/kernel/prelude.rs | 2 +- 3 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/alloc/kbox.rs
rust/kernel/alloc.rs +6 -0
@@ -5,6 +5,7 @@#[cfg(not(any(test, testlib)))] pub mod allocator;pub mod box_ext; +pub mod kbox;pub mod vec_ext;#[cfg(any(test, testlib))]
@@ -13,6 +14,11 @@#[cfg(any(test, testlib))] pub use self::allocator_testas allocator; +pub use self::kbox::Box ; +pub use self::kbox::KBox ; +pub use self::kbox::KVBox ; +pub use self::kbox::VBox ; +/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ;
rust/kernel/alloc/kbox.rs new file +465 -0
465 lines not on this page · raw message
rust/kernel/prelude.rs +1 -1
@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{box_ext::BoxExt , flags::*, vec_ext::VecExt }; +pub use crate ::alloc::{box_ext::BoxExt , flags::*, vec_ext::VecExt ,KBox ,KVBox ,VBox };#[doc(no_inline)] pub use alloc::{boxed::Box , vec::Vec };
Signature · 1 line
2.46.0
Re: [PATCH v7 09/26] rust: alloc: implement kernel `Box`
Quoted 9 lines · Danilo Krummrich
Boxprovides the simplest way to allocate memory for a generic type with one of the kernel's allocators, e.g.Kmalloc,VmallocorKVmalloc.In contrast to Rust's
Boxtype, the kernelBoxtype considers the kernel's GFP flags for all appropriate functions, always reports allocation failures throughResult<_, AllocError>and remains independent from unstable features.
Quoted 7 lines
Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 6 + rust/kernel/alloc/kbox.rs | 465 ++++++++++++++++++++++++++++++++++++++ rust/kernel/prelude.rs | 2 +- 3 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/alloc/kbox.rs
I have two nits below. I think the safety documentation can be improved later, when we have decided on a better approach (I have to to spend a significant amount of time coming up with a suggestion and even then I am not satisfied with it). So
---
Cheers,
Benno
rust/kernel/alloc/kbox.rs line 126
Quoted 10 lines
rust/kernel/alloc/kbox.rs new file +10
-0+impl <T ,A >Box <T ,A > +where +T : ?Sized , +A :Allocator , +{ +/// Creates a new `Box<T, A>` from a raw pointer. +/// +/// # Safety +/// +/// For non-ZSTs, `raw` must point at an allocation allocated with `A`that is sufficiently
Missing space after "with A".
Quoted 22 lines
rust/kernel/alloc/kbox.rs new file +22
-0+/// aligned for and holds a valid `T`. The caller passes ownership of the allocation to the +/// `Box`. +/// +/// For ZSTs, `raw` must be a dangling, well aligned pointer. +#[inline] +pub const unsafe fn from_raw (raw: *mut T ) ->Self { +// INVARIANT: Validity of `raw` is guaranteed by the safety preconditions of this function. +// SAFETY: By the safety preconditions of this function, `raw` is not a NULL pointer. +Self (unsafe {NonNull ::new_unchecked (raw) },PhantomData ::<A >) + } + +/// Consumes the `Box<T, A>` and returns a raw pointer. +/// +/// This will not run the destructor of `T` and for non-ZSTs the allocation will stay alive +/// indefinitely. Use [`Box::from_raw`] to recover the [`Box`], drop the value and free the +/// allocation, if any. +/// +/// # Examples +/// +/// ``` +/// let x = KBox::new(24, GFP_KERNEL)?; +/// let ptr = KBox::into_raw(x);
rust/kernel/alloc/kbox.rs line 158
Quoted 10 lines
rust/kernel/alloc/kbox.rs new file +10
-0+/// let x = unsafe { KBox::from_raw(ptr) }; +/// +/// assert_eq!(*x, 24); +/// # Ok::<(), Error>(()) +/// ``` +#[inline] +pub fn into_raw (b:Self ) -> *mut T { +let b =ManuallyDrop ::new (b); + + b.0 .as_ptr ()
Why not a single line ManuallyDrop::new().0.as_ptr()?
Quoted 12 lines
rust/kernel/alloc/kbox.rs new file +12
-0+ } + +/// Consumes and leaks the `Box<T, A>` and returns a mutable reference. +/// +/// See [Box::into_raw] for more details. +#[inline] +pub fn leak <'a>(b:Self ) -> &'amut T { +// SAFETY: `Box::into_raw` always returns a properly aligned and dereferenceable pointer +// which points to an initialized instance of `T`. +unsafe { &mut *Box ::into_raw (b) } + } +}
Gary Guo
maintainerRe: [PATCH v7 09/26] rust: alloc: implement kernel `Box`
In reply to Danilo KrummrichQuoted 172 lines · On Thu, 12 Sep 2024 00:52:45 +0200 Danilo Krummrich <[email protected]> wrote:
Boxprovides the simplest way to allocate memory for a generic type with one of the kernel's allocators, e.g.Kmalloc,VmallocorKVmalloc.In contrast to Rust's
Boxtype, the kernelBoxtype considers the kernel's GFP flags for all appropriate functions, always reports allocation failures throughResult<_, AllocError>and remains independent from unstable features.Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 6 + rust/kernel/alloc/kbox.rs | 465 ++++++++++++++++++++++++++++++++++++++ rust/kernel/prelude.rs | 2 +- 3 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/alloc/kbox.rsrust/kernel/alloc.rs +6
-0@@ -5,6 +5,7 @@#[cfg(not(any(test, testlib)))] pub mod allocator;pub mod box_ext; +pub mod kbox;pub mod vec_ext;#[cfg(any(test, testlib))] @@ -13,6 +14,11 @@#[cfg(any(test, testlib))] pub use self::allocator_testas allocator; +pub use self::kbox::Box ; +pub use self::kbox::KBox ; +pub use self::kbox::KVBox ; +pub use self::kbox::VBox ; +/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ;rust/kernel/alloc/kbox.rs new file +125
-0@@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Implementation of [`Box`]. + +#[allow(unused_imports)] // Used in doc comments. +use super ::allocator::{KVmalloc ,Kmalloc ,Vmalloc }; +use super ::{AllocError ,Allocator ,Flags }; +use core::fmt; +use core::marker::PhantomData ; +use core::mem::ManuallyDrop ; +use core::mem::MaybeUninit ; +use core::ops::{Deref ,DerefMut }; +use core::pin::Pin ; +use core::ptr::NonNull ; +use core::result::Result ; + +use crate ::init::{InPlaceInit ,InPlaceWrite ,Init ,PinInit }; +use crate ::types::ForeignOwnable ; + +/// The kernel's [`Box`] type -- a heap allocation for a single value of type `T`. +/// +/// This is the kernel's version of the Rust stdlib's `Box`. There are several of differences, +/// for example no `noalias` attribute is emitted and partially moving out of a `Box` is not +/// supported. There are also several API differences, e.g. `Box` always requires an [`Allocator`] +/// implementation to be passed as generic, page [`Flags`] when allocating memory and all functions +/// that may allocate memory are fallible. +/// +/// `Box` works with any of the kernel's allocators, e.g. [`Kmalloc`], [`Vmalloc`] or [`KVmalloc`]. +/// There are aliases for `Box` with these allocators ([`KBox`], [`VBox`], [`KVBox`]). +/// +/// When dropping a [`Box`], the value is also dropped and the heap memory is automatically freed. +/// +/// # Examples +/// +/// ``` +/// let b = KBox::<u64>::new(24_u64, GFP_KERNEL)?; +/// +/// assert_eq!(*b, 24_u64); +/// # Ok::<(), Error>(()) +/// ``` +/// +/// ``` +/// # use kernel::bindings; +/// const SIZE: usize = bindings::KMALLOC_MAX_SIZE as usize + 1; +/// struct Huge([u8; SIZE]); +/// +/// assert!(KBox::<Huge>::new_uninit(GFP_KERNEL | __GFP_NOWARN).is_err()); +/// ``` +/// +/// ``` +/// # use kernel::bindings; +/// const SIZE: usize = bindings::KMALLOC_MAX_SIZE as usize + 1; +/// struct Huge([u8; SIZE]); +/// +/// assert!(KVBox::<Huge>::new_uninit(GFP_KERNEL).is_ok()); +/// ``` +/// +/// # Invariants +/// +/// `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)] +pub struct Box <T : ?Sized ,A :Allocator >(NonNull <T >,PhantomData <A >); + +/// Type alias for [`Box`] with a [`Kmalloc`] allocator. +/// +/// # Examples +/// +/// ``` +/// let b = KBox::new(24_u64, GFP_KERNEL)?; +/// +/// assert_eq!(*b, 24_u64); +/// # Ok::<(), Error>(()) +/// ``` +pub type KBox <T > =Box <T ,super ::allocator::Kmalloc >; + +/// Type alias for [`Box`] with a [`Vmalloc`] allocator. +/// +/// # Examples +/// +/// ``` +/// let b = VBox::new(24_u64, GFP_KERNEL)?; +/// +/// assert_eq!(*b, 24_u64); +/// # Ok::<(), Error>(()) +/// ``` +pub type VBox <T > =Box <T ,super ::allocator::Vmalloc >; + +/// Type alias for [`Box`] with a [`KVmalloc`] allocator. +/// +/// # Examples +/// +/// ``` +/// let b = KVBox::new(24_u64, GFP_KERNEL)?; +/// +/// assert_eq!(*b, 24_u64); +/// # Ok::<(), Error>(()) +/// ``` +pub type KVBox <T > =Box <T ,super ::allocator::KVmalloc >; + +// SAFETY: `Box` is `Send` if `T` is `Send` because the `Box` owns a `T`. +unsafe impl <T ,A >Send for Box <T ,A > +where +T :Send + ?Sized , +A :Allocator , +{ +} + +// SAFETY: `Box` is `Sync` if `T` is `Sync` because the `Box` owns a `T`. +unsafe impl <T ,A >Sync for Box <T ,A > +where +T :Sync + ?Sized , +A :Allocator , +{ +} + +impl <T ,A >Box <T ,A > +where +T : ?Sized , +A :Allocator , +{ +/// Creates a new `Box<T, A>` from a raw pointer. +/// +/// # Safety +///
rust/kernel/alloc/kbox.rs line 135
Quoted 10 lines
rust/kernel/alloc/kbox.rs new file +10
-0+/// For non-ZSTs, `raw` must point at an allocation allocated with `A`that is sufficiently +/// aligned for and holds a valid `T`. The caller passes ownership of the allocation to the +/// `Box`. +/// +/// For ZSTs, `raw` must be a dangling, well aligned pointer. +#[inline] +pub const unsafe fn from_raw (raw: *mut T ) ->Self { +// INVARIANT: Validity of `raw` is guaranteed by the safety preconditions of this function. +// SAFETY: By the safety preconditions of this function, `raw` is not a NULL pointer. +Self (unsafe {NonNull ::new_unchecked (raw) },PhantomData ::<A >)
nit: the turbofish here is not necessary.
Self(unsafe { NonNull::new_unchecked(raw) }, PhantomData)
Quoted 39 lines
rust/kernel/alloc/kbox.rs new file +39
-0+ } + +/// Consumes the `Box<T, A>` and returns a raw pointer. +/// +/// This will not run the destructor of `T` and for non-ZSTs the allocation will stay alive +/// indefinitely. Use [`Box::from_raw`] to recover the [`Box`], drop the value and free the +/// allocation, if any. +/// +/// # Examples +/// +/// ``` +/// let x = KBox::new(24, GFP_KERNEL)?; +/// let ptr = KBox::into_raw(x); +/// let x = unsafe { KBox::from_raw(ptr) }; +/// +/// assert_eq!(*x, 24); +/// # Ok::<(), Error>(()) +/// ``` +#[inline] +pub fn into_raw (b:Self ) -> *mut T { +let b =ManuallyDrop ::new (b); + + b.0 .as_ptr () + } + +/// Consumes and leaks the `Box<T, A>` and returns a mutable reference. +/// +/// See [Box::into_raw] for more details. +#[inline] +pub fn leak <'a>(b:Self ) -> &'amut T { +// SAFETY: `Box::into_raw` always returns a properly aligned and dereferenceable pointer +// which points to an initialized instance of `T`. +unsafe { &mut *Box ::into_raw (b) } + } +} + +impl <T ,A >Box <MaybeUninit <T >,A > +where +A :Allocator ,
rust/kernel/alloc/kbox.rs line 184
Quoted 10 lines
rust/kernel/alloc/kbox.rs new file +10
-0+{ +/// Converts a `Box<MaybeUninit<T>, A>` to a `Box<T, A>`. +/// +/// It is undefined behavior to call this function while the value inside of `b` is not yet +/// fully initialized. +/// +/// # Safety +/// +/// Callers must ensure that the value inside of `b` is in an initialized state. +pub unsafe fn assume_init (b:Self ) ->Box <T ,A > {
I think this can take self and shadow the underlying MaybeUninit::assume_init -- the rationale being that the MaybeUninit::assume_init takes self so it cannot actually be used for Box<MaybeUninit<T>>. Rust standard library also takes the approach (with the unstable box assume_init takes self).
Note the argument of Box not having self methods don't apply here, as it doesn't deref to an arbitrary type, but rather always MaybeUninit.
Quoted 4 lines
rust/kernel/alloc/kbox.rs new file +4
-0+let raw =Self ::into_raw (b); + +// SAFETY: `raw` comes from a previous call to `Box::into_raw`. By the safety requirements +// of this function, the value inside the `Box` is in an initialized state. Hence, it is
rust/kernel/alloc/kbox.rs line 198
Quoted 10 lines
rust/kernel/alloc/kbox.rs new file +10
-0+// safe to reconstruct the `Box` as `Box<T, A>`. +unsafe {Box ::from_raw (raw.cast ()) } + } + +/// Writes the value and converts to `Box<T, A>`. +pub fn write (mut b:Self , value:T ) ->Box <T ,A > { + (*b).write (value); +// SAFETY: We've just initialized `b`'s value. +unsafe {Self ::assume_init (b) } + }
same here.
Quoted 18 lines
rust/kernel/alloc/kbox.rs new file +18
-0+} + +impl <T ,A >Box <T ,A > +where +A :Allocator , +{ +fn is_zst () ->bool { + core::mem::size_of ::<T >() ==0 + } + +/// Creates a new `Box<T, A>` and initializes its contents with `x`. +/// +/// New memory is allocated with `A`. The allocation may fail, in which case an error is +/// returned. For ZSTs no memory is allocated. +pub fn new (x:T , flags:Flags ) ->Result <Self ,AllocError > { +let b =Self ::new_uninit (flags)?; +Ok (Box ::write (b, x)) + }
Danilo Krummrich
maintainerPATCH v7 10/26 rust: treewide: switch to our kernel `Box` type
In reply to Danilo KrummrichNow that we got the kernel Box type in place, convert all existing Box users to make use of it.
--- drivers/block/rnull.rs | 4 +-- rust/kernel/init.rs | 51 ++++++++++++++++--------------- rust/kernel/init/__internal.rs | 2 +- rust/kernel/rbtree.rs | 49 ++++++++++++++++------------- rust/kernel/sync/arc.rs | 17 +++++------ rust/kernel/sync/condvar.rs | 4 +-- rust/kernel/sync/lock/mutex.rs | 2 +- rust/kernel/sync/lock/spinlock.rs | 2 +- rust/kernel/workqueue.rs | 20 ++++++------ rust/macros/lib.rs | 6 ++-- 10 files changed, 81 insertions(+), 76 deletions(-)
drivers/block/rnull.rs +2 -2
@@ -32,7 +32,7 @@ }struct NullBlkModule { - _disk:Pin <Box <Mutex <GenDisk <NullBlkDevice >>>>, + _disk:Pin <KBox <Mutex <GenDisk <NullBlkDevice >>>>, }impl kernel::Module for NullBlkModule {
@@ -47,7 +47,7 @@ fn init(_module: &'static ThisModule) -> Result<Self> { .rotational(false) .build (format_args! ("rnullb{}" ,0 ), tagset)?; -let disk =Box ::pin_init (new_mutex! (disk,"nullb:disk" ), flags::GFP_KERNEL )?; +let disk =KBox ::pin_init (new_mutex! (disk,"nullb:disk" ), flags::GFP_KERNEL )?;Ok (Self { _disk: disk }) }
rust/kernel/init.rs +26 -25
178 lines not on this page · raw message
rust/kernel/init/__internal.rs +1 -1
@@ -102,7 +102,7 @@ fn make_closure<F, O, E>(self, f: F) -> F } } -pub struct AllData <T : ?Sized >(PhantomData <fn (Box <T >) ->Box <T >>); +pub struct AllData <T : ?Sized >(PhantomData <fn (KBox <T >) ->KBox <T >>);impl <T : ?Sized >Clone for AllData <T > {fn clone (&self) ->Self {
rust/kernel/rbtree.rs +27 -22
139 lines not on this page · raw message
rust/kernel/sync/arc.rs +8 -9
@@ -17,13 +17,12 @@//! [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html use crate ::{ - alloc::{box_ext::BoxExt ,AllocError ,Flags }, + alloc::{AllocError ,Flags ,KBox }, bindings, init::{self,InPlaceInit ,Init ,PinInit }, try_init, types::{ForeignOwnable ,Opaque }, }; -use alloc::boxed::Box ;use core::{ alloc::Layout , fmt,
@@ -204,11 +203,11 @@ pub fn new(contents: T, flags: Flags) -> Result<Self, AllocError> { data: contents, }; -let inner = <Box <_ >as BoxExt <_ >>::new (value, flags)?; +let inner =KBox ::new (value, flags)?;// SAFETY: We just created `inner` with a reference count of 1, which is owned by the new // `Arc` object. -Ok (unsafe {Self ::from_inner (Box ::leak (inner).into ()) }) +Ok (unsafe {Self ::from_inner (KBox ::leak (inner).into ()) }) } }
@@ -401,8 +400,8 @@ fn drop(&mut self) {if is_zero {// The count reached zero, we must free the memory. // -// SAFETY: The pointer was initialised from the result of `Box::leak`. -unsafe {drop (Box ::from_raw (self.ptr.as_ptr ())) }; +// SAFETY: The pointer was initialised from the result of `KBox::leak`. +unsafe {drop (KBox ::from_raw (self.ptr.as_ptr ())) }; } } }
@@ -647,7 +646,7 @@ pub fn new(value: T, flags: Flags) -> Result<Self, AllocError> {/// Tries to allocate a new [`UniqueArc`] instance whose contents are not initialised yet. pub fn new_uninit (flags:Flags ) ->Result <UniqueArc <MaybeUninit <T >>,AllocError > {// INVARIANT: The refcount is initialised to a non-zero value. - let inner = Box::try_init::<AllocError>( + let inner = KBox::try_init::<AllocError>( try_init!(ArcInner { // SAFETY: There are no safety requirements for this FFI call. refcount: Opaque::new(unsafe { bindings::REFCOUNT_INIT(1) }),
@@ -657,8 +656,8 @@ pub fn new_uninit(flags: Flags) -> Result<UniqueArc<MaybeUninit<T>>, AllocError> )?;Ok (UniqueArc {// INVARIANT: The newly-created object has a refcount of 1. -// SAFETY: The pointer from the `Box` is valid. - inner:unsafe {Arc ::from_inner (Box ::leak (inner).into ()) }, +// SAFETY: The pointer from the `KBox` is valid. + inner:unsafe {Arc ::from_inner (KBox ::leak (inner).into ()) }, }) } }
rust/kernel/sync/condvar.rs +2 -2
@@ -70,8 +70,8 @@ macro_rules! new_condvar {/// } /// /// /// Allocates a new boxed `Example`. -/// fn new_example() -> Result<Pin<Box<Example>>> { -/// Box::pin_init(pin_init!(Example { +/// fn new_example() -> Result<Pin<KBox<Example>>> { +/// KBox::pin_init(pin_init!(Example { /// value <- new_mutex!(0), /// value_changed <- new_condvar!(), /// }), GFP_KERNEL)
rust/kernel/sync/lock/mutex.rs +1 -1
@@ -58,7 +58,7 @@ macro_rules! new_mutex {/// } /// /// // Allocate a boxed `Example`. -/// let e = Box::pin_init(Example::new(), GFP_KERNEL)?; +/// let e = KBox::pin_init(Example::new(), GFP_KERNEL)?; /// assert_eq!(e.c, 10); /// assert_eq!(e.d.lock().a, 20); /// assert_eq!(e.d.lock().b, 30);
rust/kernel/sync/lock/spinlock.rs +1 -1
@@ -56,7 +56,7 @@ macro_rules! new_spinlock {/// } /// /// // Allocate a boxed `Example`. -/// let e = Box::pin_init(Example::new(), GFP_KERNEL)?; +/// let e = KBox::pin_init(Example::new(), GFP_KERNEL)?; /// assert_eq!(e.c, 10); /// assert_eq!(e.d.lock().a, 20); /// assert_eq!(e.d.lock().b, 30);
rust/kernel/workqueue.rs +10 -10
@@ -216,7 +216,7 @@ pub fn try_spawn<T: 'static + Send + FnOnce()>( func: Some(func), }); - self.enqueue (Box ::pin_init (init, flags).map_err (|_|AllocError )?); + self.enqueue (KBox ::pin_init (init, flags).map_err (|_|AllocError )?);Ok (()) } }
@@ -239,9 +239,9 @@ fn project(self: Pin<&mut Self>) -> &mut Option<T> { }impl <T :FnOnce ()>WorkItem for ClosureWork <T > { -type Pointer =Pin <Box <Self >>; +type Pointer =Pin <KBox <Self >>; -fn run (mut this:Pin <Box <Self >>) { +fn run (mut this:Pin <KBox <Self >>) {if let Some (func) = this.as_mut ().project ().take () { (func)() }
@@ -297,7 +297,7 @@ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput/// Defines the method that should be called directly when a work item is executed. /// -/// This trait is implemented by `Pin<Box<T>>` and [`Arc<T>`], and is mainly intended to be +/// This trait is implemented by `Pin<KBox<T>>` and [`Arc<T>`], and is mainly intended to be /// implemented for smart pointer types. For your own structs, you would implement [`WorkItem`] /// instead. The [`run`] method on this trait will usually just perform the appropriate /// `container_of` translation and then call into the [`run`][WorkItem::run] method from the
@@ -329,7 +329,7 @@ pub unsafe trait WorkItemPointer<const ID: u64>: RawWorkItem<ID> {/// This trait is used when the `work_struct` field is defined using the [`Work`] helper. pub trait WorkItem <const ID :u64 =0 > {/// The pointer type that this struct is wrapped in. This will typically be `Arc<Self>` or -/// `Pin<Box<Self>>`. +/// `Pin<KBox<Self>>`. type Pointer :WorkItemPointer <ID >;/// The method that should be called when this work item is executed.
@@ -565,7 +565,7 @@ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput } } -unsafe impl <T ,const ID :u64 >WorkItemPointer <ID >for Pin <Box <T >> +unsafe impl <T ,const ID :u64 >WorkItemPointer <ID >for Pin <KBox <T >>where T :WorkItem <ID ,Pointer =Self >,T :HasWork <T ,ID >,
@@ -576,7 +576,7 @@ unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Pin<Box<T>>// SAFETY: This computes the pointer that `__enqueue` got from `Arc::into_raw`. let ptr =unsafe {T ::work_container_of (ptr) };// SAFETY: This pointer comes from `Arc::into_raw` and we've been given back ownership. -let boxed =unsafe {Box ::from_raw (ptr) }; +let boxed =unsafe {KBox ::from_raw (ptr) };// SAFETY: The box was already pinned when it was enqueued. let pinned =unsafe {Pin ::new_unchecked (boxed) };
@@ -584,7 +584,7 @@ unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Pin<Box<T>> } } -unsafe impl <T ,const ID :u64 >RawWorkItem <ID >for Pin <Box <T >> +unsafe impl <T ,const ID :u64 >RawWorkItem <ID >for Pin <KBox <T >>where T :WorkItem <ID ,Pointer =Self >,T :HasWork <T ,ID >,
@@ -598,9 +598,9 @@ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput// SAFETY: We're not going to move `self` or any of its fields, so its okay to temporarily // remove the `Pin` wrapper. let boxed =unsafe {Pin ::into_inner_unchecked (self) }; -let ptr =Box ::into_raw (boxed); +let ptr =KBox ::into_raw (boxed); -// SAFETY: Pointers into a `Box` point at a valid value. +// SAFETY: Pointers into a `KBox` point at a valid value. let work_ptr =unsafe {T ::raw_get_work (ptr) };// SAFETY: `raw_get_work` returns a pointer to a valid value. let work_ptr =unsafe {Work ::raw_get (work_ptr) };
rust/macros/lib.rs +3 -3
@@ -243,7 +243,7 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {/// struct DriverData { /// #[pin] /// queue: Mutex<Vec<Command>>, -/// buf: Box<[u8; 1024 * 1024]>, +/// buf: KBox<[u8; 1024 * 1024]>, /// } /// ``` ///
@@ -252,7 +252,7 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {/// struct DriverData { /// #[pin] /// queue: Mutex<Vec<Command>>, -/// buf: Box<[u8; 1024 * 1024]>, +/// buf: KBox<[u8; 1024 * 1024]>, /// raw_info: *mut Info, /// } ///
@@ -282,7 +282,7 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream {/// struct DriverData { /// #[pin] /// queue: Mutex<Vec<Command>>, -/// buf: Box<[u8; 1024 * 1024]>, +/// buf: KBox<[u8; 1024 * 1024]>, /// raw_info: *mut Info, /// } ///
Signature · 1 line
2.46.0
Re: [PATCH v7 10/26] rust: treewide: switch to our kernel `Box` type
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:46 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we got the kernel
Boxtype in place, convert all existingBoxusers to make use of it.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Have you considered defaulting the A in Box to Kmalloc? This would avoid doing a lot of tree-wide changes.
If you have a reason against it (I'm pretty sure you have), then probably you should put it in the commit message.
Best,
Gary
Quoted 12 lines
--- drivers/block/rnull.rs | 4 +-- rust/kernel/init.rs | 51 ++++++++++++++++--------------- rust/kernel/init/__internal.rs | 2 +- rust/kernel/rbtree.rs | 49 ++++++++++++++++------------- rust/kernel/sync/arc.rs | 17 +++++------ rust/kernel/sync/condvar.rs | 4 +-- rust/kernel/sync/lock/mutex.rs | 2 +- rust/kernel/sync/lock/spinlock.rs | 2 +- rust/kernel/workqueue.rs | 20 ++++++------ rust/macros/lib.rs | 6 ++-- 10 files changed, 81 insertions(+), 76 deletions(-)
Danilo Krummrich
maintainerRe: [PATCH v7 10/26] rust: treewide: switch to our kernel `Box` type
Quoted 10 lines · Gary Guo
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:46 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we got the kernel
Boxtype in place, convert all existingBoxusers to make use of it.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 7 lines
Reviewed-by Gary Guo <[email protected]>Have you considered defaulting the
AinBoxtoKmalloc? This would avoid doing a lot of tree-wide changes.If you have a reason against it (I'm pretty sure you have), then probably you should put it in the commit message.
Yes, I want people to be forced to make an explicit decision about the allocator backend they choose, because this has relevant implications.
For instance, it's likely to be unexpected to people coming from userspace Rust, that (with the default of Kmalloc) Box allocations larger than KMALLOC_MAX_SIZE would just fail.
Quoted 17 lines
Best,
GaryQuoted 12 lines
--- drivers/block/rnull.rs | 4 +-- rust/kernel/init.rs | 51 ++++++++++++++++--------------- rust/kernel/init/__internal.rs | 2 +- rust/kernel/rbtree.rs | 49 ++++++++++++++++------------- rust/kernel/sync/arc.rs | 17 +++++------ rust/kernel/sync/condvar.rs | 4 +-- rust/kernel/sync/lock/mutex.rs | 2 +- rust/kernel/sync/lock/spinlock.rs | 2 +- rust/kernel/workqueue.rs | 20 ++++++------ rust/macros/lib.rs | 6 ++-- 10 files changed, 81 insertions(+), 76 deletions(-)
Danilo Krummrich
maintainerPATCH v7 11/26 rust: alloc: remove extension of std's `Box`
In reply to Danilo KrummrichNow that all existing Box users were moved to the kernel Box type, remove the BoxExt extension and all other related extensions.
--- rust/kernel/alloc.rs | 1 - rust/kernel/alloc/box_ext.rs | 87 ------------------------------------ rust/kernel/init.rs | 46 +------------------ rust/kernel/lib.rs | 1 - rust/kernel/prelude.rs | 4 +- rust/kernel/types.rs | 50 --------------------- 6 files changed, 3 insertions(+), 186 deletions(-) delete mode 100644 rust/kernel/alloc/box_ext.rs
rust/kernel/alloc.rs +0 -1
@@ -4,7 +4,6 @@#[cfg(not(any(test, testlib)))] pub mod allocator; -pub mod box_ext;pub mod kbox;pub mod vec_ext;
rust/kernel/alloc/box_ext.rs deleted +0 -87
87 lines not on this page · raw message
rust/kernel/init.rs +1 -45
75 lines not on this page · raw message
rust/kernel/lib.rs +0 -1
@@ -14,7 +14,6 @@#![no_std] #![feature(coerce_unsized)] #![feature(dispatch_from_dyn)] -#![feature(new_uninit)] #![feature(receiver_trait)] #![feature(unsize)]
rust/kernel/prelude.rs +2 -2
@@ -14,10 +14,10 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{box_ext::BoxExt , flags::*, vec_ext::VecExt ,KBox ,KVBox ,VBox }; +pub use crate ::alloc::{flags::*, vec_ext::VecExt ,KBox ,KVBox ,VBox };#[doc(no_inline)] -pub use alloc::{boxed::Box , vec::Vec }; +pub use alloc::vec::Vec ;#[doc(no_inline)] pub use macros::{module, pin_data, pinned_drop, vtable,Zeroable };
rust/kernel/types.rs +0 -50
67 lines not on this page · raw message
Signature · 1 line
2.46.0
Re: [PATCH v7 11/26] rust: alloc: remove extension of std's `Box`
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:47 +0200 Danilo Krummrich <[email protected]> wrote:
Now that all existing
Boxusers were moved to the kernelBoxtype, remove theBoxExtextension and all other related extensions.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 9 lines
--- rust/kernel/alloc.rs | 1 - rust/kernel/alloc/box_ext.rs | 87 ------------------------------------ rust/kernel/init.rs | 46 +------------------ rust/kernel/lib.rs | 1 - rust/kernel/prelude.rs | 4 +- rust/kernel/types.rs | 50 --------------------- 6 files changed, 3 insertions(+), 186 deletions(-) delete mode 100644 rust/kernel/alloc/box_ext.rs
Danilo Krummrich
maintainerPATCH v7 12/26 rust: alloc: add `Box` to prelude
In reply to Danilo KrummrichNow that we removed BoxExt and the corresponding includes in prelude.rs, add the new kernel Box type instead.
--- rust/kernel/prelude.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
rust/kernel/prelude.rs +1 -1
@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,KBox ,KVBox ,VBox }; +pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,VBox };#[doc(no_inline)] pub use alloc::vec::Vec ;
Signature · 1 line
2.46.0
Re: [PATCH v7 12/26] rust: alloc: add `Box` to prelude
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:48 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we removed
BoxExtand the corresponding includes inprelude.rs, add the new kernelBoxtype instead.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 17 lines
--- rust/kernel/prelude.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)rust/kernel/prelude.rs +1
-1@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,KBox ,KVBox ,VBox }; +pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,VBox };#[doc(no_inline)] pub use alloc::vec::Vec ;
Danilo Krummrich
maintainerPATCH v7 13/26 rust: alloc: implement kernel `Vec` type
In reply to Danilo KrummrichVec provides a contiguous growable array type with contents allocated with the kernel's allocators (e.g. Kmalloc, Vmalloc or KVmalloc).
In contrast to Rust's stdlib Vec type, the kernel Vec type considers the kernel's GFP flags for all appropriate functions, always reports allocation failures through Result<_, AllocError> and remains independent from unstable features.
--- rust/kernel/alloc.rs | 6 + rust/kernel/alloc/kvec.rs | 638 ++++++++++++++++++++++++++++++++++++++ rust/kernel/prelude.rs | 2 +- 3 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/alloc/kvec.rs
rust/kernel/alloc.rs +6 -0
@@ -5,6 +5,7 @@#[cfg(not(any(test, testlib)))] pub mod allocator;pub mod kbox; +pub mod kvec;pub mod vec_ext;#[cfg(any(test, testlib))]
@@ -18,6 +19,11 @@pub use self::kbox::KVBox ;pub use self::kbox::VBox ; +pub use self::kvec::KVVec ; +pub use self::kvec::KVec ; +pub use self::kvec::VVec ; +pub use self::kvec::Vec ; +/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ;
rust/kernel/alloc/kvec.rs new file +638 -0
638 lines not on this page · raw message
rust/kernel/prelude.rs +1 -1
@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,VBox }; +pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec };#[doc(no_inline)] pub use alloc::vec::Vec ;
Signature · 1 line
2.46.0
Re: [PATCH v7 13/26] rust: alloc: implement kernel `Vec` type
Quoted 18 lines · Danilo Krummrich
rust/kernel/alloc/kvec.rs new file +12
-0@@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Implementation of [`Vec`]. + +use super ::{ + allocator::{KVmalloc ,Kmalloc ,Vmalloc }, +AllocError ,Allocator ,Box ,Flags , +}; +use core::{ + fmt, + marker::PhantomData , + mem::{ManuallyDrop ,MaybeUninit },
rust/kernel/alloc/kvec.rs line 22
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+ ops::Deref, + ops::DerefMut, + ops::Index, + ops::IndexMut, + ptr::NonNull, + slice, + slice::SliceIndex, +}; + +/// Create a [`Vec`] containing the arguments.
I would change this to [KVec].
Quoted 28 lines
rust/kernel/alloc/kvec.rs new file +28
-0+/// +/// # Examples +/// +/// ``` +/// let mut v = kernel::kvec![]; +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(v, [1]); +/// +/// let mut v = kernel::kvec![1; 3]?; +/// v.push(4, GFP_KERNEL)?; +/// assert_eq!(v, [1, 1, 1, 4]); +/// +/// let mut v = kernel::kvec![1, 2, 3]?; +/// v.push(4, GFP_KERNEL)?; +/// assert_eq!(v, [1, 2, 3, 4]); +/// +/// # Ok::<(), Error>(()) +/// ``` +#[macro_export] +macro_rules! kvec { + () => ( + $crate::alloc::KVec ::new () + ); + ($elem:expr; $n:expr) => ( + $crate::alloc::KVec ::from_elem ($elem, $n,GFP_KERNEL ) + ); + ($($x:expr),+ $(,)?) => ( +match $crate::alloc::KBox ::new_uninit (GFP_KERNEL ) {
rust/kernel/alloc/kvec.rs line 60
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+ Ok(b) => Ok($crate::alloc::KVec::from($crate::alloc::KBox::write(b, [$($x),+]))), + Err(e) => Err(e), + } + ); +} + +/// The kernel's [`Vec`] type. +/// +/// A contiguous growable array type with contents allocated with the kernel's allocators (e.g. +/// `Kmalloc`, `Vmalloc` or `KVmalloc`), written `Vec<T, A>`.
Can you turn these into links?
rust/kernel/alloc/kvec.rs line 63
Quoted 3 lines
rust/kernel/alloc/kvec.rs new file +3
-0+/// +/// For non-zero-sized values, a [`Vec`] will use the given allocator `A` for its allocation. For +/// the most common allocators the type aliases `KVec`, `VVec` and `KVVec` exist.
Ditto.
Quoted 41 lines
rust/kernel/alloc/kvec.rs new file +41
-0+/// +/// For zero-sized types the [`Vec`]'s pointer must be `dangling_mut::<T>`; no memory is allocated. +/// +/// Generally, [`Vec`] consists of a pointer that represents the vector's backing buffer, the +/// capacity of the vector (the number of elements that currently fit into the vector), it's length +/// (the number of elements that are currently stored in the vector) and the `Allocator` type used +/// to allocate (and free) the backing buffer. +/// +/// A [`Vec`] can be deconstructed into and (re-)constructed from it's previously named raw parts +/// and manually modified. +/// +/// [`Vec`]'s backing buffer gets, if required, automatically increased (re-allocated) when elements +/// are added to the vector. +/// +/// # Invariants +/// +/// - `self.ptr` is always properly aligned and either points to memory allocated with `A` or, for +/// zero-sized types, is a dangling, well aligned pointer. +/// +/// - `self.len` always represents the exact number of elements stored in the vector. +/// +/// - `self.cap` represents the absolute number of elements that can be stored within the vector +/// without re-allocation. However, it is legal for the backing buffer to be larger than +/// `size_of<T>` times the capacity. +/// +/// - The `Allocator` type `A` of the vector is the exact same `Allocator` type the backing buffer +/// was allocated with (and must be freed with). +pub struct Vec <T ,A :Allocator > { + ptr:NonNull <T >, +/// Represents the actual buffer size as `cap` times `size_of::<T>` bytes. +/// +/// Note: This isn't quite the same as `Self::capacity`, which in contrast returns the number of +/// elements we can still store without reallocating. +/// +/// # Invariants +/// +/// `cap` must be in the `0..=isize::MAX` range. + cap:usize , + len:usize , + _p:PhantomData <A >, +}
[...]
Quoted 15 lines
rust/kernel/alloc/kvec.rs new file +15
-0+/// Appends an element to the back of the [`Vec`] instance. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// v.push(2, GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 2]); +/// # Ok::<(), Error>(()) +/// ``` +pub fn push (&mut self, v:T , flags:Flags ) ->Result <(),AllocError > { +Vec ::reserve (self,1 , flags)?;
rust/kernel/alloc/kvec.rs line 291
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+ +// SAFETY: +// - `self.len` is smaller than `self.capacity` and hence, the resulting pointer is +// guaranteed to be part of the same allocated object. +// - `self.len` can not overflow `isize`. +let ptr =unsafe { self.as_mut_ptr ().add (self.len) }; + +// SAFETY: +// - `ptr` is properly aligned and valid for writes. +unsafe { core::ptr::write (ptr, v) };
Why not use self.spare_capacity_mut()[0].write(v);?
If you want to avoid the bounds check, you can do
let first = self.spare_capacity_mut().first();
// SAFETY: the call to `Vec::reserve` above ensures that `spare_capacity_mut()` is non-empty.
unsafe { first.unwrap_unchecked() }.write(v);Quoted 34 lines
rust/kernel/alloc/kvec.rs new file +34
-0+ +// SAFETY: We just initialised the first spare entry, so it is safe to increase the length +// by 1. We also know that the new length is <= capacity because of the previous call to +// `reserve` above. +unsafe { self.set_len (self.len () +1 ) }; +Ok (()) + } + +/// Creates a new [`Vec`] instance with at least the given capacity. +/// +/// # Examples +/// +/// ``` +/// let v = KVec::<u32>::with_capacity(20, GFP_KERNEL)?; +/// +/// assert!(v.capacity() >= 20); +/// # Ok::<(), Error>(()) +/// ``` +pub fn with_capacity (capacity:usize , flags:Flags ) ->Result <Self ,AllocError > { +let mut v =Vec ::new (); + +Self ::reserve (&mut v, capacity, flags)?; + +Ok (v) + } + +/// Pushes clones of the elements of slice into the [`Vec`] instance. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +///
rust/kernel/alloc/kvec.rs line 335
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+/// v.extend_from_slice(&[20, 30, 40], GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 20, 30, 40]); +/// +/// v.extend_from_slice(&[50, 60], GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 20, 30, 40, 50, 60]); +/// # Ok::<(), Error>(()) +/// ``` +pub fn extend_from_slice (&mut self, other: &[T ], flags:Flags ) ->Result <(),AllocError > +where +T :Clone ,
This method can be moved into the other impl block below, it already has the T: Clone bound.
Quoted 58 lines
rust/kernel/alloc/kvec.rs new file +58
-0+ { + self.reserve (other.len (), flags)?; +for (slot, item)in core::iter::zip (self.spare_capacity_mut (), other) { + slot.write (item.clone ()); + } + +// SAFETY: +// - `other.len()` spare entries have just been initialized, so it is safe to increase +// the length by the same number. +// - `self.len() + other.len() <= self.capacity()` is guaranteed by the preceding `reserve` +// call. +unsafe { self.set_len (self.len () + other.len ()) }; +Ok (()) + } + +/// Creates a Vec<T, A> from a pointer, a length and a capacity using the allocator `A`. +/// +/// # Examples +/// +/// ``` +/// let mut v = kernel::kvec![1, 2, 3]?; +/// v.reserve(1, GFP_KERNEL)?; +/// +/// let (mut ptr, mut len, cap) = v.into_raw_parts(); +/// +/// // SAFETY: We've just reserved memory for another element. +/// unsafe { ptr.add(len).write(4) }; +/// len += 1; +/// +/// // SAFETY: We only wrote an additional element at the end of the `KVec`'s buffer and +/// // correspondingly increased the length of the `KVec` by one. Otherwise, we construct it +/// // from the exact same raw parts. +/// let v = unsafe { KVec::from_raw_parts(ptr, len, cap) }; +/// +/// assert_eq!(v, [1, 2, 3, 4]); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// +/// # Safety +/// +/// If `T` is a ZST: +/// +/// - `ptr` must be a dangling, well aligned pointer. +/// +/// Otherwise: +/// +/// - `ptr` must have been allocated with the allocator `A`. +/// - `ptr` must satisfy or exceed the alignment requirements of `T`. +/// - `ptr` must point to memory with a size of at least `size_of::<T>() * capacity`. +/// bytes. +/// - The allocated size in bytes must not be larger than `isize::MAX`. +/// - `length` must be less than or equal to `capacity`. +/// - The first `length` elements must be initialized values of type `T`. +/// +/// It is also valid to create an empty `Vec` passing a dangling pointer for `ptr` and zero for +/// `cap` and `len`. +pub unsafe fn from_raw_parts (ptr: *mut T , length:usize , capacity:usize ) ->Self {
rust/kernel/alloc/kvec.rs line 403
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+let cap =if Self ::is_zst () {0 }else { capacity }; + +Self { +// SAFETY: By the safety requirements, `ptr` is either dangling or pointing to a valid +// memory allocation, allocated with `A`. + ptr:unsafe {NonNull ::new_unchecked (ptr) }, + cap, + len: length, + _p:PhantomData ::<A >, + }
Would be nice to have debug_assert!(length <= capacity) here. But feel free to make that a good-first-issue instead of including it in the next version. (there are probably more asserts elsewhere)
Quoted 14 lines
rust/kernel/alloc/kvec.rs new file +14
-0+ } + +/// Consumes the `Vec<T, A>` and returns its raw components `pointer`, `length` and `capacity`. +/// +/// This will not run the destructor of the contained elements and for non-ZSTs the allocation +/// will stay alive indefinitely. Use [`Vec::from_raw_parts`] to recover the [`Vec`], drop the +/// elements and free the allocation, if any. +pub fn into_raw_parts (self) -> (*mut T ,usize ,usize ) { +let mut me =ManuallyDrop ::new (self); +let len = me.len (); +let capacity = me.capacity (); +let ptr = me.as_mut_ptr (); + (ptr, len, capacity) + }
[...]
rust/kernel/alloc/kvec.rs line 619
Quoted 2 lines
rust/kernel/alloc/kvec.rs new file +2
-0+macro_rules! impl_slice_eq { + ([$($vars:tt)*] $lhs:ty, $rhs:ty) => {
You could wrap the entire pattern in "$()*", same for the entire body and then...
Quoted 9 lines
rust/kernel/alloc/kvec.rs new file +9
-0+ impl<T, U, $($vars)*> PartialEq<$rhs> for $lhs +where +T :PartialEq <U >, + { +#[inline] +fn eq (&self, other: &$rhs) ->bool { self[..] == other[..] } + } + } +}
rust/kernel/alloc/kvec.rs line 638
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+ +impl_slice_eq! { [A1 :Allocator ,A2 :Allocator ]Vec <T ,A1 >,Vec <U ,A2 > } +impl_slice_eq! { [A :Allocator ]Vec <T ,A >, &[U ] } +impl_slice_eq! { [A :Allocator ]Vec <T ,A >, &mut [U ] } +impl_slice_eq! { [A :Allocator ] &[T ],Vec <U ,A > } +impl_slice_eq! { [A :Allocator ] &mut [T ],Vec <U ,A > } +impl_slice_eq! { [A :Allocator ]Vec <T ,A >, [U ] } +impl_slice_eq! { [A :Allocator ] [T ],Vec <U ,A > } +impl_slice_eq! { [A :Allocator ,const N :usize ]Vec <T ,A >, [U ;N ] } +impl_slice_eq! { [A :Allocator ,const N :usize ]Vec <T ,A >, &[U ;N ] }
...we could have a single impl_slice_eq invocation here:
impl_slice_eq! {
[A1: Allocator, A2: Allocator] Vec<T, A1>, Vec<U, A2>
[A: Allocator] Vec<T, A>, &[U]
[A: Allocator] Vec<T, A>, &mut [U]
[A: Allocator] &[T], Vec<U, A>
[A: Allocator] &mut [T], Vec<U, A>
[A: Allocator] Vec<T, A>, [U]
[A: Allocator] [T], Vec<U, A>
[A: Allocator, const N: usize] Vec<T, A>, [U; N]
[A: Allocator, const N: usize] Vec<T, A>, &[U; N]
}Not a huge improvement, but I think it makes it a bit nicer to read.
---
Cheers,
Benno
Quoted 16 lines
rust/kernel/prelude.rs +1
-2@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,VBox }; +pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec };#[doc(no_inline)] pub use alloc::vec::Vec ; --2.46.0
Danilo Krummrich
maintainerRe: [PATCH v7 13/26] rust: alloc: implement kernel `Vec` type
Quoted 20 lines · Benno Lossin
Quoted 19 lines · Danilo Krummrich
rust/kernel/alloc/kvec.rs new file +19
-0+/// Appends an element to the back of the [`Vec`] instance. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// v.push(2, GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 2]); +/// # Ok::<(), Error>(()) +/// ``` +pub fn push (&mut self, v:T , flags:Flags ) ->Result <(),AllocError > { +Vec ::reserve (self,1 , flags)?; + +// SAFETY: +// - `self.len` is smaller than `self.capacity` and hence, the resulting pointer is +// guaranteed to be part of the same allocated object.
Quoted 8 lines
rust/kernel/alloc/kvec.rs line 291
Quoted 6 lines
rust/kernel/alloc/kvec.rs new file +6
-0+// - `self.len` can not overflow `isize`. +let ptr =unsafe { self.as_mut_ptr ().add (self.len) }; + +// SAFETY: +// - `ptr` is properly aligned and valid for writes. +unsafe { core::ptr::write (ptr, v) };Why not use
self.spare_capacity_mut()[0].write(v);?
Before v7 I did exactly that, but in v6 you suggested to use the raw pointer instead to avoid the bounds check.
Quoted 6 lines
If you want to avoid the bounds check, you can do
let first = self.spare_capacity_mut().first(); // SAFETY: the call to `Vec::reserve` above ensures that `spare_capacity_mut()` is non-empty. unsafe { first.unwrap_unchecked() }.write(v);
first does a similar check to create the Option<&T>, right?. I'd rather keep the raw pointer access as suggested in v6.
Re: [PATCH v7 13/26] rust: alloc: implement kernel `Vec` type
Quoted 30 lines · Danilo Krummrich
Quoted 20 lines · Benno Lossin
Quoted 19 lines · Danilo Krummrich
rust/kernel/alloc/kvec.rs new file +19
-0+/// Appends an element to the back of the [`Vec`] instance. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// v.push(2, GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 2]); +/// # Ok::<(), Error>(()) +/// ``` +pub fn push (&mut self, v:T , flags:Flags ) ->Result <(),AllocError > { +Vec ::reserve (self,1 , flags)?; + +// SAFETY: +// - `self.len` is smaller than `self.capacity` and hence, the resulting pointer is +// guaranteed to be part of the same allocated object. Quoted 8 lines
rust/kernel/alloc/kvec.rs line 291
Quoted 6 lines
rust/kernel/alloc/kvec.rs new file +6
-0+// - `self.len` can not overflow `isize`. +let ptr =unsafe { self.as_mut_ptr ().add (self.len) }; + +// SAFETY: +// - `ptr` is properly aligned and valid for writes. +unsafe { core::ptr::write (ptr, v) };Why not use
self.spare_capacity_mut()[0].write(v);?
Quoted 2 lines
Before v7 I did exactly that, but in v6 you suggested to use the raw pointer instead to avoid the bounds check.
Ah I see... Would be pretty useful for me to have my previous comments easily accessible, I don't usually look at the previous thread. Is anyone aware of some tools for that?
Quoted 8 lines
Quoted 5 lines
If you want to avoid the bounds check, you can do
let first = self.spare_capacity_mut().first(); // SAFETY: the call to `Vec::reserve` above ensures that `spare_capacity_mut()` is non-empty. unsafe { first.unwrap_unchecked() }.write(v);
firstdoes a similar check to create theOption<&T>, right?. I'd rather keep the raw pointer access as suggested in v6.
It does a check, but the optimizer will get rid of it if you use unwrap_unchecked [1]. But feel free to leave it as-is.
[1]: https://godbolt.org/z/zYbMTo86M
---
Cheers,
Benno
Gary Guo
maintainerRe: [PATCH v7 13/26] rust: alloc: implement kernel `Vec` type
In reply to Danilo KrummrichQuoted 209 lines · On Thu, 12 Sep 2024 00:52:49 +0200 Danilo Krummrich <[email protected]> wrote:
Vecprovides a contiguous growable array type with contents allocated with the kernel's allocators (e.g.Kmalloc,VmallocorKVmalloc).In contrast to Rust's stdlib
Vectype, the kernelVectype considers the kernel's GFP flags for all appropriate functions, always reports allocation failures throughResult<_, AllocError>and remains independent from unstable features.Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 6 + rust/kernel/alloc/kvec.rs | 638 ++++++++++++++++++++++++++++++++++++++ rust/kernel/prelude.rs | 2 +- 3 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 rust/kernel/alloc/kvec.rsrust/kernel/alloc.rs +6
-0@@ -5,6 +5,7 @@#[cfg(not(any(test, testlib)))] pub mod allocator;pub mod kbox; +pub mod kvec;pub mod vec_ext;#[cfg(any(test, testlib))] @@ -18,6 +19,11 @@pub use self::kbox::KVBox ;pub use self::kbox::VBox ; +pub use self::kvec::KVVec ; +pub use self::kvec::KVec ; +pub use self::kvec::VVec ; +pub use self::kvec::Vec ; +/// Indicates an allocation error. #[derive( Copy , Clone , PartialEq , Eq , Debug )] pub struct AllocError ;rust/kernel/alloc/kvec.rs new file +163
-0@@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Implementation of [`Vec`]. + +use super ::{ + allocator::{KVmalloc ,Kmalloc ,Vmalloc }, +AllocError ,Allocator ,Box ,Flags , +}; +use core::{ + fmt, + marker::PhantomData , + mem::{ManuallyDrop ,MaybeUninit }, + ops::Deref , + ops::DerefMut , + ops::Index , + ops::IndexMut , + ptr::NonNull , + slice, + slice::SliceIndex , +}; + +/// Create a [`Vec`] containing the arguments. +/// +/// # Examples +/// +/// ``` +/// let mut v = kernel::kvec![]; +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(v, [1]); +/// +/// let mut v = kernel::kvec![1; 3]?; +/// v.push(4, GFP_KERNEL)?; +/// assert_eq!(v, [1, 1, 1, 4]); +/// +/// let mut v = kernel::kvec![1, 2, 3]?; +/// v.push(4, GFP_KERNEL)?; +/// assert_eq!(v, [1, 2, 3, 4]); +/// +/// # Ok::<(), Error>(()) +/// ``` +#[macro_export] +macro_rules! kvec { + () => ( + $crate::alloc::KVec ::new () + ); + ($elem:expr; $n:expr) => ( + $crate::alloc::KVec ::from_elem ($elem, $n,GFP_KERNEL ) + ); + ($($x:expr),+ $(,)?) => ( +match $crate::alloc::KBox ::new_uninit (GFP_KERNEL ) { +Ok (b) =>Ok ($crate::alloc::KVec ::from ($crate::alloc::KBox ::write (b, [$($x),+]))), +Err (e) =>Err (e), + } + ); +} + +/// The kernel's [`Vec`] type. +/// +/// A contiguous growable array type with contents allocated with the kernel's allocators (e.g. +/// `Kmalloc`, `Vmalloc` or `KVmalloc`), written `Vec<T, A>`. +/// +/// For non-zero-sized values, a [`Vec`] will use the given allocator `A` for its allocation. For +/// the most common allocators the type aliases `KVec`, `VVec` and `KVVec` exist. +/// +/// For zero-sized types the [`Vec`]'s pointer must be `dangling_mut::<T>`; no memory is allocated. +/// +/// Generally, [`Vec`] consists of a pointer that represents the vector's backing buffer, the +/// capacity of the vector (the number of elements that currently fit into the vector), it's length +/// (the number of elements that are currently stored in the vector) and the `Allocator` type used +/// to allocate (and free) the backing buffer. +/// +/// A [`Vec`] can be deconstructed into and (re-)constructed from it's previously named raw parts +/// and manually modified. +/// +/// [`Vec`]'s backing buffer gets, if required, automatically increased (re-allocated) when elements +/// are added to the vector. +/// +/// # Invariants +/// +/// - `self.ptr` is always properly aligned and either points to memory allocated with `A` or, for +/// zero-sized types, is a dangling, well aligned pointer. +/// +/// - `self.len` always represents the exact number of elements stored in the vector. +/// +/// - `self.cap` represents the absolute number of elements that can be stored within the vector +/// without re-allocation. However, it is legal for the backing buffer to be larger than +/// `size_of<T>` times the capacity. +/// +/// - The `Allocator` type `A` of the vector is the exact same `Allocator` type the backing buffer +/// was allocated with (and must be freed with). +pub struct Vec <T ,A :Allocator > { + ptr:NonNull <T >, +/// Represents the actual buffer size as `cap` times `size_of::<T>` bytes. +/// +/// Note: This isn't quite the same as `Self::capacity`, which in contrast returns the number of +/// elements we can still store without reallocating. +/// +/// # Invariants +/// +/// `cap` must be in the `0..=isize::MAX` range. + cap:usize , + len:usize , + _p:PhantomData <A >, +} + +/// Type alias for [`Vec`] with a [`Kmalloc`] allocator. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// # Ok::<(), Error>(()) +/// ``` +pub type KVec <T > =Vec <T ,Kmalloc >; + +/// Type alias for [`Vec`] with a [`Vmalloc`] allocator. +/// +/// # Examples +/// +/// ``` +/// let mut v = VVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// # Ok::<(), Error>(()) +/// ``` +pub type VVec <T > =Vec <T ,Vmalloc >; + +/// Type alias for [`Vec`] with a [`KVmalloc`] allocator. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// # Ok::<(), Error>(()) +/// ``` +pub type KVVec <T > =Vec <T ,KVmalloc >; + +// SAFETY: `Vec` is `Send` if `T` is `Send` because `Vec` owns its elements. +unsafe impl <T ,A >Send for Vec <T ,A > +where +T :Send , +A :Allocator , +{ +} + +// SAFETY: `Vec` is `Sync` if `T` is `Sync` because `Vec` owns its elements. +unsafe impl <T ,A >Sync for Vec <T ,A > +where +T :Sync , +A :Allocator , +{ +} + +impl <T ,A >Vec <T ,A > +where +A :Allocator ,
rust/kernel/alloc/kvec.rs line 173
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+{ +#[inline] +fn is_zst () ->bool { + core::mem::size_of ::<T >() ==0 + } + +/// Returns the number of elements that can be stored within the vector without allocating +/// additional memory. +pub fn capacity (&self) ->usize { +if Self ::is_zst () {
Better to ensure everything ZST related is const to avoid putting load on optimizer.
The Rust standard library defines a trait SizedTypeProperties and an associative const, but you can also just do:
if const { Self::is_zst() } {(and change is_zst to const).
You might need to add feature(inline_const) since it's stable in 1.79 and we have MSRV of 1.76 (the trait method doesn't need new feature gate).
Quoted 98 lines
rust/kernel/alloc/kvec.rs new file +98
-0+ usize::MAX + }else { + self.cap + } + } + +/// Returns the number of elements stored within the vector. +#[inline] +pub fn len (&self) ->usize { + self.len + } + +/// Forcefully sets `self.len` to `new_len`. +/// +/// # Safety +/// +/// - `new_len` must be less than or equal to [`Self::capacity`]. +/// - If `new_len` is greater than `self.len`, all elements within the interval +/// [`self.len`,`new_len`) must be initialized. +#[inline] +pub unsafe fn set_len (&mut self, new_len:usize ) { +debug_assert! (new_len <= self.capacity ()); + self.len = new_len; + } + +/// Returns a slice of the entire vector. +#[inline] +pub fn as_slice (&self) -> &[T ] { + self + } + +/// Returns a mutable slice of the entire vector. +#[inline] +pub fn as_mut_slice (&mut self) -> &mut [T ] { + self + } + +/// Returns a mutable raw pointer to the vector's backing buffer, or, if `T` is a ZST, a +/// dangling raw pointer. +#[inline] +pub fn as_mut_ptr (&mut self) -> *mut T { + self.ptr.as_ptr () + } + +/// Returns a raw pointer to the vector's backing buffer, or, if `T` is a ZST, a dangling raw +/// pointer. +#[inline] +pub fn as_ptr (&self) -> *const T { + self.ptr.as_ptr () + } + +/// Returns `true` if the vector contains no elements, `false` otherwise. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// assert!(v.is_empty()); +/// +/// v.push(1, GFP_KERNEL); +/// assert!(!v.is_empty()); +/// ``` +#[inline] +pub fn is_empty (&self) ->bool { + self.len () ==0 + } + +/// Creates a new, empty Vec<T, A>. +/// +/// This method does not allocate by itself. +#[inline] +pub const fn new () ->Self { +Self { + ptr:NonNull ::dangling (), + cap:0 , + len:0 , + _p:PhantomData ::<A >, + } + } + +/// Returns a slice of `MaybeUninit<T>` for the remaining spare capacity of the vector. +pub fn spare_capacity_mut (&mut self) -> &mut [MaybeUninit <T >] { +// SAFETY: +// - `self.len` is smaller than `self.capacity` and hence, the resulting pointer is +// guaranteed to be part of the same allocated object. +// - `self.len` can not overflow `isize`. +let ptr =unsafe { self.as_mut_ptr ().add (self.len) }as *mut MaybeUninit <T >; + +// SAFETY: The memory between `self.len` and `self.capacity` is guaranteed to be allocated +// and valid, but uninitialized. +unsafe { slice::from_raw_parts_mut (ptr, self.capacity () - self.len) } + } + +/// Appends an element to the back of the [`Vec`] instance. +/// +/// # Examples +/// +/// ```
rust/kernel/alloc/kvec.rs line 281
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// assert_eq!(&v, &[1]); +/// +/// v.push(2, GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 2]); +/// # Ok::<(), Error>(()) +/// ``` +pub fn push (&mut self, v:T , flags:Flags ) ->Result <(),AllocError > { +Vec ::reserve (self,1 , flags)?;
Not sure why this isn't self.reserve(1, flags)??
Quoted 22 lines
rust/kernel/alloc/kvec.rs new file +22
-0+ +// SAFETY: +// - `self.len` is smaller than `self.capacity` and hence, the resulting pointer is +// guaranteed to be part of the same allocated object. +// - `self.len` can not overflow `isize`. +let ptr =unsafe { self.as_mut_ptr ().add (self.len) }; + +// SAFETY: +// - `ptr` is properly aligned and valid for writes. +unsafe { core::ptr::write (ptr, v) }; + +// SAFETY: We just initialised the first spare entry, so it is safe to increase the length +// by 1. We also know that the new length is <= capacity because of the previous call to +// `reserve` above. +unsafe { self.set_len (self.len () +1 ) }; +Ok (()) + } + +/// Creates a new [`Vec`] instance with at least the given capacity. +/// +/// # Examples +///
rust/kernel/alloc/kvec.rs line 313
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+/// ``` +/// let v = KVec::<u32>::with_capacity(20, GFP_KERNEL)?; +/// +/// assert!(v.capacity() >= 20); +/// # Ok::<(), Error>(()) +/// ``` +pub fn with_capacity (capacity:usize , flags:Flags ) ->Result <Self ,AllocError > { +let mut v =Vec ::new (); + +Self ::reserve (&mut v, capacity, flags)?;
same here.
Quoted 204 lines
rust/kernel/alloc/kvec.rs new file +204
-0+ +Ok (v) + } + +/// Pushes clones of the elements of slice into the [`Vec`] instance. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// +/// v.extend_from_slice(&[20, 30, 40], GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 20, 30, 40]); +/// +/// v.extend_from_slice(&[50, 60], GFP_KERNEL)?; +/// assert_eq!(&v, &[1, 20, 30, 40, 50, 60]); +/// # Ok::<(), Error>(()) +/// ``` +pub fn extend_from_slice (&mut self, other: &[T ], flags:Flags ) ->Result <(),AllocError > +where +T :Clone , + { + self.reserve (other.len (), flags)?; +for (slot, item)in core::iter::zip (self.spare_capacity_mut (), other) { + slot.write (item.clone ()); + } + +// SAFETY: +// - `other.len()` spare entries have just been initialized, so it is safe to increase +// the length by the same number. +// - `self.len() + other.len() <= self.capacity()` is guaranteed by the preceding `reserve` +// call. +unsafe { self.set_len (self.len () + other.len ()) }; +Ok (()) + } + +/// Creates a Vec<T, A> from a pointer, a length and a capacity using the allocator `A`. +/// +/// # Examples +/// +/// ``` +/// let mut v = kernel::kvec![1, 2, 3]?; +/// v.reserve(1, GFP_KERNEL)?; +/// +/// let (mut ptr, mut len, cap) = v.into_raw_parts(); +/// +/// // SAFETY: We've just reserved memory for another element. +/// unsafe { ptr.add(len).write(4) }; +/// len += 1; +/// +/// // SAFETY: We only wrote an additional element at the end of the `KVec`'s buffer and +/// // correspondingly increased the length of the `KVec` by one. Otherwise, we construct it +/// // from the exact same raw parts. +/// let v = unsafe { KVec::from_raw_parts(ptr, len, cap) }; +/// +/// assert_eq!(v, [1, 2, 3, 4]); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// +/// # Safety +/// +/// If `T` is a ZST: +/// +/// - `ptr` must be a dangling, well aligned pointer. +/// +/// Otherwise: +/// +/// - `ptr` must have been allocated with the allocator `A`. +/// - `ptr` must satisfy or exceed the alignment requirements of `T`. +/// - `ptr` must point to memory with a size of at least `size_of::<T>() * capacity`. +/// bytes. +/// - The allocated size in bytes must not be larger than `isize::MAX`. +/// - `length` must be less than or equal to `capacity`. +/// - The first `length` elements must be initialized values of type `T`. +/// +/// It is also valid to create an empty `Vec` passing a dangling pointer for `ptr` and zero for +/// `cap` and `len`. +pub unsafe fn from_raw_parts (ptr: *mut T , length:usize , capacity:usize ) ->Self { +let cap =if Self ::is_zst () {0 }else { capacity }; + +Self { +// SAFETY: By the safety requirements, `ptr` is either dangling or pointing to a valid +// memory allocation, allocated with `A`. + ptr:unsafe {NonNull ::new_unchecked (ptr) }, + cap, + len: length, + _p:PhantomData ::<A >, + } + } + +/// Consumes the `Vec<T, A>` and returns its raw components `pointer`, `length` and `capacity`. +/// +/// This will not run the destructor of the contained elements and for non-ZSTs the allocation +/// will stay alive indefinitely. Use [`Vec::from_raw_parts`] to recover the [`Vec`], drop the +/// elements and free the allocation, if any. +pub fn into_raw_parts (self) -> (*mut T ,usize ,usize ) { +let mut me =ManuallyDrop ::new (self); +let len = me.len (); +let capacity = me.capacity (); +let ptr = me.as_mut_ptr (); + (ptr, len, capacity) + } + +/// Ensures that the capacity exceeds the length by at least `additional` +/// elements. +/// +/// # Examples +/// +/// ``` +/// let mut v = KVec::new(); +/// v.push(1, GFP_KERNEL)?; +/// +/// v.reserve(10, GFP_KERNEL)?; +/// let cap = v.capacity(); +/// assert!(cap >= 10); +/// +/// v.reserve(10, GFP_KERNEL)?; +/// let new_cap = v.capacity(); +/// assert_eq!(new_cap, cap); +/// +/// # Ok::<(), Error>(()) +/// ``` +pub fn reserve (&mut self, additional:usize , flags:Flags ) ->Result <(),AllocError > { +let len = self.len (); +let cap = self.capacity (); + +if cap - len >= additional { +return Ok (()); + } + +if Self ::is_zst () { +// The capacity is already `usize::MAX` for ZSTs, we can't go higher. +return Err (AllocError ); + } + +// We know `cap` is <= `isize::MAX` because of the type invariants of `Self`. So the +// multiplication by two won't overflow. +let new_cap = core::cmp::max (cap *2 , len.checked_add (additional).ok_or (AllocError )?); +let layout = core::alloc::Layout ::array ::<T >(new_cap).map_err (|_|AllocError )?; + +// We need to make sure that `ptr` is either NULL or comes from a previous call to +// `realloc_flags`. A `Vec<T, A>`'s `ptr` value is not guaranteed to be NULL and might be +// dangling after being created with `Vec::new`. Instead, we can rely on `Vec<T, A>`'s +// capacity to be zero if no memory has been allocated yet. +let ptr =if cap ==0 { +None + }else { +Some (self.ptr.cast ()) + }; + +// SAFETY: `ptr` is valid because it's either `None` or comes from a previous call to +// `A::realloc`. We also verified that the type is not a ZST. +let ptr =unsafe {A ::realloc (ptr, layout, flags)? }; + + self.ptr = ptr.cast (); + +// INVARIANT: `Layout::array` fails if the resulting byte size is greater than `isize::MAX`. + self.cap = new_cap; + +Ok (()) + } +} + +impl <T :Clone ,A :Allocator >Vec <T ,A > { +/// Extend the vector by `n` clones of `value`. +pub fn extend_with (&mut self, n:usize , value:T , flags:Flags ) ->Result <(),AllocError > { +if n ==0 { +return Ok (()); + } + + self.reserve (n, flags)?; + +let spare = self.spare_capacity_mut (); + +for itemin spare.iter_mut ().take (n -1 ) { + item.write (value.clone ()); + } + +// We can write the last element directly without cloning needlessly. + spare[n -1 ].write (value); + +// SAFETY: +// - `self.len() + n < self.capacity()` due to the call to reserve above, +// - the loop and the line above initialized the next `n` elements. +unsafe { self.set_len (self.len () + n) }; + +Ok (()) + } + +/// Create a new `Vec<T, A> and extend it by `n` clones of `value`. +pub fn from_elem (value:T , n:usize , flags:Flags ) ->Result <Self ,AllocError > { +let mut v =Self ::with_capacity (n, flags)?; + + v.extend_with (n, value, flags)?; + +Ok (v) + } +} + +impl <T ,A >Drop for Vec <T ,A > +where +A :Allocator ,
rust/kernel/alloc/kvec.rs line 527
Quoted 10 lines
rust/kernel/alloc/kvec.rs new file +10
-0+{ +fn drop (&mut self) { +// SAFETY: We need to drop the vector's elements in place, before we free the backing +// memory. +unsafe { + core::ptr::drop_in_place (core::ptr::slice_from_raw_parts_mut ( + self.as_mut_ptr (), + self.len, + )) + };
Comment: this can be core::ptr::drop_in_place(&mut *self). The standard library plays safe with raw pointers because it has #[may_dangle] on T. However I think we should keep the code as is.
Quoted 124 lines
rust/kernel/alloc/kvec.rs new file +111
-0+ +// If `cap == 0` we never allocated any memory in the first place. +if self.cap !=0 { +// SAFETY: `self.ptr` was previously allocated with `A`. +unsafe {A ::free (self.ptr.cast ()) }; + } + } +} + +impl <T ,A ,const N :usize >From <Box <[T ;N ],A >>for Vec <T ,A > +where +A :Allocator , +{ +fn from (b:Box <[T ;N ],A >) ->Vec <T ,A > { +let len = b.len (); +let ptr =Box ::into_raw (b); + +// SAFETY: +// - `b` has been allocated with `A`, +// - `ptr` fulfills the alignment requirements for `T`, +// - `ptr` points to memory with at least a size of `size_of::<T>() * len`, +// - all elements within `b` are initialized values of `T`, +// - `len` does not exceed `isize::MAX`. +unsafe {Vec ::from_raw_parts (ptras _ , len, len) } + } +} + +impl <T >Default for KVec <T > { +#[inline] +fn default () ->Self { +Self ::new () + } +} + +impl <T : fmt::Debug ,A :Allocator > fmt::Debug for Vec <T ,A > { +fn fmt (&self, f: &mut fmt::Formatter <'_>) -> fmt::Result { + fmt::Debug ::fmt (&**self, f) + } +} + +impl <T ,A >Deref for Vec <T ,A > +where +A :Allocator , +{ +type Target = [T ]; + +#[inline] +fn deref (&self) -> &[T ] { +// SAFETY: The memory behind `self.as_ptr()` is guaranteed to contain `self.len` +// initialized elements of type `T`. +unsafe { slice::from_raw_parts (self.as_ptr (), self.len) } + } +} + +impl <T ,A >DerefMut for Vec <T ,A > +where +A :Allocator , +{ +#[inline] +fn deref_mut (&mut self) -> &mut [T ] { +// SAFETY: The memory behind `self.as_ptr()` is guaranteed to contain `self.len` +// initialized elements of type `T`. +unsafe { slice::from_raw_parts_mut (self.as_mut_ptr (), self.len) } + } +} + +impl <T :Eq ,A >Eq for Vec <T ,A >where A :Allocator {} + +impl <T ,I :SliceIndex <[T ]>,A >Index <I >for Vec <T ,A > +where +A :Allocator , +{ +type Output =I ::Output ; + +#[inline] +fn index (&self, index:I ) -> &Self ::Output { +Index ::index (&**self, index) + } +} + +impl <T ,I :SliceIndex <[T ]>,A >IndexMut <I >for Vec <T ,A > +where +A :Allocator , +{ +#[inline] +fn index_mut (&mut self, index:I ) -> &mut Self ::Output { +IndexMut ::index_mut (&mut **self, index) + } +} + +macro_rules! impl_slice_eq { + ([$($vars:tt)*] $lhs:ty, $rhs:ty) => { +impl <T ,U , $($vars)*>PartialEq <$rhs>for $lhs +where +T :PartialEq <U >, + { +#[inline] +fn eq (&self, other: &$rhs) ->bool { self[..] == other[..] } + } + } +} + +impl_slice_eq! { [A1 :Allocator ,A2 :Allocator ]Vec <T ,A1 >,Vec <U ,A2 > } +impl_slice_eq! { [A :Allocator ]Vec <T ,A >, &[U ] } +impl_slice_eq! { [A :Allocator ]Vec <T ,A >, &mut [U ] } +impl_slice_eq! { [A :Allocator ] &[T ],Vec <U ,A > } +impl_slice_eq! { [A :Allocator ] &mut [T ],Vec <U ,A > } +impl_slice_eq! { [A :Allocator ]Vec <T ,A >, [U ] } +impl_slice_eq! { [A :Allocator ] [T ],Vec <U ,A > } +impl_slice_eq! { [A :Allocator ,const N :usize ]Vec <T ,A >, [U ;N ] } +impl_slice_eq! { [A :Allocator ,const N :usize ]Vec <T ,A >, &[U ;N ] }rust/kernel/prelude.rs +1
-1@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,VBox }; +pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec };#[doc(no_inline)] pub use alloc::vec::Vec ;
Danilo Krummrich
maintainerPATCH v7 14/26 rust: alloc: implement `IntoIterator` for `Vec`
In reply to Danilo KrummrichImplement IntoIterator for Vec, Vec's IntoIter type, as well as Iterator for IntoIter.
Vec::into_iter disassembles the Vec into its raw parts; additionally, IntoIter keeps track of a separate pointer, which is incremented correspondingsly as the iterator advances, while the length, or the count of elements, is decremented.
This also means that IntoIter takes the ownership of the backing buffer and is responsible to drop the remaining elements and free the backing buffer, if it's dropped.
--- rust/kernel/alloc.rs | 1 + rust/kernel/alloc/kvec.rs | 181 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+)
rust/kernel/alloc.rs +1 -0
@@ -19,6 +19,7 @@pub use self::kbox::KVBox ;pub use self::kbox::VBox ; +pub use self::kvec::IntoIter ;pub use self::kvec::KVVec ;pub use self::kvec::KVec ;pub use self::kvec::VVec ;
rust/kernel/alloc/kvec.rs +181 -0
190 lines not on this page · raw message
Signature · 1 line
2.46.0
Re: [PATCH v7 14/26] rust: alloc: implement `IntoIterator` for `Vec`
Quoted 12 lines · Danilo Krummrich
Implement
IntoIteratorforVec,Vec'sIntoItertype, as well asIteratorforIntoIter.
Vec::into_iterdisassembles theVecinto its raw parts; additionally,IntoIterkeeps track of a separate pointer, which is incremented correspondingsly as the iterator advances, while the length, or the count of elements, is decremented.This also means that
IntoItertakes the ownership of the backing buffer and is responsible to drop the remaining elements and free the backing buffer, if it's dropped.
Quoted 6 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc.rs | 1 + rust/kernel/alloc/kvec.rs | 181 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+)
One comment below, with that fixed:
---
Cheers,
Benno
Quoted 34 lines
rust/kernel/alloc/kvec.rs +34
-0+impl <T ,A >IntoIterator for Vec <T ,A > +where +A :Allocator , +{ +type Item =T ; +type IntoIter =IntoIter <T ,A >; + +/// Consumes the `Vec<T, A>` and creates an `Iterator`, which moves each value out of the +/// vector (from start to end). +/// +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![1, 2]?; +/// let mut v_iter = v.into_iter(); +/// +/// let first_element: Option<u32> = v_iter.next(); +/// +/// assert_eq!(first_element, Some(1)); +/// assert_eq!(v_iter.next(), Some(2)); +/// assert_eq!(v_iter.next(), None); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// +/// ``` +/// let v = kernel::kvec![]; +/// let mut v_iter = v.into_iter(); +/// +/// let first_element: Option<u32> = v_iter.next(); +/// +/// assert_eq!(first_element, None); +/// +/// # Ok::<(), Error>(())
rust/kernel/alloc/kvec.rs fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] } · line 813
Quoted 10 lines
rust/kernel/alloc/kvec.rs +10
-0+/// ``` +#[inline] +fn into_iter (self) ->Self ::IntoIter { +let (ptr, len, cap) = self.into_raw_parts (); + +IntoIter { + ptr, +// SAFETY: `ptr` is either a dangling pointer or a pointer to a valid memory +// allocation, allocated with `A`. + buf:unsafe {NonNull ::new_unchecked (ptr) },
Instead of this unsafe call, you can do
let buf = self.ptr;
Before the call to into_raw_parts.
Quoted 9 lines
rust/kernel/alloc/kvec.rs +6
-1+ len, + cap, + _p: PhantomData::<A>, + } + } +} --2.46.0
Re: [PATCH v7 14/26] rust: alloc: implement `IntoIterator` for `Vec`
In reply to Danilo KrummrichQuoted 8 lines · On Thu, 12 Sep 2024 00:52:50 +0200 Danilo Krummrich <[email protected]> wrote:
Implement
IntoIteratorforVec,Vec'sIntoItertype, as well asIteratorforIntoIter.
Vec::into_iterdisassembles theVecinto its raw parts; additionally,IntoIterkeeps track of a separate pointer, which is incremented correspondingsly as the iterator advances, while the length, or the count of elements, is decremented.
Quoted 6 lines
This also means that
IntoItertakes the ownership of the backing buffer and is responsible to drop the remaining elements and free the backing buffer, if it's dropped.Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
A small nit below, with it fixed:
Quoted 144 lines
--- rust/kernel/alloc.rs | 1 + rust/kernel/alloc/kvec.rs | 181 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+)rust/kernel/alloc.rs +1
-0@@ -19,6 +19,7 @@pub use self::kbox::KVBox ;pub use self::kbox::VBox ; +pub use self::kvec::IntoIter ;pub use self::kvec::KVVec ;pub use self::kvec::KVec ;pub use self::kvec::VVec ;rust/kernel/alloc/kvec.rs +112
-0@@ -14,6 +14,7 @@ ops::DerefMut, ops::Index, ops::IndexMut, + ptr, ptr::NonNull, slice, slice::SliceIndex,@@ -636,3 +637,183 @@ fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] }impl_slice_eq! { [A :Allocator ] [T ],Vec <U ,A > }impl_slice_eq! { [A :Allocator ,const N :usize ]Vec <T ,A >, [U ;N ] }impl_slice_eq! { [A :Allocator ,const N :usize ]Vec <T ,A >, &[U ;N ] } + +impl <'a,T ,A >IntoIterator for &'aVec <T ,A > +where +A :Allocator , +{ +type Item = &'aT ; +type IntoIter = slice::Iter <'a,T >; + +fn into_iter (self) ->Self ::IntoIter { + self.iter () + } +} + +impl <'a,T ,A :Allocator >IntoIterator for &'amut Vec <T ,A > +where +A :Allocator , +{ +type Item = &'amut T ; +type IntoIter = slice::IterMut <'a,T >; + +fn into_iter (self) ->Self ::IntoIter { + self.iter_mut () + } +} + +/// An [`Iterator`] implementation for [`Vec`] that moves elements out of a vector. +/// +/// This structure is created by the [`Vec::into_iter`] method on [`Vec`] (provided by the +/// [`IntoIterator`] trait). +/// +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![0, 1, 2]?; +/// let iter = v.into_iter(); +/// +/// # Ok::<(), Error>(()) +/// ``` +pub struct IntoIter <T ,A :Allocator > { + ptr: *mut T , + buf:NonNull <T >, + len:usize , + cap:usize , + _p:PhantomData <A >, +} + +impl <T ,A >IntoIter <T ,A > +where +A :Allocator , +{ +fn as_raw_mut_slice (&mut self) -> *mut [T ] { + ptr::slice_from_raw_parts_mut (self.ptr, self.len) + } +} + +impl <T ,A >Iterator for IntoIter <T ,A > +where +A :Allocator , +{ +type Item =T ; + +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![1, 2, 3]?; +/// let mut it = v.into_iter(); +/// +/// assert_eq!(it.next(), Some(1)); +/// assert_eq!(it.next(), Some(2)); +/// assert_eq!(it.next(), Some(3)); +/// assert_eq!(it.next(), None); +/// +/// # Ok::<(), Error>(()) +/// ``` +fn next (&mut self) ->Option <T > { +if self.len ==0 { +return None ; + } + +let current = self.ptr; + +// SAFETY: We can't overflow; decreasing `self.len` by one every time we advance `self.ptr` +// by one guarantees that. +unsafe { self.ptr = self.ptr.add (1 ) }; + + self.len -=1 ; + +// SAFETY: `current` is guaranteed to point at a valid element within the buffer. +Some (unsafe { current.read () }) + } + +/// # Examples +/// +/// ``` +/// let v: KVec<u32> = kernel::kvec![1, 2, 3]?; +/// let mut iter = v.into_iter(); +/// let size = iter.size_hint().0; +/// +/// iter.next(); +/// assert_eq!(iter.size_hint().0, size - 1); +/// +/// iter.next(); +/// assert_eq!(iter.size_hint().0, size - 2); +/// +/// iter.next(); +/// assert_eq!(iter.size_hint().0, size - 3); +/// +/// # Ok::<(), Error>(()) +/// ``` +fn size_hint (&self) -> (usize ,Option <usize >) { + (self.len,Some (self.len))
rust/kernel/alloc/kvec.rs fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] } · line 760
Quoted 10 lines
rust/kernel/alloc/kvec.rs +10
-0+ } +} + +impl <T ,A >Drop for IntoIter <T ,A > +where +A :Allocator , +{ +fn drop (&mut self) { +// SAFETY: The pointer in `self.0` is guaranteed to be valid by the type invariant. +unsafe { ptr::drop_in_place (self.as_raw_mut_slice ()) };
as_raw_mut_slice is only used once? It's better to construct the raw pointer directly so there's no need to refer to some other place when reviewing unsafe code.
Quoted 59 lines
rust/kernel/alloc/kvec.rs +59
-0+ +// If `cap == 0` we never allocated any memory in the first place. +if self.cap !=0 { +// SAFETY: `self.buf` was previously allocated with `A`. +unsafe {A ::free (self.buf.cast ()) }; + } + } +} + +impl <T ,A >IntoIterator for Vec <T ,A > +where +A :Allocator , +{ +type Item =T ; +type IntoIter =IntoIter <T ,A >; + +/// Consumes the `Vec<T, A>` and creates an `Iterator`, which moves each value out of the +/// vector (from start to end). +/// +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![1, 2]?; +/// let mut v_iter = v.into_iter(); +/// +/// let first_element: Option<u32> = v_iter.next(); +/// +/// assert_eq!(first_element, Some(1)); +/// assert_eq!(v_iter.next(), Some(2)); +/// assert_eq!(v_iter.next(), None); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// +/// ``` +/// let v = kernel::kvec![]; +/// let mut v_iter = v.into_iter(); +/// +/// let first_element: Option<u32> = v_iter.next(); +/// +/// assert_eq!(first_element, None); +/// +/// # Ok::<(), Error>(()) +/// ``` +#[inline] +fn into_iter (self) ->Self ::IntoIter { +let (ptr, len, cap) = self.into_raw_parts (); + +IntoIter { + ptr, +// SAFETY: `ptr` is either a dangling pointer or a pointer to a valid memory +// allocation, allocated with `A`. + buf:unsafe {NonNull ::new_unchecked (ptr) }, + len, + cap, + _p:PhantomData ::<A >, + } + } +}
Danilo Krummrich
maintainerPATCH v7 15/26 rust: alloc: implement `collect` for `IntoIter`
In reply to Danilo KrummrichCurrently, we can't implement FromIterator. There are a couple of issues with this trait in the kernel, namely:
- Rust's specialization feature is unstable. This prevents us to optimze for the special case where I::IntoIter equals Vec's IntoIter type.
- We also can't use I::IntoIter's type ID either to work around this, since FromIterator doesn't require this type to be 'static.
- FromIterator::from_iter does return Self instead of Result<Self, AllocError>, hence we can't properly handle allocation failures.
- Neither Iterator::collect nor FromIterator::from_iter can handle additional allocation flags.
Instead, provide IntoIter::collect, such that we can at least convert IntoIter into a Vec again.
--- rust/kernel/alloc/kvec.rs | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)
rust/kernel/alloc/kvec.rs +86 -0
@@ -690,6 +690,92 @@ impl<T, A> IntoIter<T, A>fn as_raw_mut_slice (&mut self) -> *mut [T ] { ptr::slice_from_raw_parts_mut (self.ptr, self.len) } + +fn into_raw_parts (self) -> (*mut T ,NonNull <T >,usize ,usize ) { +let me =ManuallyDrop ::new (self); +let ptr = me.ptr; +let buf = me.buf; +let len = me.len; +let cap = me.cap; + (ptr, buf, len, cap) + } + +/// Same as `Iterator::collect` but specialized for `Vec`'s `IntoIter`. +/// +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![1, 2, 3]?; +/// let mut it = v.into_iter(); +/// +/// assert_eq!(it.next(), Some(1)); +/// +/// let v = it.collect(GFP_KERNEL); +/// assert_eq!(v, [2, 3]); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// # Implementation Details +/// +/// Currently, we can't implement `FromIterator`. There are a couple of issues with this trait +/// in the kernel, namely: +/// +/// - Rust's specialization feature is unstable. This prevents us to optimze for the special +/// case where `I::IntoIter` equals `Vec`'s `IntoIter` type. +/// - We also can't use `I::IntoIter`'s type ID either to work around this, since `FromIterator` +/// doesn't require this type to be `'static`. +/// - `FromIterator::from_iter` does return `Self` instead of `Result<Self, AllocError>`, hence +/// we can't properly handle allocation failures. +/// - Neither `Iterator::collect` nor `FromIterator::from_iter` can handle additional allocation +/// flags. +/// +/// Instead, provide `IntoIter::collect`, such that we can at least convert a `IntoIter` into a +/// `Vec` again. +/// +/// Note that `IntoIter::collect` doesn't require `Flags`, since it re-uses the existing backing +/// buffer. However, this backing buffer may be shrunk to the actual count of elements. +pub fn collect (self, flags:Flags ) ->Vec <T ,A > { +let (mut ptr, buf, len,mut cap) = self.into_raw_parts (); +let has_advanced = ptr != buf.as_ptr (); + +if has_advanced { +// Copy the contents we have advanced to at the beginning of the buffer. +// +// SAFETY: +// - `ptr` is valid for reads of `len * size_of::<T>()` bytes, +// - `buf.as_ptr()` is valid for writes of `len * size_of::<T>()` bytes, +// - `ptr` and `buf.as_ptr()` are not be subject to aliasing restrictions relative to +// each other, +// - both `ptr` and `buf.ptr()` are properly aligned. +unsafe { ptr::copy (ptr, buf.as_ptr (), len) }; + ptr = buf.as_ptr (); + } + +// This can never fail, `len` is guaranteed to be smaller than `cap`. +let layout = core::alloc::Layout ::array ::<T >(len).unwrap (); + +// SAFETY: `buf` points to the start of the backing buffer and `len` is guaranteed to be +// smaller than `cap`. Depending on `alloc` this operation may shrink the buffer or leaves +// it as it is. + ptr =match unsafe {A ::realloc (Some (buf.cast ()), layout, flags) } { +// If we fail to shrink, which likely can't even happen, continue with the existing +// buffer. +Err (_) => ptr, +Ok (ptr) => { + cap = len; + ptr.as_ptr ().cast () + } + }; + +// SAFETY: If the iterator has been advanced, the advanced elements have been copied to +// the beginning of the buffer and `len` has been adjusted accordingly. +// +// - `ptr` is guaranteed to point to the start of the backing buffer. +// - `cap` is either the original capacity or, after shrinking the buffer, equal to `len`. +// - `alloc` is guaranteed to be unchanged since `into_iter` has been called on the original +// `Vec`. +unsafe {Vec ::from_raw_parts (ptr, len, cap) } + } }impl <T ,A >Iterator for IntoIter <T ,A >
Signature · 1 line
2.46.0
Re: [PATCH v7 15/26] rust: alloc: implement `collect` for `IntoIter`
Quoted 14 lines · Danilo Krummrich
Currently, we can't implement
FromIterator. There are a couple of issues with this trait in the kernel, namely:- Rust's specialization feature is unstable. This prevents us to optimze for the special case where
I::IntoIterequalsVec'sIntoItertype.
- We also can't useI::IntoIter's type ID either to work around this, sinceFromIteratordoesn't require this type to be'static.
-FromIterator::from_iterdoes returnSelfinstead ofResult<Self, AllocError>, hence we can't properly handle allocation failures.
- NeitherIterator::collectnorFromIterator::from_itercan handle additional allocation flags.
Quoted 8 lines
Instead, provide
IntoIter::collect, such that we can at least convertIntoIterinto aVecagain.Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc/kvec.rs | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)
One comment below, but feel free to keep it as-is.
Quoted 22 lines
rust/kernel/alloc/kvec.rs +22
-0+pub fn collect (self, flags:Flags ) ->Vec <T ,A > { +let (mut ptr, buf, len,mut cap) = self.into_raw_parts (); +let has_advanced = ptr != buf.as_ptr (); + +if has_advanced { +// Copy the contents we have advanced to at the beginning of the buffer. +// +// SAFETY: +// - `ptr` is valid for reads of `len * size_of::<T>()` bytes, +// - `buf.as_ptr()` is valid for writes of `len * size_of::<T>()` bytes, +// - `ptr` and `buf.as_ptr()` are not be subject to aliasing restrictions relative to +// each other, +// - both `ptr` and `buf.ptr()` are properly aligned. +unsafe { ptr::copy (ptr, buf.as_ptr (), len) }; + ptr = buf.as_ptr (); + } + +// This can never fail, `len` is guaranteed to be smaller than `cap`. +let layout = core::alloc::Layout ::array ::<T >(len).unwrap (); + +// SAFETY: `buf` points to the start of the backing buffer and `len` is guaranteed to be +// smaller than `cap`. Depending on `alloc` this operation may shrink the buffer or leaves
rust/kernel/alloc/kvec.rs impl<T, A> IntoIter<T, A> · line 768
Quoted 10 lines
rust/kernel/alloc/kvec.rs +10
-0+// it as it is. + ptr =match unsafe {A ::realloc (Some (buf.cast ()), layout, flags) } { +// If we fail to shrink, which likely can't even happen, continue with the existing +// buffer. +Err (_) => ptr, +Ok (ptr) => { + cap = len; + ptr.as_ptr ().cast () + } + };
Would it make sense to only do the resize if the iterator has advanced? If it hasn't, doing into_iter().collect() would be a no-op, which would make sense IMO.
---
Cheers,
Benno
Quoted 16 lines
rust/kernel/alloc/kvec.rs +10
-1+ +// SAFETY: If the iterator has been advanced, the advanced elements have been copied to +// the beginning of the buffer and `len` has been adjusted accordingly. +// +// - `ptr` is guaranteed to point to the start of the backing buffer. +// - `cap` is either the original capacity or, after shrinking the buffer, equal to `len`. +// - `alloc` is guaranteed to be unchanged since `into_iter` has been called on the original +// `Vec`. +unsafe {Vec ::from_raw_parts (ptr, len, cap) } + } }impl <T ,A >Iterator for IntoIter <T ,A > --2.46.0
Gary Guo
maintainerRe: [PATCH v7 15/26] rust: alloc: implement `collect` for `IntoIter`
In reply to Danilo KrummrichQuoted 14 lines · On Thu, 12 Sep 2024 00:52:51 +0200 Danilo Krummrich <[email protected]> wrote:
Currently, we can't implement
FromIterator. There are a couple of issues with this trait in the kernel, namely:- Rust's specialization feature is unstable. This prevents us to optimze for the special case where
I::IntoIterequalsVec'sIntoItertype.
- We also can't useI::IntoIter's type ID either to work around this, sinceFromIteratordoesn't require this type to be'static.
-FromIterator::from_iterdoes returnSelfinstead ofResult<Self, AllocError>, hence we can't properly handle allocation failures.
- NeitherIterator::collectnorFromIterator::from_itercan handle additional allocation flags.
Quoted 5 lines
Instead, provide
IntoIter::collect, such that we can at least convertIntoIterinto aVecagain.Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
A question is how useful is this? The way this can be used seems fairly limited: you into_iter, consume a few elements, and the collect()?
It feels whatever user this serves, it would make more sense for them to use a VecDeque and just pop_front().
Also, inline comments below.
Quoted 65 lines
--- rust/kernel/alloc/kvec.rs | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)rust/kernel/alloc/kvec.rs +53
-0@@ -690,6 +690,92 @@ impl<T, A> IntoIter<T, A>fn as_raw_mut_slice (&mut self) -> *mut [T ] { ptr::slice_from_raw_parts_mut (self.ptr, self.len) } + +fn into_raw_parts (self) -> (*mut T ,NonNull <T >,usize ,usize ) { +let me =ManuallyDrop ::new (self); +let ptr = me.ptr; +let buf = me.buf; +let len = me.len; +let cap = me.cap; + (ptr, buf, len, cap) + } + +/// Same as `Iterator::collect` but specialized for `Vec`'s `IntoIter`. +/// +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![1, 2, 3]?; +/// let mut it = v.into_iter(); +/// +/// assert_eq!(it.next(), Some(1)); +/// +/// let v = it.collect(GFP_KERNEL); +/// assert_eq!(v, [2, 3]); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// # Implementation Details +/// +/// Currently, we can't implement `FromIterator`. There are a couple of issues with this trait +/// in the kernel, namely: +/// +/// - Rust's specialization feature is unstable. This prevents us to optimze for the special +/// case where `I::IntoIter` equals `Vec`'s `IntoIter` type. +/// - We also can't use `I::IntoIter`'s type ID either to work around this, since `FromIterator` +/// doesn't require this type to be `'static`. +/// - `FromIterator::from_iter` does return `Self` instead of `Result<Self, AllocError>`, hence +/// we can't properly handle allocation failures. +/// - Neither `Iterator::collect` nor `FromIterator::from_iter` can handle additional allocation +/// flags. +/// +/// Instead, provide `IntoIter::collect`, such that we can at least convert a `IntoIter` into a +/// `Vec` again. +/// +/// Note that `IntoIter::collect` doesn't require `Flags`, since it re-uses the existing backing +/// buffer. However, this backing buffer may be shrunk to the actual count of elements. +pub fn collect (self, flags:Flags ) ->Vec <T ,A > { +let (mut ptr, buf, len,mut cap) = self.into_raw_parts (); +let has_advanced = ptr != buf.as_ptr (); + +if has_advanced { +// Copy the contents we have advanced to at the beginning of the buffer. +// +// SAFETY: +// - `ptr` is valid for reads of `len * size_of::<T>()` bytes,
rust/kernel/alloc/kvec.rs impl<T, A> IntoIter<T, A> · line 755
Quoted 10 lines
rust/kernel/alloc/kvec.rs +10
-0+// - `buf.as_ptr()` is valid for writes of `len * size_of::<T>()` bytes, +// - `ptr` and `buf.as_ptr()` are not be subject to aliasing restrictions relative to +// each other, +// - both `ptr` and `buf.ptr()` are properly aligned. +unsafe { ptr::copy (ptr, buf.as_ptr (), len) }; + ptr = buf.as_ptr (); + } + +// This can never fail, `len` is guaranteed to be smaller than `cap`. +let layout = core::alloc::Layout ::array ::<T >(len).unwrap ();
nit: could be unwrap_unchecked(). Although feel free to leave it to avoid an additional unsafe.
rust/kernel/alloc/kvec.rs impl<T, A> IntoIter<T, A> · line 768
Quoted 13 lines
rust/kernel/alloc/kvec.rs +13
-0+ +// SAFETY: `buf` points to the start of the backing buffer and `len` is guaranteed to be +// smaller than `cap`. Depending on `alloc` this operation may shrink the buffer or leaves +// it as it is. + ptr =match unsafe {A ::realloc (Some (buf.cast ()), layout, flags) } { +// If we fail to shrink, which likely can't even happen, continue with the existing +// buffer. +Err (_) => ptr, +Ok (ptr) => { + cap = len; + ptr.as_ptr ().cast () + } + };
This should be moved to Vec::shrink_to_fit. And then this function can just Vec::from_raw_parts(...) and then vec.shrink_to_fit.
But my question would be why this function needs to shrink in the first place.
Quoted 13 lines
rust/kernel/alloc/kvec.rs +10
-0+ +// SAFETY: If the iterator has been advanced, the advanced elements have been copied to +// the beginning of the buffer and `len` has been adjusted accordingly. +// +// - `ptr` is guaranteed to point to the start of the backing buffer. +// - `cap` is either the original capacity or, after shrinking the buffer, equal to `len`. +// - `alloc` is guaranteed to be unchanged since `into_iter` has been called on the original +// `Vec`. +unsafe {Vec ::from_raw_parts (ptr, len, cap) } + } }impl <T ,A >Iterator for IntoIter <T ,A >
Danilo Krummrich
maintainerRe: [PATCH v7 15/26] rust: alloc: implement `collect` for `IntoIter`
Quoted 20 lines · Gary Guo
Quoted 17 lines · On Thu, 12 Sep 2024 00:52:51 +0200 Danilo Krummrich <[email protected]> wrote:
Currently, we can't implement
FromIterator. There are a couple of issues with this trait in the kernel, namely:- Rust's specialization feature is unstable. This prevents us to optimze for the special case where
I::IntoIterequalsVec'sIntoItertype.
- We also can't useI::IntoIter's type ID either to work around this, sinceFromIteratordoesn't require this type to be'static.
-FromIterator::from_iterdoes returnSelfinstead ofResult<Self, AllocError>, hence we can't properly handle allocation failures.
- NeitherIterator::collectnorFromIterator::from_itercan handle additional allocation flags.Instead, provide
IntoIter::collect, such that we can at least convertIntoIterinto aVecagain.
Quoted 6 lines
Quoted 2 lines
Reviewed-by Alice Ryhl <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>A question is how useful is this? The way this can be used seems fairly limited: you
into_iter, consume a few elements, and thecollect()?
Well, it allows us to convert back to a Vec, which we otherwise can't until we implement our own FromIterator trait.
Also note that we want to have this specialization of collect for performance reasons anyways. The Rust stdlib uses specialization traits (which aren't yet stable) for this optimization.
Quoted 3 lines
It feels whatever user this serves, it would make more sense for them to use a
VecDequeand justpop_front().
We don't have VecDeque (yet).
Quoted 73 lines
Also, inline comments below.
Quoted 70 lines
--- rust/kernel/alloc/kvec.rs | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)rust/kernel/alloc/kvec.rs +58
-0@@ -690,6 +690,92 @@ impl<T, A> IntoIter<T, A>fn as_raw_mut_slice (&mut self) -> *mut [T ] { ptr::slice_from_raw_parts_mut (self.ptr, self.len) } + +fn into_raw_parts (self) -> (*mut T ,NonNull <T >,usize ,usize ) { +let me =ManuallyDrop ::new (self); +let ptr = me.ptr; +let buf = me.buf; +let len = me.len; +let cap = me.cap; + (ptr, buf, len, cap) + } + +/// Same as `Iterator::collect` but specialized for `Vec`'s `IntoIter`. +/// +/// # Examples +/// +/// ``` +/// let v = kernel::kvec![1, 2, 3]?; +/// let mut it = v.into_iter(); +/// +/// assert_eq!(it.next(), Some(1)); +/// +/// let v = it.collect(GFP_KERNEL); +/// assert_eq!(v, [2, 3]); +/// +/// # Ok::<(), Error>(()) +/// ``` +/// # Implementation Details +/// +/// Currently, we can't implement `FromIterator`. There are a couple of issues with this trait +/// in the kernel, namely: +/// +/// - Rust's specialization feature is unstable. This prevents us to optimze for the special +/// case where `I::IntoIter` equals `Vec`'s `IntoIter` type. +/// - We also can't use `I::IntoIter`'s type ID either to work around this, since `FromIterator` +/// doesn't require this type to be `'static`. +/// - `FromIterator::from_iter` does return `Self` instead of `Result<Self, AllocError>`, hence +/// we can't properly handle allocation failures. +/// - Neither `Iterator::collect` nor `FromIterator::from_iter` can handle additional allocation +/// flags. +/// +/// Instead, provide `IntoIter::collect`, such that we can at least convert a `IntoIter` into a +/// `Vec` again. +/// +/// Note that `IntoIter::collect` doesn't require `Flags`, since it re-uses the existing backing +/// buffer. However, this backing buffer may be shrunk to the actual count of elements. +pub fn collect (self, flags:Flags ) ->Vec <T ,A > { +let (mut ptr, buf, len,mut cap) = self.into_raw_parts (); +let has_advanced = ptr != buf.as_ptr (); + +if has_advanced { +// Copy the contents we have advanced to at the beginning of the buffer. +// +// SAFETY: +// - `ptr` is valid for reads of `len * size_of::<T>()` bytes, +// - `buf.as_ptr()` is valid for writes of `len * size_of::<T>()` bytes, +// - `ptr` and `buf.as_ptr()` are not be subject to aliasing restrictions relative to +// each other, +// - both `ptr` and `buf.ptr()` are properly aligned. +unsafe { ptr::copy (ptr, buf.as_ptr (), len) };
Quoted 8 lines
rust/kernel/alloc/kvec.rs impl<T, A> IntoIter<T, A> · line 755
Quoted 5 lines
rust/kernel/alloc/kvec.rs +5
-0+ ptr = buf.as_ptr (); + } + +// This can never fail, `len` is guaranteed to be smaller than `cap`. +let layout = core::alloc::Layout ::array ::<T >(len).unwrap ();nit: could be
unwrap_unchecked(). Although feel free to leave it to avoid an additional unsafe.
This will be replaced with ArrayLayout anyways.
Quoted 15 lines
Quoted 4 lines
rust/kernel/alloc/kvec.rs +3
-0+ +// SAFETY: `buf` points to the start of the backing buffer and `len` is guaranteed to be +// smaller than `cap`. Depending on `alloc` this operation may shrink the buffer or leaves rust/kernel/alloc/kvec.rs impl<T, A> IntoIter<T, A> · line 768
Quoted 10 lines
rust/kernel/alloc/kvec.rs +10
-0+// it as it is. + ptr =match unsafe {A ::realloc (Some (buf.cast ()), layout, flags) } { +// If we fail to shrink, which likely can't even happen, continue with the existing +// buffer. +Err (_) => ptr, +Ok (ptr) => { + cap = len; + ptr.as_ptr ().cast () + } + };
Quoted 2 lines
This should be moved to
Vec::shrink_to_fit. And then this function can justVec::from_raw_parts(...)and thenvec.shrink_to_fit.
I'll put it on my list for a follow up patch, I really think we should focus on landing the series now.
Quoted 3 lines
But my question would be why this function needs to shrink in the first place.
It's meant as an optimization for Iterator::collect in the mid / long term. A user expects that the resulting allocation isn't larger than actually needed for Iterator::collect.
Quoted 15 lines
Quoted 14 lines
rust/kernel/alloc/kvec.rs +10
-0+ +// SAFETY: If the iterator has been advanced, the advanced elements have been copied to +// the beginning of the buffer and `len` has been adjusted accordingly. +// +// - `ptr` is guaranteed to point to the start of the backing buffer. +// - `cap` is either the original capacity or, after shrinking the buffer, equal to `len`. +// - `alloc` is guaranteed to be unchanged since `into_iter` has been called on the original +// `Vec`. +unsafe {Vec ::from_raw_parts (ptr, len, cap) } + } }impl <T ,A >Iterator for IntoIter <T ,A >
Danilo Krummrich
maintainerPATCH v7 16/26 rust: treewide: switch to the kernel `Vec` type
In reply to Danilo KrummrichNow that we got the kernel Vec in place, convert all existing Vec users to make use of it.
--- rust/kernel/str.rs | 12 +++++------- rust/kernel/sync/locked_by.rs | 2 +- rust/kernel/types.rs | 2 +- rust/kernel/uaccess.rs | 17 +++++++---------- rust/macros/lib.rs | 6 +++--- samples/rust/rust_minimal.rs | 4 ++-- 6 files changed, 19 insertions(+), 24 deletions(-)
rust/kernel/str.rs +5 -7
@@ -2,8 +2,7 @@//! String representations. -use crate ::alloc::{flags::*, vec_ext::VecExt ,AllocError }; -use alloc::vec::Vec ; +use crate ::alloc::{flags::*,AllocError ,KVec };use core::fmt::{self,Write };use core::ops::{self,Deref ,DerefMut ,Index };
@@ -790,7 +789,7 @@ fn write_str(&mut self, s: &str) -> fmt::Result {/// assert_eq!(s.is_ok(), false); /// ``` pub struct CString { - buf:Vec <u8 >, + buf:KVec <u8 >, }impl CString {
@@ -803,7 +802,7 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {let size = f.bytes_written ();// Allocate a vector with the required number of bytes, and write to it. -let mut buf = <Vec <_ >as VecExt <_ >>::with_capacity (size,GFP_KERNEL )?; +let mut buf =KVec ::with_capacity (size,GFP_KERNEL )?;// SAFETY: The buffer stored in `buf` is at least of size `size` and is valid for writes. let mut f =unsafe {Formatter ::from_buffer (buf.as_mut_ptr (), size) }; f.write_fmt (args)?;
@@ -850,10 +849,9 @@ impl<'a> TryFrom<&'a CStr> for CString {type Error =AllocError ;fn try_from (cstr: &'aCStr ) ->Result <CString ,AllocError > { -let mut buf =Vec ::new (); +let mut buf =KVec ::new (); - <Vec <_ >as VecExt <_ >>::extend_from_slice (&mut buf, cstr.as_bytes_with_nul (),GFP_KERNEL ) - .map_err (|_|AllocError )?; + buf.extend_from_slice (cstr.as_bytes_with_nul (),GFP_KERNEL )?;// INVARIANT: The `CStr` and `CString` types have the same invariants for // the string data, and we copied it over without changes.
rust/kernel/sync/locked_by.rs +1 -1
@@ -43,7 +43,7 @@/// struct InnerDirectory { /// /// The sum of the bytes used by all files. /// bytes_used: u64, -/// _files: Vec<File>, +/// _files: KVec<File>, /// } /// /// struct Directory {
rust/kernel/types.rs +1 -1
@@ -135,7 +135,7 @@ unsafe fn from_foreign(_: *const core::ffi::c_void) -> Self {}/// # use kernel::types::ScopeGuard; /// fn example3(arg: bool) -> Result { /// let mut vec = -/// ScopeGuard::new_with_data(Vec::new(), |v| pr_info!("vec had {} elements\n", v.len())); +/// ScopeGuard::new_with_data(KVec::new(), |v| pr_info!("vec had {} elements\n", v.len())); /// /// vec.push(10u8, GFP_KERNEL)?; /// if arg {
rust/kernel/uaccess.rs +7 -10
@@ -11,7 +11,6 @@ prelude::*, types::{AsBytes, FromBytes}, }; -use alloc::vec::Vec ;use core::ffi::{c_ulong, c_void};use core::mem::{size_of,MaybeUninit };
@@ -46,7 +45,6 @@/// every byte in the region. /// /// ```no_run -/// use alloc::vec::Vec; /// use core::ffi::c_void; /// use kernel::error::Result; /// use kernel::uaccess::{UserPtr, UserSlice};
@@ -54,7 +52,7 @@/// fn bytes_add_one(uptr: UserPtr, len: usize) -> Result<()> { /// let (read, mut write) = UserSlice::new(uptr, len).reader_writer(); /// -/// let mut buf = Vec::new(); +/// let mut buf = KVec::new(); /// read.read_all(&mut buf, GFP_KERNEL)?; /// /// for b in &mut buf {
@@ -69,7 +67,6 @@/// Example illustrating a TOCTOU (time-of-check to time-of-use) bug. /// /// ```no_run -/// use alloc::vec::Vec; /// use core::ffi::c_void; /// use kernel::error::{code::EINVAL, Result}; /// use kernel::uaccess::{UserPtr, UserSlice};
@@ -78,21 +75,21 @@/// fn is_valid(uptr: UserPtr, len: usize) -> Result<bool> { /// let read = UserSlice::new(uptr, len).reader(); /// -/// let mut buf = Vec::new(); +/// let mut buf = KVec::new(); /// read.read_all(&mut buf, GFP_KERNEL)?; /// /// todo!() /// } /// /// /// Returns the bytes behind this user pointer if they are valid. -/// fn get_bytes_if_valid(uptr: UserPtr, len: usize) -> Result<Vec<u8>> { +/// fn get_bytes_if_valid(uptr: UserPtr, len: usize) -> Result<KVec<u8>> { /// if !is_valid(uptr, len)? { /// return Err(EINVAL); /// } /// /// let read = UserSlice::new(uptr, len).reader(); /// -/// let mut buf = Vec::new(); +/// let mut buf = KVec::new(); /// read.read_all(&mut buf, GFP_KERNEL)?; /// /// // THIS IS A BUG! The bytes could have changed since we checked them.
@@ -130,7 +127,7 @@ pub fn new(ptr: UserPtr, length: usize) -> Self {/// Reads the entirety of the user slice, appending it to the end of the provided buffer. /// /// Fails with [`EFAULT`] if the read happens on a bad address. -pub fn read_all (self, buf: &mut Vec <u8 >, flags:Flags ) ->Result { +pub fn read_all (self, buf: &mut KVec <u8 >, flags:Flags ) ->Result { self.reader ().read_all (buf, flags) }
@@ -291,9 +288,9 @@ pub fn read<T: FromBytes>(&mut self) -> Result<T> {/// Reads the entirety of the user slice, appending it to the end of the provided buffer. /// /// Fails with [`EFAULT`] if the read happens on a bad address. -pub fn read_all (mut self, buf: &mut Vec <u8 >, flags:Flags ) ->Result { +pub fn read_all (mut self, buf: &mut KVec <u8 >, flags:Flags ) ->Result {let len = self.length; -VecExt ::<u8 >::reserve (buf, len, flags)?; + buf.reserve (len, flags)?;// The call to `try_reserve` was successful, so the spare capacity is at least `len` bytes // long.
rust/macros/lib.rs +3 -3
@@ -242,7 +242,7 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {/// #[pin_data] /// struct DriverData { /// #[pin] -/// queue: Mutex<Vec<Command>>, +/// queue: Mutex<KVec<Command>>, /// buf: KBox<[u8; 1024 * 1024]>, /// } /// ```
@@ -251,7 +251,7 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {/// #[pin_data(PinnedDrop)] /// struct DriverData { /// #[pin] -/// queue: Mutex<Vec<Command>>, +/// queue: Mutex<KVec<Command>>, /// buf: KBox<[u8; 1024 * 1024]>, /// raw_info: *mut Info, /// }
@@ -281,7 +281,7 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream {/// #[pin_data(PinnedDrop)] /// struct DriverData { /// #[pin] -/// queue: Mutex<Vec<Command>>, +/// queue: Mutex<KVec<Command>>, /// buf: KBox<[u8; 1024 * 1024]>, /// raw_info: *mut Info, /// }
samples/rust/rust_minimal.rs +2 -2
@@ -13,7 +13,7 @@ }struct RustMinimal { - numbers:Vec <i32 >, + numbers:KVec <i32 >, }impl kernel::Module for RustMinimal {
@@ -21,7 +21,7 @@ fn init(_module: &'static ThisModule) -> Result<Self> {pr_info! ("Rust minimal sample (init)\n" );pr_info! ("Am I built-in? {}\n" , !cfg! (MODULE )); -let mut numbers =Vec ::new (); +let mut numbers =KVec ::new (); numbers.push (72 ,GFP_KERNEL )?; numbers.push (108 ,GFP_KERNEL )?; numbers.push (200 ,GFP_KERNEL )?;
Signature · 1 line
2.46.0
Re: [PATCH v7 16/26] rust: treewide: switch to the kernel `Vec` type
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:52 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we got the kernel
Vecin place, convert all existingVecusers to make use of it.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Similar to KBox, please add in the commit message about why Vec doesn't have a default allocator type.
Quoted 216 lines
--- rust/kernel/str.rs | 12 +++++------- rust/kernel/sync/locked_by.rs | 2 +- rust/kernel/types.rs | 2 +- rust/kernel/uaccess.rs | 17 +++++++---------- rust/macros/lib.rs | 6 +++--- samples/rust/rust_minimal.rs | 4 ++-- 6 files changed, 19 insertions(+), 24 deletions(-)rust/kernel/str.rs +5
-7@@ -2,8 +2,7 @@//! String representations. -use crate ::alloc::{flags::*, vec_ext::VecExt ,AllocError }; -use alloc::vec::Vec ; +use crate ::alloc::{flags::*,AllocError ,KVec };use core::fmt::{self,Write };use core::ops::{self,Deref ,DerefMut ,Index };@@ -790,7 +789,7 @@ fn write_str(&mut self, s: &str) -> fmt::Result {/// assert_eq!(s.is_ok(), false); /// ``` pub struct CString { - buf:Vec <u8 >, + buf:KVec <u8 >, }impl CString {@@ -803,7 +802,7 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {let size = f.bytes_written ();// Allocate a vector with the required number of bytes, and write to it. -let mut buf = <Vec <_ >as VecExt <_ >>::with_capacity (size,GFP_KERNEL )?; +let mut buf =KVec ::with_capacity (size,GFP_KERNEL )?;// SAFETY: The buffer stored in `buf` is at least of size `size` and is valid for writes. let mut f =unsafe {Formatter ::from_buffer (buf.as_mut_ptr (), size) }; f.write_fmt (args)?;@@ -850,10 +849,9 @@ impl<'a> TryFrom<&'a CStr> for CString {type Error =AllocError ;fn try_from (cstr: &'aCStr ) ->Result <CString ,AllocError > { -let mut buf =Vec ::new (); +let mut buf =KVec ::new (); - <Vec <_ >as VecExt <_ >>::extend_from_slice (&mut buf, cstr.as_bytes_with_nul (),GFP_KERNEL ) - .map_err (|_|AllocError )?; + buf.extend_from_slice (cstr.as_bytes_with_nul (),GFP_KERNEL )?;// INVARIANT: The `CStr` and `CString` types have the same invariants for // the string data, and we copied it over without changes. rust/kernel/sync/locked_by.rs +1
-1@@ -43,7 +43,7 @@/// struct InnerDirectory { /// /// The sum of the bytes used by all files. /// bytes_used: u64, -/// _files: Vec<File>, +/// _files: KVec<File>, /// } /// /// struct Directory { rust/kernel/types.rs +1
-1@@ -135,7 +135,7 @@ unsafe fn from_foreign(_: *const core::ffi::c_void) -> Self {}/// # use kernel::types::ScopeGuard; /// fn example3(arg: bool) -> Result { /// let mut vec = -/// ScopeGuard::new_with_data(Vec::new(), |v| pr_info!("vec had {} elements\n", v.len())); +/// ScopeGuard::new_with_data(KVec::new(), |v| pr_info!("vec had {} elements\n", v.len())); /// /// vec.push(10u8, GFP_KERNEL)?; /// if arg { rust/kernel/uaccess.rs +7
-10@@ -11,7 +11,6 @@ prelude::*, types::{AsBytes, FromBytes}, }; -use alloc::vec::Vec ;use core::ffi::{c_ulong, c_void};use core::mem::{size_of,MaybeUninit };@@ -46,7 +45,6 @@/// every byte in the region. /// /// ```no_run -/// use alloc::vec::Vec; /// use core::ffi::c_void; /// use kernel::error::Result; /// use kernel::uaccess::{UserPtr, UserSlice}; @@ -54,7 +52,7 @@/// fn bytes_add_one(uptr: UserPtr, len: usize) -> Result<()> { /// let (read, mut write) = UserSlice::new(uptr, len).reader_writer(); /// -/// let mut buf = Vec::new(); +/// let mut buf = KVec::new(); /// read.read_all(&mut buf, GFP_KERNEL)?; /// /// for b in &mut buf { @@ -69,7 +67,6 @@/// Example illustrating a TOCTOU (time-of-check to time-of-use) bug. /// /// ```no_run -/// use alloc::vec::Vec; /// use core::ffi::c_void; /// use kernel::error::{code::EINVAL, Result}; /// use kernel::uaccess::{UserPtr, UserSlice}; @@ -78,21 +75,21 @@/// fn is_valid(uptr: UserPtr, len: usize) -> Result<bool> { /// let read = UserSlice::new(uptr, len).reader(); /// -/// let mut buf = Vec::new(); +/// let mut buf = KVec::new(); /// read.read_all(&mut buf, GFP_KERNEL)?; /// /// todo!() /// } /// /// /// Returns the bytes behind this user pointer if they are valid. -/// fn get_bytes_if_valid(uptr: UserPtr, len: usize) -> Result<Vec<u8>> { +/// fn get_bytes_if_valid(uptr: UserPtr, len: usize) -> Result<KVec<u8>> { /// if !is_valid(uptr, len)? { /// return Err(EINVAL); /// } /// /// let read = UserSlice::new(uptr, len).reader(); /// -/// let mut buf = Vec::new(); +/// let mut buf = KVec::new(); /// read.read_all(&mut buf, GFP_KERNEL)?; /// /// // THIS IS A BUG! The bytes could have changed since we checked them. @@ -130,7 +127,7 @@ pub fn new(ptr: UserPtr, length: usize) -> Self {/// Reads the entirety of the user slice, appending it to the end of the provided buffer. /// /// Fails with [`EFAULT`] if the read happens on a bad address. -pub fn read_all (self, buf: &mut Vec <u8 >, flags:Flags ) ->Result { +pub fn read_all (self, buf: &mut KVec <u8 >, flags:Flags ) ->Result { self.reader ().read_all (buf, flags) }@@ -291,9 +288,9 @@ pub fn read<T: FromBytes>(&mut self) -> Result<T> {/// Reads the entirety of the user slice, appending it to the end of the provided buffer. /// /// Fails with [`EFAULT`] if the read happens on a bad address. -pub fn read_all (mut self, buf: &mut Vec <u8 >, flags:Flags ) ->Result { +pub fn read_all (mut self, buf: &mut KVec <u8 >, flags:Flags ) ->Result {let len = self.length; -VecExt ::<u8 >::reserve (buf, len, flags)?; + buf.reserve (len, flags)?;// The call to `try_reserve` was successful, so the spare capacity is at least `len` bytes // long. rust/macros/lib.rs +3
-3@@ -242,7 +242,7 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {/// #[pin_data] /// struct DriverData { /// #[pin] -/// queue: Mutex<Vec<Command>>, +/// queue: Mutex<KVec<Command>>, /// buf: KBox<[u8; 1024 * 1024]>, /// } /// ``` @@ -251,7 +251,7 @@ pub fn concat_idents(ts: TokenStream) -> TokenStream {/// #[pin_data(PinnedDrop)] /// struct DriverData { /// #[pin] -/// queue: Mutex<Vec<Command>>, +/// queue: Mutex<KVec<Command>>, /// buf: KBox<[u8; 1024 * 1024]>, /// raw_info: *mut Info, /// } @@ -281,7 +281,7 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream {/// #[pin_data(PinnedDrop)] /// struct DriverData { /// #[pin] -/// queue: Mutex<Vec<Command>>, +/// queue: Mutex<KVec<Command>>, /// buf: KBox<[u8; 1024 * 1024]>, /// raw_info: *mut Info, /// } samples/rust/rust_minimal.rs +2
-2@@ -13,7 +13,7 @@ }struct RustMinimal { - numbers:Vec <i32 >, + numbers:KVec <i32 >, }impl kernel::Module for RustMinimal {@@ -21,7 +21,7 @@ fn init(_module: &'static ThisModule) -> Result<Self> {pr_info! ("Rust minimal sample (init)\n" );pr_info! ("Am I built-in? {}\n" , !cfg! (MODULE )); -let mut numbers =Vec ::new (); +let mut numbers =KVec ::new (); numbers.push (72 ,GFP_KERNEL )?; numbers.push (108 ,GFP_KERNEL )?; numbers.push (200 ,GFP_KERNEL )?;
Danilo Krummrich
maintainerPATCH v7 17/26 rust: alloc: remove `VecExt` extension
In reply to Danilo KrummrichNow that all existing Vec users were moved to the kernel Vec type, remove the VecExt extension.
--- rust/kernel/alloc.rs | 1 - rust/kernel/alloc/vec_ext.rs | 185 ----------------------------------- rust/kernel/prelude.rs | 5 +- 3 files changed, 1 insertion(+), 190 deletions(-) delete mode 100644 rust/kernel/alloc/vec_ext.rs
rust/kernel/alloc.rs +0 -1
@@ -6,7 +6,6 @@pub mod allocator;pub mod kbox;pub mod kvec; -pub mod vec_ext;#[cfg(any(test, testlib))] pub mod allocator_test;
rust/kernel/alloc/vec_ext.rs deleted +0 -185
185 lines not on this page · raw message
rust/kernel/prelude.rs +1 -4
@@ -14,10 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec }; - -#[doc(no_inline)] -pub use alloc::vec::Vec ; +pub use crate ::alloc::{flags::*,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec };#[doc(no_inline)] pub use macros::{module, pin_data, pinned_drop, vtable,Zeroable };
Signature · 1 line
2.46.0
Re: [PATCH v7 17/26] rust: alloc: remove `VecExt` extension
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:53 +0200 Danilo Krummrich <[email protected]> wrote:
Now that all existing
Vecusers were moved to the kernelVectype, remove theVecExtextension.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 226 lines
--- rust/kernel/alloc.rs | 1 - rust/kernel/alloc/vec_ext.rs | 185 ----------------------------------- rust/kernel/prelude.rs | 5 +- 3 files changed, 1 insertion(+), 190 deletions(-) delete mode 100644 rust/kernel/alloc/vec_ext.rsrust/kernel/alloc.rs +0
-1@@ -6,7 +6,6 @@pub mod allocator;pub mod kbox;pub mod kvec; -pub mod vec_ext;#[cfg(any(test, testlib))] pub mod allocator_test;rust/kernel/alloc/vec_ext.rs deleted +0
-185@@ -1,185 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -//! Extensions to [`Vec`] for fallible allocations. - -use super ::{AllocError ,Flags }; -use alloc::vec::Vec ; - -/// Extensions to [`Vec`]. -pub trait VecExt <T >:Sized { -/// Creates a new [`Vec`] instance with at least the given capacity. -/// -/// # Examples -/// -/// ``` -/// let v = Vec::<u32>::with_capacity(20, GFP_KERNEL)?; -/// -/// assert!(v.capacity() >= 20); -/// # Ok::<(), Error>(()) -/// ``` -fn with_capacity (capacity:usize , flags:Flags ) ->Result <Self ,AllocError >; - -/// Appends an element to the back of the [`Vec`] instance. -/// -/// # Examples -/// -/// ``` -/// let mut v = Vec::new(); -/// v.push(1, GFP_KERNEL)?; -/// assert_eq!(&v, &[1]); -/// -/// v.push(2, GFP_KERNEL)?; -/// assert_eq!(&v, &[1, 2]); -/// # Ok::<(), Error>(()) -/// ``` -fn push (&mut self, v:T , flags:Flags ) ->Result <(),AllocError >; - -/// Pushes clones of the elements of slice into the [`Vec`] instance. -/// -/// # Examples -/// -/// ``` -/// let mut v = Vec::new(); -/// v.push(1, GFP_KERNEL)?; -/// -/// v.extend_from_slice(&[20, 30, 40], GFP_KERNEL)?; -/// assert_eq!(&v, &[1, 20, 30, 40]); -/// -/// v.extend_from_slice(&[50, 60], GFP_KERNEL)?; -/// assert_eq!(&v, &[1, 20, 30, 40, 50, 60]); -/// # Ok::<(), Error>(()) -/// ``` -fn extend_from_slice (&mut self, other: &[T ], flags:Flags ) ->Result <(),AllocError > -where -T :Clone ; - -/// Ensures that the capacity exceeds the length by at least `additional` elements. -/// -/// # Examples -/// -/// ``` -/// let mut v = Vec::new(); -/// v.push(1, GFP_KERNEL)?; -/// -/// v.reserve(10, GFP_KERNEL)?; -/// let cap = v.capacity(); -/// assert!(cap >= 10); -/// -/// v.reserve(10, GFP_KERNEL)?; -/// let new_cap = v.capacity(); -/// assert_eq!(new_cap, cap); -/// -/// # Ok::<(), Error>(()) -/// ``` -fn reserve (&mut self, additional:usize , flags:Flags ) ->Result <(),AllocError >; -} - -impl <T >VecExt <T >for Vec <T > { -fn with_capacity (capacity:usize , flags:Flags ) ->Result <Self ,AllocError > { -let mut v =Vec ::new (); - <Self as VecExt <_ >>::reserve (&mut v, capacity, flags)?; -Ok (v) - } - -fn push (&mut self, v:T , flags:Flags ) ->Result <(),AllocError > { - <Self as VecExt <_ >>::reserve (self,1 , flags)?; -let s = self.spare_capacity_mut (); - s[0 ].write (v); - -// SAFETY: We just initialised the first spare entry, so it is safe to increase the length -// by 1. We also know that the new length is <= capacity because of the previous call to -// `reserve` above. -unsafe { self.set_len (self.len () +1 ) }; -Ok (()) - } - -fn extend_from_slice (&mut self, other: &[T ], flags:Flags ) ->Result <(),AllocError > -where -T :Clone , - { - <Self as VecExt <_ >>::reserve (self, other.len (), flags)?; -for (slot, item)in core::iter::zip (self.spare_capacity_mut (), other) { - slot.write (item.clone ()); - } - -// SAFETY: We just initialised the `other.len()` spare entries, so it is safe to increase -// the length by the same amount. We also know that the new length is <= capacity because -// of the previous call to `reserve` above. -unsafe { self.set_len (self.len () + other.len ()) }; -Ok (()) - } - -#[cfg(any(test, testlib))] -fn reserve (&mut self, additional:usize , _flags:Flags ) ->Result <(),AllocError > { -Vec ::reserve (self, additional); -Ok (()) - } - -#[cfg(not(any(test, testlib)))] -fn reserve (&mut self, additional:usize , flags:Flags ) ->Result <(),AllocError > { -let len = self.len (); -let cap = self.capacity (); - -if cap - len >= additional { -return Ok (()); - } - -if core::mem::size_of ::<T >() ==0 { -// The capacity is already `usize::MAX` for SZTs, we can't go higher. -return Err (AllocError ); - } - -// We know cap is <= `isize::MAX` because `Layout::array` fails if the resulting byte size -// is greater than `isize::MAX`. So the multiplication by two won't overflow. -let new_cap = core::cmp::max (cap *2 , len.checked_add (additional).ok_or (AllocError )?); -let layout = core::alloc::Layout ::array ::<T >(new_cap).map_err (|_|AllocError )?; - -let (old_ptr, len, cap) =destructure (self); - -// We need to make sure that `ptr` is either NULL or comes from a previous call to -// `krealloc_aligned`. A `Vec<T>`'s `ptr` value is not guaranteed to be NULL and might be -// dangling after being created with `Vec::new`. Instead, we can rely on `Vec<T>`'s capacity -// to be zero if no memory has been allocated yet. -let ptr =if cap ==0 { - core::ptr::null_mut () - }else { - old_ptr - }; - -// SAFETY: `ptr` is valid because it's either NULL or comes from a previous call to -// `krealloc_aligned`. We also verified that the type is not a ZST. -let new_ptr =unsafe {super ::allocator::krealloc_aligned (ptr.cast (), layout, flags) }; -if new_ptr.is_null () { -// SAFETY: We are just rebuilding the existing `Vec` with no changes. -unsafe {rebuild (self, old_ptr, len, cap) }; -Err (AllocError ) - }else { -// SAFETY: `ptr` has been reallocated with the layout for `new_cap` elements. New cap -// is greater than `cap`, so it continues to be >= `len`. -unsafe {rebuild (self, new_ptr.cast ::<T >(), len, new_cap) }; -Ok (()) - } - } -} - -#[cfg(not(any(test, testlib)))] -fn destructure <T >(v: &mut Vec <T >) -> (*mut T ,usize ,usize ) { -let mut tmp =Vec ::new (); - core::mem::swap (&mut tmp, v); -let mut tmp = core::mem::ManuallyDrop ::new (tmp); -let len = tmp.len (); -let cap = tmp.capacity (); - (tmp.as_mut_ptr (), len, cap) -} - -/// Rebuilds a `Vec` from a pointer, length, and capacity. -/// -/// # Safety -/// -/// The same as [`Vec::from_raw_parts`]. -#[cfg(not(any(test, testlib)))] -unsafe fn rebuild <T >(v: &mut Vec <T >, ptr: *mut T , len:usize , cap:usize ) { -// SAFETY: The safety requirements from this function satisfy those of `from_raw_parts`. -let mut tmp =unsafe {Vec ::from_raw_parts (ptr, len, cap) }; - core::mem::swap (&mut tmp, v); -}rust/kernel/prelude.rs +1
-4@@ -14,10 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*, vec_ext::VecExt ,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec }; - -#[doc(no_inline)] -pub use alloc::vec::Vec ; +pub use crate ::alloc::{flags::*,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec };#[doc(no_inline)] pub use macros::{module, pin_data, pinned_drop, vtable,Zeroable };
Danilo Krummrich
maintainerPATCH v7 18/26 rust: alloc: add `Vec` to prelude
In reply to Danilo KrummrichNow that we removed VecExt and the corresponding includes in prelude.rs, add the new kernel Vec type instead.
--- rust/kernel/prelude.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
rust/kernel/prelude.rs +1 -1
@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec }; +pub use crate ::alloc::{flags::*,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec ,Vec };#[doc(no_inline)] pub use macros::{module, pin_data, pinned_drop, vtable,Zeroable };
Signature · 1 line
2.46.0
Re: [PATCH v7 18/26] rust: alloc: add `Vec` to prelude
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:54 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we removed
VecExtand the corresponding includes inprelude.rs, add the new kernelVectype instead.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 17 lines
--- rust/kernel/prelude.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)rust/kernel/prelude.rs +1
-1@@ -14,7 +14,7 @@#[doc(no_inline)] pub use core::pin::Pin ; -pub use crate ::alloc::{flags::*,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec }; +pub use crate ::alloc::{flags::*,Box ,KBox ,KVBox ,KVVec ,KVec ,VBox ,VVec ,Vec };#[doc(no_inline)] pub use macros::{module, pin_data, pinned_drop, vtable,Zeroable };
Danilo Krummrich
maintainerPATCH v7 19/26 rust: error: use `core::alloc::LayoutError`
In reply to Danilo KrummrichUse core::alloc::LayoutError instead of alloc::alloc::LayoutError in preparation to get rid of Rust's alloc crate.
--- rust/kernel/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
rust/kernel/error.rs +1 -1
@@ -6,7 +6,7 @@use crate ::{alloc::AllocError , str::CStr }; -use alloc::alloc::LayoutError ; +use core::alloc::LayoutError ;use core::fmt;use core::num::TryFromIntError ;
Signature · 1 line
2.46.0
Re: [PATCH v7 19/26] rust: error: use `core::alloc::LayoutError`
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:55 +0200 Danilo Krummrich <[email protected]> wrote:
Use
core::alloc::LayoutErrorinstead ofalloc::alloc::LayoutErrorin preparation to get rid of Rust's alloc crate.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 17 lines
--- rust/kernel/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)rust/kernel/error.rs +1
-1@@ -6,7 +6,7 @@use crate ::{alloc::AllocError , str::CStr }; -use alloc::alloc::LayoutError ; +use core::alloc::LayoutError ;use core::fmt;use core::num::TryFromIntError ;
Danilo Krummrich
maintainerPATCH v7 20/26 rust: error: check for config `test` in `Error::name`
In reply to Danilo KrummrichAdditional to testlib also check for test in Error::name. This is required by a subsequent patch that (indirectly) uses Error in test cases.
--- rust/kernel/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
rust/kernel/error.rs +2 -2
@@ -143,7 +143,7 @@ pub(crate) fn to_ptr<T>(self) -> *mut T { }/// Returns a string representing the error, if one exists. -#[cfg(not(testlib))] +#[cfg(not(any(test, testlib)))] pub fn name (&self) ->Option <&'staticCStr > {// SAFETY: Just an FFI call, there are no extra safety requirements. let ptr =unsafe { bindings::errname (-self.0 ) };
@@ -160,7 +160,7 @@ pub fn name(&self) -> Option<&'static CStr> {/// When `testlib` is configured, this always returns `None` to avoid the dependency on a /// kernel function so that tests that use this (e.g., by calling [`Result::unwrap`]) can still /// run in userspace. -#[cfg(testlib)] +#[cfg(any(test, testlib))] pub fn name (&self) ->Option <&'staticCStr > {None }
Signature · 1 line
2.46.0
Re: [PATCH v7 20/26] rust: error: check for config `test` in `Error::name`
Quoted 7 lines · On Thu, 12 Sep 2024 00:52:56 +0200 Danilo Krummrich <[email protected]> wrote:
Additional to
testlibalso check fortestinError::name. This is required by a subsequent patch that (indirectly) usesErrorin test cases.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 26 lines
--- rust/kernel/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)rust/kernel/error.rs +2
-2@@ -143,7 +143,7 @@ pub(crate) fn to_ptr<T>(self) -> *mut T { }/// Returns a string representing the error, if one exists. -#[cfg(not(testlib))] +#[cfg(not(any(test, testlib)))] pub fn name (&self) ->Option <&'staticCStr > {// SAFETY: Just an FFI call, there are no extra safety requirements. let ptr =unsafe { bindings::errname (-self.0 ) };@@ -160,7 +160,7 @@ pub fn name(&self) -> Option<&'static CStr> {/// When `testlib` is configured, this always returns `None` to avoid the dependency on a /// kernel function so that tests that use this (e.g., by calling [`Result::unwrap`]) can still /// run in userspace. -#[cfg(testlib)] +#[cfg(any(test, testlib))] pub fn name (&self) ->Option <&'staticCStr > {None }
Danilo Krummrich
maintainerPATCH v7 21/26 rust: alloc: implement `contains` for `Flags`
In reply to Danilo KrummrichProvide a simple helper function to check whether given flags do contain one or multiple other flags.
This is used by a subsequent patch implementing the Cmalloc Allocator to check for __GFP_ZERO.
--- rust/kernel/alloc.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
rust/kernel/alloc.rs +6 -1
@@ -34,7 +34,7 @@/// They can be combined with the operators `|`, `&`, and `!`. /// /// Values can be used from the [`flags`] module. -#[derive( Clone , Copy )] +#[derive( Clone , Copy , PartialEq )] pub struct Flags (u32 );impl Flags {
@@ -42,6 +42,11 @@ impl Flags {pub (crate )fn as_raw (self) ->u32 { self.0 } + +/// Check whether `flags` is contained in `self`. +pub fn contains (self, flags:Flags ) ->bool { + (self & flags) == flags + } }impl core::ops::BitOr for Flags {
Signature · 1 line
2.46.0
Re: [PATCH v7 21/26] rust: alloc: implement `contains` for `Flags`
Quoted 9 lines · On Thu, 12 Sep 2024 00:52:57 +0200 Danilo Krummrich <[email protected]> wrote:
Provide a simple helper function to check whether given flags do contain one or multiple other flags.
This is used by a subsequent patch implementing the Cmalloc
Allocatorto check for__GFP_ZERO.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 29 lines
--- rust/kernel/alloc.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)rust/kernel/alloc.rs +6
-1@@ -34,7 +34,7 @@/// They can be combined with the operators `|`, `&`, and `!`. /// /// Values can be used from the [`flags`] module. -#[derive( Clone , Copy )] +#[derive( Clone , Copy , PartialEq )] pub struct Flags (u32 );impl Flags {@@ -42,6 +42,11 @@ impl Flags {pub (crate )fn as_raw (self) ->u32 { self.0 } + +/// Check whether `flags` is contained in `self`. +pub fn contains (self, flags:Flags ) ->bool { + (self & flags) == flags + } }impl core::ops::BitOr for Flags {
Danilo Krummrich
maintainerPATCH v7 22/26 rust: alloc: implement `Cmalloc` in module allocator_test
In reply to Danilo KrummrichSo far the kernel's Box and Vec types can't be used by userspace test cases, since all users of those types (e.g. CString) use kernel allocators for instantiation.
In order to allow userspace test cases to make use of such types as well, implement the Cmalloc allocator within the allocator_test module and type alias all kernel allocators to Cmalloc. The Cmalloc allocator uses libc's realloc() function as allocator backend.
--- rust/kernel/alloc/allocator_test.rs | 193 +++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 7 deletions(-)
rust/kernel/alloc/allocator_test.rs +186 -7
@@ -1,21 +1,200 @@// SPDX-License-Identifier: GPL-2.0 +//! So far the kernel's `Box` and `Vec` types can't be used by userspace test cases, since all users +//! of those types (e.g. `CString`) use kernel allocators for instantiation. +//! +//! In order to allow userspace test cases to make use of such types as well, implement the +//! `Cmalloc` allocator within the allocator_test module and type alias all kernel allocators to +//! `Cmalloc`. The `Cmalloc` allocator uses libc's realloc() function as allocator backend. +#![allow(missing_docs)] -use super ::{AllocError ,Allocator ,Flags }; +use super ::{flags::*,AllocError ,Allocator ,Flags };use core::alloc::Layout ; +use core::cmp; +use core::mem; +use core::ptr;use core::ptr::NonNull ; -pub struct Kmalloc ; +/// The userspace allocator based on libc. +pub struct Cmalloc ; + +pub type Kmalloc =Cmalloc ;pub type Vmalloc =Kmalloc ;pub type KVmalloc =Kmalloc ; -unsafe impl Allocator for Kmalloc { +extern "C" { +#[link_name = "aligned_alloc" ] +fn libc_aligned_alloc (align:usize , size:usize ) -> *mut core::ffi::c_void ; + +#[link_name = "free" ] +fn libc_free (ptr: *mut core::ffi::c_void ); +} + +struct CmallocData { +// The actual size as requested through `Cmalloc::alloc` or `Cmalloc::realloc`. + size:usize , +// The offset from the pointer returned to the caller of `Cmalloc::alloc` or `Cmalloc::realloc` +// to the actual base address of the allocation. + offset:usize , +} + +impl Cmalloc { +/// Adjust the size and alignment such that we can additionally store `CmallocData` right +/// before the actual data described by `layout`. +/// +/// Example: +/// +/// - For `CmallocData` assume an alignment of 8 and a size of 16. +/// - For `layout` assume and alignment of 16 and a size of 64. +/// +///```text +/// 0 16 32 96 +/// |----------------|----------------|------------------------------------------------| +/// empty CmallocData data +///``` +/// +/// For this example the returned `Layout` has an alignment of 32 and a size of 96. +fn layout_adjust (layout:Layout ) ->Result <Layout ,AllocError > { +let layout = layout.pad_to_align (); + +// Ensure that `CmallocData` fits into half the alignment. Additionally, this guarantees +// that advancing a pointer aligned to `align` by `align / 2` we still satisfy or exceed +// the alignment requested through `layout`. +let align = cmp::max ( + layout.align (), + mem::size_of ::<CmallocData >().next_power_of_two (), + ) *2 ; + +// Add the additional space required for `CmallocData`. +let size = layout.size () + mem::size_of ::<CmallocData >(); + +Ok (Layout ::from_size_align (size, align) + .map_err (|_|AllocError )? + .pad_to_align ()) + } + +fn alloc_store_data (layout:Layout ) ->Result <NonNull <u8 >,AllocError > { +let requested_size = layout.size (); + +let layout =Self ::layout_adjust (layout)?; +let min_align = layout.align () /2 ; + +// SAFETY: Returns either NULL or a pointer to a memory allocation that satisfies or +// exceeds the given size and alignment requirements. +let raw_ptr =unsafe {libc_aligned_alloc (layout.align (), layout.size ()) }as *mut u8 ; + +let priv_ptr =NonNull ::new (raw_ptr).ok_or (AllocError )?; + +// SAFETY: +// - By adding `min_align` the pointer remains within the allocated object and `min_align` +// can not exceed `isize`. +// +// GUARANTEE: +// - The adjustments from `Self::layout_adjust` ensure that after this operation the +// original size and alignment requirements are still satisfied or exceeded. +let ptr =unsafe { priv_ptr.as_ptr ().add (min_align) }; + +// SAFETY: `min_align` is greater than or equal to the size of `CmallocData`, hence we +// don't exceed the allocation boundaries. +let data_ptr: *mut CmallocData =unsafe { ptr.sub (mem::size_of ::<CmallocData >()) }.cast (); + +let data =CmallocData { + size: requested_size, + offset: min_align, + }; + +// SAFETY: `data_ptr` is properly aligned and within the allocation boundaries reserved for +// `CmallocData`. +unsafe { data_ptr.write (data) }; + +NonNull ::new (ptr).ok_or (AllocError ) + } + +/// # Safety +/// +/// - `ptr` must have been previously allocated with `Self::alloc_store_data`. +unsafe fn data (ptr:NonNull <u8 >) ->CmallocData { +// SAFETY: `Self::alloc_store_data` stores the `CmallocData` right before the address +// returned to callers of `Self::alloc_store_data`. +let data_ptr: *mut CmallocData = +unsafe { ptr.as_ptr ().sub (mem::size_of ::<CmallocData >()) }.cast (); + +// `CmallocData` has been previously stored at this offset with `Self::alloc_store_data`. +// +// SAFETY: +// - `data_ptr` points to a properly aligned and initialized value of `CmallocData`. +unsafe { core::ptr::read (data_ptr) } + } + +/// # Safety +/// +/// This function must not be called more than once for the same allocation. +/// +/// - `ptr` must have been previously allocated with `Self::alloc_store_data`. +unsafe fn free_read_data (ptr:NonNull <u8 >) { +// SAFETY: `ptr` has been created by `Self::alloc_store_data`. +let data =unsafe {Self ::data (ptr) }; + +// SAFETY: `ptr` has been created by `Self::alloc_store_data`. +let priv_ptr =unsafe { ptr.as_ptr ().sub (data.offset) }; + +// SAFETY: `priv_ptr` has previously been allocatored with this `Allocator`. +unsafe {libc_free (priv_ptr.cast ()) }; + } +} + +unsafe impl Allocator for Cmalloc { +fn alloc (layout:Layout , flags:Flags ) ->Result <NonNull <[u8 ]>,AllocError > { +if layout.size () ==0 { +return Ok (NonNull ::slice_from_raw_parts (NonNull ::dangling (),0 )); + } + +let ptr =Self ::alloc_store_data (layout)?; + +if flags.contains (__GFP_ZERO) { +// SAFETY: `Self::alloc_store_data` guarantees that `ptr` points to memory of at least +// `layout.size()` bytes. +unsafe { ptr.as_ptr ().write_bytes (0 , layout.size ()) }; + } + +Ok (NonNull ::slice_from_raw_parts (ptr, layout.size ())) + } +unsafe fn realloc ( - _ptr:Option <NonNull <u8 >>, - _layout:Layout , - _flags:Flags , + ptr:Option <NonNull <u8 >>, + layout:Layout , + flags:Flags , ) ->Result <NonNull <[u8 ]>,AllocError > { -panic! (); +let Some (src) = ptrelse { +return Self ::alloc (layout, flags); + }; + +if layout.size () ==0 { +// SAFETY: `src` has been created by `Self::alloc_store_data`. +unsafe {Self ::free_read_data (src) }; + +return Ok (NonNull ::slice_from_raw_parts (NonNull ::dangling (),0 )); + } + +let dst =Self ::alloc (layout, flags)?; + +// SAFETY: `src` has been created by `Self::alloc_store_data`. +let data =unsafe {Self ::data (src) }; + +// SAFETY: `src` has previously been allocated with this `Allocator`; `dst` has just been +// newly allocated. Copy up to the smaller of both sizes. +unsafe { + ptr::copy_nonoverlapping ( + src.as_ptr (), + dst.as_ptr ().cast (), + cmp::min (layout.size (), data.size), + ) + }; + +// SAFETY: `src` has been created by `Self::alloc_store_data`. +unsafe {Self ::free_read_data (src) }; + +Ok (dst) } }
Signature · 1 line
2.46.0
Gary Guo
maintainerRe: [PATCH v7 22/26] rust: alloc: implement `Cmalloc` in module allocator_test
Quoted 8 lines · On Thu, 12 Sep 2024 00:52:58 +0200 Danilo Krummrich <[email protected]> wrote:
So far the kernel's
BoxandVectypes can't be used by userspace test cases, since all users of those types (e.g.CString) use kernel allocators for instantiation.In order to allow userspace test cases to make use of such types as well, implement the
Cmallocallocator within theallocator_testmodule and type alias all kernel allocators toCmalloc. TheCmallocallocator uses libc'srealloc()function as allocator backend.
I think this needs an explaination of why it needs a complex implementation instead of just using aligned_alloc and free.
Quoted 5 lines
Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc/allocator_test.rs | 193 +++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 7 deletions(-)
Danilo Krummrich
maintainerRe: [PATCH v7 22/26] rust: alloc: implement `Cmalloc` in module allocator_test
Quoted 7 lines · Gary Guo
Quoted 4 lines · On Thu, 12 Sep 2024 00:52:58 +0200 Danilo Krummrich <[email protected]> wrote:
So far the kernel's
BoxandVectypes can't be used by userspace test cases, since all users of those types (e.g.CString) use kernel allocators for instantiation.
Quoted 7 lines
Quoted 4 lines
In order to allow userspace test cases to make use of such types as well, implement the
Cmallocallocator within theallocator_testmodule and type alias all kernel allocators toCmalloc. TheCmallocallocator uses libc'srealloc()function as allocator backend.I think this needs an explaination of why it needs a complex implementation instead of just using
aligned_allocand free.
The reason basically is that there's no aligned_realloc in libc. Anyways, it's gonna simplified by the introduction of old_layout.
Quoted 7 lines
Quoted 6 lines
Signed-off-by Danilo Krummrich <[email protected]>--- rust/kernel/alloc/allocator_test.rs | 193 +++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 7 deletions(-)
Danilo Krummrich
maintainerPATCH v7 23/26 rust: str: test: replace `alloc::format`
In reply to Danilo KrummrichThe current implementation of tests in str.rs use format! to format strings for comparison, which, internally, creates a new String.
In order to prepare for getting rid of Rust's alloc crate, we have to cut this dependency. Instead, implement format! for CString.
Note that for userspace tests, Kmalloc, which is backing CString's memory, is just a type alias to Cmalloc.
--- rust/kernel/str.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)
rust/kernel/str.rs +22 -1
@@ -523,7 +523,28 @@ macro_rules! c_str {#[cfg(test)] mod tests {use super ::*; -use alloc::format; + +struct String (CString ); + +impl String { +fn from_fmt (args: fmt::Arguments <'_>) ->Self { +String (CString ::try_from_fmt (args).unwrap ()) + } + } + +impl Deref for String { +type Target =str ; + +fn deref (&self) -> &str { + self.0 .to_str ().unwrap () + } + } + +macro_rules! format { + ($($f:tt)*) => ({ + &*String ::from_fmt (kernel::fmt!($($f)*)) + }) + }const ALL_ASCII_CHARS : &'staticstr ="\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\
Signature · 1 line
2.46.0
Re: [PATCH v7 23/26] rust: str: test: replace `alloc::format`
Quoted 6 lines · On Thu, 12 Sep 2024 00:52:59 +0200 Danilo Krummrich <[email protected]> wrote:
The current implementation of tests in
str.rsuseformat!to format strings for comparison, which, internally, creates a newString.In order to prepare for getting rid of Rust's alloc crate, we have to cut this dependency. Instead, implement
format!forCString.
Quoted 6 lines
Note that for userspace tests,
Kmalloc, which is backingCString's memory, is just a type alias toCmalloc.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 38 lines
--- rust/kernel/str.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)rust/kernel/str.rs +22
-1@@ -523,7 +523,28 @@ macro_rules! c_str {#[cfg(test)] mod tests {use super ::*; -use alloc::format; + +struct String (CString ); + +impl String { +fn from_fmt (args: fmt::Arguments <'_>) ->Self { +String (CString ::try_from_fmt (args).unwrap ()) + } + } + +impl Deref for String { +type Target =str ; + +fn deref (&self) -> &str { + self.0 .to_str ().unwrap () + } + } + +macro_rules! format { + ($($f:tt)*) => ({ + &*String ::from_fmt (kernel::fmt!($($f)*)) + }) + }const ALL_ASCII_CHARS : &'staticstr ="\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\
Danilo Krummrich
maintainerPATCH v7 24/26 rust: alloc: update module comment of alloc.rs
In reply to Danilo KrummrichBefore we remove Rust's alloc crate, rewrite the module comment in alloc.rs to avoid a rustdoc warning.
Besides that, the module comment in alloc.rs isn't correct anymore, we're no longer extending Rust's alloc crate.
--- rust/kernel/alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
rust/kernel/alloc.rs +1 -1
@@ -1,6 +1,6 @@// SPDX-License-Identifier: GPL-2.0 -//! Extensions to the [`alloc`] crate. +//! Implementation of the kernel's memory allocation infrastructure. #[cfg(not(any(test, testlib)))] pub mod allocator;
Signature · 1 line
2.46.0
Re: [PATCH v7 24/26] rust: alloc: update module comment of alloc.rs
Quoted 9 lines · On Thu, 12 Sep 2024 00:53:00 +0200 Danilo Krummrich <[email protected]> wrote:
Before we remove Rust's alloc crate, rewrite the module comment in
alloc.rsto avoid a rustdoc warning.Besides that, the module comment in
alloc.rsisn't correct anymore, we're no longer extending Rust's alloc crate.Reviewed-by Alice Ryhl <[email protected]>Reviewed-by Benno Lossin <[email protected]>Signed-off-by Danilo Krummrich <[email protected]>
Quoted 16 lines
--- rust/kernel/alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)rust/kernel/alloc.rs +1
-1@@ -1,6 +1,6 @@// SPDX-License-Identifier: GPL-2.0 -//! Extensions to the [`alloc`] crate. +//! Implementation of the kernel's memory allocation infrastructure. #[cfg(not(any(test, testlib)))] pub mod allocator;
Danilo Krummrich
maintainerPATCH v7 25/26 kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
In reply to Danilo KrummrichNow that we have our own Allocator, Box and Vec types we can remove Rust's alloc crate and the new_uninit unstable feature.
Also remove Kmalloc's GlobalAlloc implementation -- we can't remove this in a separate patch, since the alloc crate requires a #[global_allocator] to set, that implements GlobalAlloc.
--- rust/Makefile | 43 +++++---------------- rust/exports.c | 1 - rust/kernel/alloc/allocator.rs | 63 +------------------------------ scripts/Makefile.build | 7 +--- scripts/generate_rust_analyzer.py | 11 +----- 5 files changed, 15 insertions(+), 110 deletions(-)
rust/Makefile +10 -33
@@ -15,8 +15,8 @@ always-$(CONFIG_RUST) += libmacros.so no-clean-files += libmacros.so always-$(CONFIG_RUST) += bindings/bindings_generated.rs bindings/bindings_helpers_generated.rs -obj-$(CONFIG_RUST) += alloc.o bindings.o kernel.o -always-$(CONFIG_RUST) += exports_alloc_generated.h exports_helpers_generated.h \ +obj-$(CONFIG_RUST) += bindings.o kernel.o +always-$(CONFIG_RUST) += exports_helpers_generated.h \ exports_bindings_generated.h exports_kernel_generated.h always-$(CONFIG_RUST) += uapi/uapi_generated.rs
@@ -53,11 +53,6 @@ endif core-cfgs = \ --cfg no_fp_fmt_parse -alloc-cfgs = \ - --cfg no_global_oom_handling \ - --cfg no_rc \ - --cfg no_sync - quiet_cmd_rustdoc = RUSTDOC $(if $(rustdoc_host),H, ) $< cmd_rustdoc = \ OBJTREE=$(abspath $(objtree)) \
@@ -81,7 +76,7 @@ quiet_cmd_rustdoc = RUSTDOC $(if $(rustdoc_host),H, ) $< # command-like flags to solve the issue. Meanwhile, we use the non-custom case # and then retouch the generated files. rustdoc: rustdoc-core rustdoc-macros rustdoc-compiler_builtins \ - rustdoc-alloc rustdoc-kernel + rustdoc-kernel $(Q)cp $(srctree)/Documentation/images/logo.svg $(rustdoc_output)/static.files/ $(Q)cp $(srctree)/Documentation/images/COPYING-logo $(rustdoc_output)/static.files/ $(Q)find $(rustdoc_output) -name '*.html' -type f -print0 | xargs -0 sed -Ei \
@@ -105,20 +100,11 @@ rustdoc-core: $(RUST_LIB_SRC)/core/src/lib.rs FORCE rustdoc-compiler_builtins: $(src)/compiler_builtins.rs rustdoc-core FORCE +$(call if_changed,rustdoc) -# We need to allow `rustdoc::broken_intra_doc_links` because some -# `no_global_oom_handling` functions refer to non-`no_global_oom_handling` -# functions. Ideally `rustdoc` would have a way to distinguish broken links -# due to things that are "configured out" vs. entirely non-existing ones. -rustdoc-alloc: private rustc_target_flags = $(alloc-cfgs) \ - -Arustdoc::broken_intra_doc_links -rustdoc-alloc: $(RUST_LIB_SRC)/alloc/src/lib.rs rustdoc-core rustdoc-compiler_builtins FORCE - +$(call if_changed,rustdoc) - -rustdoc-kernel: private rustc_target_flags = --extern alloc \ +rustdoc-kernel: private rustc_target_flags = \ --extern build_error --extern macros=$(objtree)/$(obj)/libmacros.so \ --extern bindings --extern uapi rustdoc-kernel: $(src)/kernel/lib.rs rustdoc-core rustdoc-macros \ - rustdoc-compiler_builtins rustdoc-alloc $(obj)/libmacros.so \ + rustdoc-compiler_builtins $(obj)/libmacros.so \ $(obj)/bindings.o FORCE +$(call if_changed,rustdoc)
@@ -162,7 +148,7 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $< mkdir -p $(objtree)/$(obj)/test/doctests/kernel; \ OBJTREE=$(abspath $(objtree)) \ $(RUSTDOC) --test $(rust_flags) \ - -L$(objtree)/$(obj) --extern alloc --extern kernel \ + -L$(objtree)/$(obj) --extern kernel \ --extern build_error --extern macros \ --extern bindings --extern uapi \ --no-run --crate-name kernel -Zunstable-options \
@@ -198,7 +184,7 @@ rusttest-macros: $(src)/macros/lib.rs FORCE +$(call if_changed,rustc_test) +$(call if_changed,rustdoc_test) -rusttest-kernel: private rustc_target_flags = --extern alloc \ +rusttest-kernel: private rustc_target_flags = \ --extern build_error --extern macros --extern bindings --extern uapi rusttest-kernel: $(src)/kernel/lib.rs \ rusttestlib-build_error rusttestlib-macros rusttestlib-bindings \
@@ -311,9 +297,6 @@ quiet_cmd_exports = EXPORTS $@ $(obj)/exports_core_generated.h: $(obj)/core.o FORCE $(call if_changed,exports) -$(obj)/exports_alloc_generated.h: $(obj)/alloc.o FORCE - $(call if_changed,exports) - # Even though Rust kernel modules should never use the bindings directly, # symbols from the `bindings` crate and the C helpers need to be exported # because Rust generics and inlined functions may not get their code generated
@@ -360,7 +343,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L rust-analyzer: $(Q)$(srctree)/scripts/generate_rust_analyzer.py \ - --cfgs='core=$(core-cfgs)' --cfgs='alloc=$(alloc-cfgs)' \ + --cfgs='core=$(core-cfgs)' \ $(realpath $(srctree)) $(realpath $(objtree)) \ $(rustc_sysroot) $(RUST_LIB_SRC) $(KBUILD_EXTMOD) > \ $(if $(KBUILD_EXTMOD),$(extmod_prefix),$(objtree))/rust-project.json
@@ -398,12 +381,6 @@ $(obj)/compiler_builtins.o: private rustc_objcopy = -w -W '__*' $(obj)/compiler_builtins.o: $(src)/compiler_builtins.rs $(obj)/core.o FORCE +$(call if_changed_rule,rustc_library) -$(obj)/alloc.o: private skip_clippy = 1 -$(obj)/alloc.o: private skip_flags = -Wunreachable_pub -$(obj)/alloc.o: private rustc_target_flags = $(alloc-cfgs) -$(obj)/alloc.o: $(RUST_LIB_SRC)/alloc/src/lib.rs $(obj)/compiler_builtins.o FORCE - +$(call if_changed_rule,rustc_library) - $(obj)/build_error.o: $(src)/build_error.rs $(obj)/compiler_builtins.o FORCE +$(call if_changed_rule,rustc_library)
@@ -418,9 +395,9 @@ $(obj)/uapi.o: $(src)/uapi/lib.rs \ $(obj)/uapi/uapi_generated.rs FORCE +$(call if_changed_rule,rustc_library) -$(obj)/kernel.o: private rustc_target_flags = --extern alloc \ +$(obj)/kernel.o: private rustc_target_flags = \ --extern build_error --extern macros --extern bindings --extern uapi -$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/alloc.o $(obj)/build_error.o \ +$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/build_error.o \ $(obj)/libmacros.so $(obj)/bindings.o $(obj)/uapi.o FORCE +$(call if_changed_rule,rustc_library)
rust/exports.c +0 -1
@@ -16,7 +16,6 @@#define EXPORT_SYMBOL_RUST_GPL (sym) extern int sym; EXPORT_SYMBOL_GPL(sym)#include "exports_core_generated.h" -#include "exports_alloc_generated.h" #include "exports_helpers_generated.h" #include "exports_bindings_generated.h" #include "exports_kernel_generated.h"
rust/kernel/alloc/allocator.rs +2 -61
84 lines not on this page · raw message
scripts/Makefile.build +1 -6
@@ -262,18 +262,13 @@ $(obj)/%.lst: $(obj)/%.c FORCE # Compile Rust sources (.rs) # --------------------------------------------------------------------------- - -rust_allowed_features := new_uninit - # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree # modules case. rust_common_cmd = \ RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \ - -Zallow-features=$(rust_allowed_features) \ -Zcrate-attr=no_std \ - -Zcrate-attr='feature($(rust_allowed_features))' \ - -Zunstable-options --extern force:alloc --extern kernel \ + -Zunstable-options --extern kernel \ --crate-type rlib -L $(objtree)/rust/ \ --crate-name $(basename $(notdir $@)) \ --sysroot=/dev/null \
scripts/generate_rust_analyzer.py +2 -9
@@ -64,13 +64,6 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): [], ) - append_crate( - "alloc", - sysroot_src / "alloc" / "src" / "lib.rs", - ["core", "compiler_builtins"], - cfg=crates_cfgs.get("alloc", []), - ) - append_crate( "macros", srctree / "rust" / "macros" / "lib.rs",
@@ -96,7 +89,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( "kernel", srctree / "rust" / "kernel" / "lib.rs", - ["core", "alloc", "macros", "build_error", "bindings"], + ["core", "macros", "build_error", "bindings"], cfg=cfg, ) crates[-1]["source"] = {
@@ -133,7 +126,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( name, path, - ["core", "alloc", "kernel"], + ["core", "kernel"], cfg=cfg, )
Signature · 1 line
2.46.0
Gary Guo
maintainerRe: [PATCH v7 25/26] kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
Quoted 21 lines · On Thu, 12 Sep 2024 00:53:01 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we have our own
Allocator,BoxandVectypes we can remove Rust'salloccrate and thenew_uninitunstable feature.Also remove
Kmalloc'sGlobalAllocimplementation -- we can't remove this in a separate patch, since thealloccrate requires a#[global_allocator]to set, that implementsGlobalAlloc.Signed-off-by Danilo Krummrich <[email protected]>--- rust/Makefile | 43 +++++---------------- rust/exports.c | 1 - rust/kernel/alloc/allocator.rs | 63 +------------------------------ scripts/Makefile.build | 7 +--- scripts/generate_rust_analyzer.py | 11 +----- 5 files changed, 15 insertions(+), 110 deletions(-)scripts/Makefile.build +0
-0
scripts/Makefile.build $(obj)/%.lst: $(obj)/%.c FORCE · line 267
Quoted 6 lines
scripts/Makefile.build +0
-3@@ -262,18 +262,13 @@ $(obj)/%.lst: $(obj)/%.c FORCE # Compile Rust sources (.rs) # --------------------------------------------------------------------------- - -rust_allowed_features := new_uninit -
Would it make sense to throw in a soon-to-be-stable (or already-stable-but-beyond-min-rust-version) feature here to not have to remove the build system support for unstable feature directly?
For example, I know that const_refs_to_static might be added here soon-ish.
scripts/Makefile.build $(obj)/%.lst: $(obj)/%.c FORCE · line 273
Quoted 6 lines
scripts/Makefile.build +0
-1# `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree # modules case. rust_common_cmd = \ RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \ - -Zallow-features=$(rust_allowed_features) \
I think this actually allow all features instead of allowing none.
Quoted 43 lines
scripts/Makefile.build +1
-2-Zcrate-attr=no_std \ - -Zcrate-attr='feature($(rust_allowed_features))' \ - -Zunstable-options --extern force:alloc --extern kernel \ + -Zunstable-options --extern kernel \ --crate-type rlib -L $(objtree)/rust/ \ --crate-name $(basename $(notdir $@)) \ --sysroot=/dev/null \scripts/generate_rust_analyzer.py +2
-9@@ -64,13 +64,6 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): [], ) - append_crate( - "alloc", - sysroot_src / "alloc" / "src" / "lib.rs", - ["core", "compiler_builtins"], - cfg=crates_cfgs.get("alloc", []), - ) - append_crate( "macros", srctree / "rust" / "macros" / "lib.rs",@@ -96,7 +89,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( "kernel", srctree / "rust" / "kernel" / "lib.rs", - ["core", "alloc", "macros", "build_error", "bindings"], + ["core", "macros", "build_error", "bindings"], cfg=cfg, ) crates[-1]["source"] = {@@ -133,7 +126,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( name, path, - ["core", "alloc", "kernel"], + ["core", "kernel"], cfg=cfg, )
Danilo Krummrich
maintainerRe: [PATCH v7 25/26] kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
Quoted 25 lines · Gary Guo
Quoted 22 lines · On Thu, 12 Sep 2024 00:53:01 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we have our own
Allocator,BoxandVectypes we can remove Rust'salloccrate and thenew_uninitunstable feature.Also remove
Kmalloc'sGlobalAllocimplementation -- we can't remove this in a separate patch, since thealloccrate requires a#[global_allocator]to set, that implementsGlobalAlloc.Signed-off-by Danilo Krummrich <[email protected]>--- rust/Makefile | 43 +++++---------------- rust/exports.c | 1 - rust/kernel/alloc/allocator.rs | 63 +------------------------------ scripts/Makefile.build | 7 +--- scripts/generate_rust_analyzer.py | 11 +----- 5 files changed, 15 insertions(+), 110 deletions(-)scripts/Makefile.build +0
-0@@ -262,18 +262,13 @@ $(obj)/%.lst: $(obj)/%.c FORCE
Quoted 9 lines
scripts/Makefile.build $(obj)/%.lst: $(obj)/%.c FORCE · line 267
Quoted 5 lines
scripts/Makefile.build +0
-3# Compile Rust sources (.rs) # --------------------------------------------------------------------------- - -rust_allowed_features := new_uninit -Would it make sense to throw in a soon-to-be-stable (or already-stable-but-beyond-min-rust-version) feature here to not have to remove the build system support for unstable feature directly?
It's only about those two lines, right? I can only remove the new_uninit and leave rust_allowed_features := and leave...
Quoted 10 lines
For example, I know that
const_refs_to_staticmight be added here soon-ish.scripts/Makefile.build $(obj)/%.lst: $(obj)/%.c FORCE · line 273
Quoted 6 lines
scripts/Makefile.build +0
-1# `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree # modules case. rust_common_cmd = \ RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \ - -Zallow-features=$(rust_allowed_features) \
...this line, I guess?
Quoted 47 lines
I think this actually allow all features instead of allowing none.
Quoted 43 lines
scripts/Makefile.build +1
-2-Zcrate-attr=no_std \ - -Zcrate-attr='feature($(rust_allowed_features))' \ - -Zunstable-options --extern force:alloc --extern kernel \ + -Zunstable-options --extern kernel \ --crate-type rlib -L $(objtree)/rust/ \ --crate-name $(basename $(notdir $@)) \ --sysroot=/dev/null \scripts/generate_rust_analyzer.py +2
-9@@ -64,13 +64,6 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): [], ) - append_crate( - "alloc", - sysroot_src / "alloc" / "src" / "lib.rs", - ["core", "compiler_builtins"], - cfg=crates_cfgs.get("alloc", []), - ) - append_crate( "macros", srctree / "rust" / "macros" / "lib.rs",@@ -96,7 +89,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( "kernel", srctree / "rust" / "kernel" / "lib.rs", - ["core", "alloc", "macros", "build_error", "bindings"], + ["core", "macros", "build_error", "bindings"], cfg=cfg, ) crates[-1]["source"] = {@@ -133,7 +126,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( name, path, - ["core", "alloc", "kernel"], + ["core", "kernel"], cfg=cfg, )
Danilo Krummrich
maintainerRe: [PATCH v7 25/26] kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
Quoted 43 lines · Danilo Krummrich
Quoted 25 lines · Gary Guo
Quoted 22 lines · On Thu, 12 Sep 2024 00:53:01 +0200 Danilo Krummrich <[email protected]> wrote:
Now that we have our own
Allocator,BoxandVectypes we can remove Rust'salloccrate and thenew_uninitunstable feature.Also remove
Kmalloc'sGlobalAllocimplementation -- we can't remove this in a separate patch, since thealloccrate requires a#[global_allocator]to set, that implementsGlobalAlloc.Signed-off-by Danilo Krummrich <[email protected]>--- rust/Makefile | 43 +++++---------------- rust/exports.c | 1 - rust/kernel/alloc/allocator.rs | 63 +------------------------------ scripts/Makefile.build | 7 +--- scripts/generate_rust_analyzer.py | 11 +----- 5 files changed, 15 insertions(+), 110 deletions(-)scripts/Makefile.build +0
-0@@ -262,18 +262,13 @@ $(obj)/%.lst: $(obj)/%.c FORCEQuoted 9 lines
scripts/Makefile.build $(obj)/%.lst: $(obj)/%.c FORCE · line 267
Quoted 5 lines
scripts/Makefile.build +0
-3# Compile Rust sources (.rs) # --------------------------------------------------------------------------- - -rust_allowed_features := new_uninit -Would it make sense to throw in a soon-to-be-stable (or already-stable-but-beyond-min-rust-version) feature here to not have to remove the build system support for unstable feature directly?
It's only about those two lines, right? I can only remove the
new_uninitand leaverust_allowed_features :=and leave...Quoted 4 lines
For example, I know that
const_refs_to_staticmight be added here soon-ish.
Quoted 8 lines
Quoted 6 lines
scripts/Makefile.build $(obj)/%.lst: $(obj)/%.c FORCE · line 273
Quoted 6 lines
scripts/Makefile.build +0
-1# `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree # modules case. rust_common_cmd = \ RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \ - -Zallow-features=$(rust_allowed_features) \...this line, I guess?
Seems like I can't, it blows up with an empty feature list. I'll keep it as it is for now. Shouldn't be a big deal to add it again, once needed.
Quoted 48 lines
Quoted 48 lines
I think this actually allow all features instead of allowing none.
Quoted 43 lines
scripts/Makefile.build +1
-2-Zcrate-attr=no_std \ - -Zcrate-attr='feature($(rust_allowed_features))' \ - -Zunstable-options --extern force:alloc --extern kernel \ + -Zunstable-options --extern kernel \ --crate-type rlib -L $(objtree)/rust/ \ --crate-name $(basename $(notdir $@)) \ --sysroot=/dev/null \scripts/generate_rust_analyzer.py +2
-9@@ -64,13 +64,6 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): [], ) - append_crate( - "alloc", - sysroot_src / "alloc" / "src" / "lib.rs", - ["core", "compiler_builtins"], - cfg=crates_cfgs.get("alloc", []), - ) - append_crate( "macros", srctree / "rust" / "macros" / "lib.rs",@@ -96,7 +89,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( "kernel", srctree / "rust" / "kernel" / "lib.rs", - ["core", "alloc", "macros", "build_error", "bindings"], + ["core", "macros", "build_error", "bindings"], cfg=cfg, ) crates[-1]["source"] = {@@ -133,7 +126,7 @@ def generate_crates(srctree, objtree, sysroot_src, external_src, cfgs): append_crate( name, path, - ["core", "alloc", "kernel"], + ["core", "kernel"], cfg=cfg, )
Re: [PATCH v7 25/26] kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
In reply to Gary GuoQuoted 4 lines · Gary Guo
Would it make sense to throw in a soon-to-be-stable (or already-stable-but-beyond-min-rust-version) feature here to not have to remove the build system support for unstable feature directly?
Yeah, agreed -- we are going to have lint_reasons and arbitrary_self_types before this series, so it should be fine.
Cheers,
Miguel
Danilo Krummrich
maintainerRe: [PATCH v7 25/26] kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
Quoted 8 lines · Miguel Ojeda
Quoted 4 lines · Gary Guo
Would it make sense to throw in a soon-to-be-stable (or already-stable-but-beyond-min-rust-version) feature here to not have to remove the build system support for unstable feature directly?
Yeah, agreed -- we are going to have
lint_reasonsandarbitrary_self_typesbefore this series, so it should be fine.
So, I guess you'll just fix it up when applying the series?
Quoted 4 lines
Cheers,
Miguel
Re: [PATCH v7 25/26] kbuild: rust: remove the `alloc` crate and `GlobalAlloc`
Quoted 2 lines · Danilo Krummrich
So, I guess you'll just fix it up when applying the series?
Sure, if there is no need for a new version, then no worries.
Cheers,
Miguel
Danilo Krummrich
maintainerPATCH v7 26/26 MAINTAINERS: add entry for the Rust `alloc` module
In reply to Danilo KrummrichAdd maintainers entry for the Rust alloc module.
Currently, this includes the Allocator API itself, Allocator implementations, such as Kmalloc or Vmalloc, as well as the kernel's implementation of the primary memory allocation data structures, Box and Vec.
--- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+)
MAINTAINERS +7 -0
@@ -19929,6 +19929,13 @@ F: scripts/*rust* F: tools/testing/selftests/rust/ K: \b(?i:rust)\b +RUST [ALLOC] +M: Danilo Krummrich <[email protected]> +L: [email protected] +S: Maintained +F: rust/kernel/alloc.rs +F: rust/kernel/alloc/ + RXRPC SOCKETS (AF_RXRPC) M: David Howells <[email protected]> M: Marc Dionne <[email protected]>
Signature · 1 line
2.46.0