From: Daniel Gomez <da.gomez@kernel.org>
To: "Matthew Wilcox (Oracle)" <willy@infradead.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Daniel Gomez" <da.gomez@kernel.org>,
"Andrew Morton" <akpm@linux-foundation.org>
Cc: Julia Lawall <Julia.Lawall@inria.fr>,
Corinn Tiffany <corinn.tiffany@inria.fr>,
"Liam R. Howlett" <liam@infradead.org>,
Philipp Stanner <pstanner@redhat.com>,
linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Samsung GOST <gost.dev@samsung.com>,
Daniel Gomez <da.gomez@samsung.com>
Subject: [PATCH 1/3] rust: rxarray: add rust xarray support
Date: Wed, 23 Sep 2026 23:00:27 +0200 [thread overview]
Message-ID: <20260923-rxarray-next-v1-1-92eedf185649@samsung.com> (raw)
In-Reply-To: <20260923-rxarray-next-v1-0-92eedf185649@samsung.com>
From: Daniel Gomez <da.gomez@samsung.com>
Add Rust XArray support.
This is the Rust implementation of the XArray lib/xarray.c. As of now,
it supports basic functionality: store, load, load_mut and erase.
Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
Signed-off-by: Daniel Gomez <da.gomez@samsung.com>
---
MAINTAINERS | 11 +
rust/kernel/lib.rs | 1 +
rust/kernel/rxarray.rs | 1407 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1419 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c2414447892c2..c40a254c35d3d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29681,6 +29681,17 @@ C: https://rust-for-linux.zulipchat.com
T: git https://github.com/Rust-for-Linux/linux.git xarray-next
F: rust/kernel/xarray.rs
+XARRAY [RUST]
+M: Daniel Gomez <da.gomez@kernel.org>
+R: Andreas Hindborg <a.hindborg@kernel.org>
+L: rust-for-linux@vger.kernel.org
+S: Supported
+W: https://rust-for-linux.com
+B: https://github.com/Rust-for-Linux/linux/issues
+C: https://rust-for-linux.zulipchat.com
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/da.gomez/linux.git rxarray-next
+F: rust/kernel/rxarray.rs
+
XBOX DVD IR REMOTE
M: Benjamin Valentin <benpicco@googlemail.com>
S: Maintained
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 4d5c96ddc49c7..1e3c8d3051e53 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -122,6 +122,7 @@
pub mod rbtree;
pub mod regulator;
pub mod revocable;
+pub mod rxarray;
pub mod safety;
pub mod scatterlist;
pub mod security;
diff --git a/rust/kernel/rxarray.rs b/rust/kernel/rxarray.rs
new file mode 100644
index 0000000000000..9b939b0147252
--- /dev/null
+++ b/rust/kernel/rxarray.rs
@@ -0,0 +1,1407 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust XArray implementation.
+//!
+//! This module implements an extensible array (aka XArray) via the [`XArray`] type. See [`XArray4`]
+//! and [`XArray6`] for common configurations.
+//!
+//! The array can hold two types of entries at the same time where each index independently stores
+//! either an owned pointer (`T: ForeignOwnable`) or a bounded integer up to `usize::MAX >> 1`
+//! backed by [`Bounded`]. Users insert entries via the [`Entry`] type. References to entries in
+//! the tree are handled by the [`BorrowedEntry`] and [`BorrowedEntryMut`] types.
+//!
+//! # Differences from C XArray
+//!
+//! The C XArray uses RCU for lock-free reads and an internal spinlock for writes. This
+//! implementation does not provide internal locking. A mutable reference is required for writes, a
+//! shared reference for reads. Locking can be applied externally. RCU support is planned.
+//!
+//! This implementation allocates during store. `xa_reserve()` and `xas_nomem()` are not yet
+//! supported.
+//!
+//! C `xa_destroy()` frees only internal nodes; callers must free their own stored values. Dropping
+//! [`XArray`] also frees all stored entries.
+//!
+//! C `xa_mk_value()` issues `WARN_ON` if the value exceeds `LONG_MAX`; the left shift overflows,
+//! producing a wrong entry.
+//! Rust [`Entry::int()`] rejects out-of-range values at compile time via `Bounded`, and
+//! [`Entry::try_int()`] returns [`None`] at runtime.
+//!
+//! The C XArray grows in depth on demand to be efficient for small indices, creating a short tree
+//! whenever possible and adding levels as larger indices appear. This implementation always walks
+//! the full levels, whatever the largest stored index is. Variable height is planned.
+//!
+//! C XArray features like search marks, multi-index entries, advanced API, etc. are not yet
+//! implemented.
+//!
+//! This implementation aims for full feature parity with the C XArray; any gap not covered above is
+//! a defect in this list.
+//!
+//! # Data structure
+//!
+//! The XArray is a radix tree that maps `usize` indices to [`Entry`] values. Each node holds an
+//! array of `SIZE` slots (where `SIZE = 1 << SHIFT`), and the tree depth is
+//! `ceil(usize::BITS / SHIFT)`. Indices are decomposed so that each chunk indexes a slot in a
+//! node at the corresponding level.
+//!
+//! ```text
+//! XArray
+//! │
+//! ▼
+//! ┌────────────────┐
+//! │ Root Node │ Level = levels-1
+//! │ slots[0..SIZE] │
+//! └────────────────┘
+//! / | \
+//! ┌───────────┘ │ └───────────┐
+//! ▼ ▼ ▼
+//! ┌────────────────┐ ┌────────┐ ┌────────────────┐
+//! │ Node │ │ Empty │ │ Node │
+//! │ slots[0..SIZE] │ └────────┘ │ slots[0..SIZE] │
+//! └────────────────┘ └────────────────┘
+//! / \ |
+//! ▼ ▼ ▼
+//! ... ... ┌────────────────┐
+//! │ Leaf Node │ Level = 0
+//! │ slots[0..SIZE] │
+//! └────────────────┘
+//! / | \
+//! ▼ ▼ ▼
+//! Entry Empty Entry
+//! (Int) (Ptr)
+//! ```
+//!
+//! Index decomposition for [`XArray6`] (SHIFT=6, 64 slots per node, 11 levels on 64-bit):
+//!
+//! ```text
+//! ┌─────────────────────────────────────────────────────────────────┐
+//! │ index │
+//! ├─────────┬─────────┬───────────────┬─────────┬─────────┬─────────┤
+//! │ level10 │ level 9 │ ... │ level 2 │ level 1 │ level 0 │
+//! │ [63:60] │ [59:54] │ │ [17:12] │ [11:6] │ [5:0] │
+//! └─────────┴─────────┴───────────────┴─────────┴─────────┴─────────┘
+//! ```
+//!
+//! At each level, the slot offset is: `(index >> (level * SHIFT)) & (SIZE - 1)`.
+//!
+//! # C API
+//!
+//! This implementation does not have a C API yet. When one is added, its FFI layer must validate
+//! at runtime what the Rust API enforces at compile or construction time:
+//!
+//! - **Int values**: use [`Entry::try_int()`]; reject [`None`] as `-EINVAL`. C has no [`Bounded`],
+//! so the FFI wrapper is the enforcement point.
+//! - **Pointers**: validate 4-byte alignment and non-null at runtime.
+//! `const_assert!(T::FOREIGN_ALIGN >= 4)` only covers Rust callers.
+//! - **NULL pointers**: dispatch as erase (`xa_store(NULL) == xa_erase`) or reserve
+//! (`XA_FLAGS_ALLOC`) before reaching [`XArray::store()`].
+//! - **Error entries**: reject `xa_is_err()` values; Rust would misclassify them as node pointers
+//! (see `Slot` invariants).
+
+use crate::{
+ alloc::Flags,
+ fmt,
+ num::Bounded,
+ prelude::*,
+ types::ForeignOwnable, //
+};
+use core::{
+ marker::PhantomData,
+ mem, //
+};
+
+/// Type alias for [`XArray`] with a shift of 4 and 16 slots per node.
+pub type XArray4<T> = XArray<T, 4, 16>;
+
+/// Type alias for [`XArray`] with a shift of 6 and 64 slots per node.
+pub type XArray6<T> = XArray<T, 6, 64>;
+
+/// Type alias for [`Entry::Int`] values. Integers that fit in `usize::BITS - 1` bits
+/// (`0..=usize::MAX >> 1`).
+pub type Value = Bounded<usize, { usize::BITS - 1 }>;
+
+/// An entry is either an [`Entry::Int`] integer (`0..=usize::MAX >> 1`) or an owned
+/// [`Entry::Pointer`].
+///
+/// Empty slots are represented by [`None`], not a separate variant.
+///
+/// Integer values are validated by [`Value`]. Pointer alignment (`T::FOREIGN_ALIGN >= 4`) is a
+/// requirement of the slot encoding, enforced at compile time when an [`XArray`] over `T` is
+/// constructed (see the [`XArray`] invariants); [`Entry`] itself carries no invariants.
+pub enum Entry<T: ForeignOwnable> {
+ /// Integer value (`0..=usize::MAX >> 1`).
+ Int(Value),
+ /// Pointer payload. Owned (`T`).
+ Pointer(T),
+}
+
+impl<T: ForeignOwnable> Entry<T> {
+ /// Creates an [`Entry::Int`] validated at compile time.
+ ///
+ /// Fails to compile if `V > usize::MAX >> 1`.
+ pub const fn int<const V: usize>() -> Self {
+ Entry::Int(Value::new::<V>())
+ }
+
+ /// Creates an [`Entry::Int`] validated at runtime.
+ ///
+ /// Returns [`None`] if `v > usize::MAX >> 1`.
+ pub fn try_int(v: usize) -> Option<Self> {
+ Value::try_new(v).map(Entry::Int)
+ }
+}
+
+/// A borrowed entry returned by [`XArray::load()`], containing either an integer or a borrowed
+/// pointer.
+pub enum BorrowedEntry<'a, T: ForeignOwnable + 'a> {
+ /// Integer value (`0..=usize::MAX >> 1`).
+ Int(Value),
+ /// Pointer payload `T::Borrowed<'_>`.
+ Pointer(T::Borrowed<'a>),
+}
+
+/// A mutably borrowed entry returned by [`XArray::load_mut()`], containing either an integer or a
+/// borrowed pointer.
+pub enum BorrowedEntryMut<'a, T: ForeignOwnable + 'a> {
+ /// Integer value (`0..=usize::MAX >> 1`).
+ Int(Value),
+ /// Pointer payload `T::BorrowedMut<'_>`.
+ Pointer(T::BorrowedMut<'a>),
+}
+
+/// The error returned by [`XArray::store()`].
+///
+/// Contains the underlying error and the entry that was not stored.
+pub struct StoreError<T: ForeignOwnable> {
+ /// The error that occurred.
+ pub error: Error,
+ /// The entry that was not stored.
+ pub entry: Entry<T>,
+}
+
+impl<T: ForeignOwnable> fmt::Debug for StoreError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("StoreError")
+ .field("error", &self.error)
+ .finish()
+ }
+}
+
+impl<T: ForeignOwnable> From<StoreError<T>> for Error {
+ #[inline]
+ fn from(value: StoreError<T>) -> Self {
+ value.error
+ }
+}
+
+/// An extensible array backed by a radix tree, mapping `usize` indices to [`Entry`] values.
+///
+/// `T` must be a [`ForeignOwnable`] whose `FOREIGN_ALIGN` is at least 4. The type can be named with
+/// any other `T`, but constructing a value of it fails to compile.
+///
+/// # Ownership
+///
+/// When an [`XArray`] is dropped, all stored [`Entry::Pointer`] entries are freed via
+/// [`ForeignOwnable::from_foreign()`]. This differs from the C `xa_destroy()`, which only frees
+/// internal nodes and requires callers to free stored pointers themselves. [`Entry::Int`] entries
+/// are encoded integers with no backing allocation and need no cleanup.
+///
+/// # Examples
+///
+/// ```
+/// use kernel::alloc::{flags, KBox};
+/// use kernel::rxarray::{BorrowedEntry, Entry, XArray6};
+///
+/// let mut xa = XArray6::<KBox<u64>>::new();
+/// assert!(xa.is_empty());
+///
+/// // Store a pointer entry at index 1.
+/// let boxed = KBox::new(0xbeef_u64, flags::GFP_KERNEL)?;
+/// xa.store(1, Entry::Pointer(boxed), flags::GFP_KERNEL)?;
+/// match xa.load(1) {
+/// Some(BorrowedEntry::Pointer(val)) => assert_eq!(*val, 0xbeef_u64),
+/// _ => panic!("expected Pointer"),
+/// }
+///
+/// // Store a value entry at index 0.
+/// let old = xa.store(0, Entry::int::<0xdead>(), flags::GFP_KERNEL)?;
+/// assert!(old.is_none());
+/// assert!(!xa.is_empty());
+///
+/// match xa.load(0) {
+/// Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0xdead),
+/// _ => panic!("expected Int"),
+/// }
+///
+/// let old = xa.store(0, Entry::int::<0xcafe>(), flags::GFP_KERNEL)?;
+/// match old {
+/// Some(Entry::Int(v)) => assert_eq!(v, 0xdead),
+/// _ => panic!("expected old Int"),
+/// }
+///
+/// match xa.erase(0) {
+/// Some(Entry::Int(v)) => assert_eq!(v, 0xcafe),
+/// _ => panic!("expected erased Int"),
+/// }
+/// assert!(xa.erase(1).is_some());
+/// assert!(xa.is_empty());
+///
+/// # Ok::<(), Error>(())
+/// ```
+///
+/// # Invariants
+///
+/// - `T::FOREIGN_ALIGN >= 4`, ensuring pointer entries do not collide with the integer or internal
+/// entry encoding.
+/// - `SHIFT > 0` and `SIZE == 1 << SHIFT`.
+/// - Every slot in every node satisfies the `Slot` invariants.
+/// - Interior levels (level > 0) contain only empty or node slots. Leaf level (level 0) contains
+/// only empty, `Int`, or `Pointer` slots.
+/// - The subtree below every node slot contains at least one of the `Entry` types (`Int` or
+/// `Pointer`).
+pub struct XArray<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> {
+ root: Node<T, SHIFT, SIZE>,
+}
+
+impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> Default for XArray<T, SHIFT, SIZE> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> XArray<T, SHIFT, SIZE> {
+ const fn validate() {
+ const_assert!(SHIFT > 0, "SHIFT must be > 0");
+ const_assert!(SIZE == (1 << SHIFT), "SIZE != 1 << SHIFT");
+ const_assert!(
+ T::FOREIGN_ALIGN >= 4,
+ "ForeignOwnable pointers must be 4-byte aligned"
+ );
+ }
+
+ /// Creates a new empty [`XArray`].
+ pub const fn new() -> Self {
+ Self::validate();
+ // INVARIANT:
+ // - `Self::validate` checks `T::FOREIGN_ALIGN >= 4`, `SHIFT > 0` and `SIZE == 1 << SHIFT`
+ // at compile time.
+ // - `Node::new` fills every slot with `Slot::EMPTY`, which satisfies the empty-slot case
+ // of the `Slot` invariants.
+ // - Every slot is empty, and an empty slot is valid at any level.
+ XArray { root: Node::new() }
+ }
+
+ /// Returns the number of levels in the tree.
+ const fn levels() -> usize {
+ (usize::BITS as usize).div_ceil(SHIFT)
+ }
+
+ /// Returns `true` if the tree contains no entries.
+ ///
+ /// Scanning the root node suffices: by the type invariants, every node slot in it leads to at
+ /// least one entry.
+ pub fn is_empty(&self) -> bool {
+ self.root.is_empty()
+ }
+
+ /// Erases the entry at `index`.
+ ///
+ /// Returns the previous entry, or [`None`] if the slot was empty. Empty intermediate nodes are
+ /// freed during traversal.
+ pub fn erase(&mut self, index: usize) -> Option<Entry<T>> {
+ self.root.erase(index, Self::levels() - 1)
+ }
+
+ /// Stores an entry at `index`.
+ ///
+ /// Returns the previous entry, or [`None`] if the slot was empty.
+ ///
+ /// # Errors
+ ///
+ /// Returns a [`StoreError`] carrying the entry back to the caller, with error [`ENOMEM`] if
+ /// a new intermediate node cannot be allocated. A failed stored leaves the tree unchanged. For
+ /// C callers, [`EINVAL`] in case of tree corruption detection.
+ pub fn store(
+ &mut self,
+ index: usize,
+ entry: Entry<T>,
+ flags: Flags,
+ ) -> Result<Option<Entry<T>>, StoreError<T>> {
+ self.root.store(index, entry, Self::levels() - 1, flags)
+ }
+
+ /// Loads the entry at `index`.
+ ///
+ /// Returns [`None`] if the slot is empty. The returned [`BorrowedEntry`] is a value that
+ /// borrows from the array where pointer entries carry `T::Borrowed<'_>` (e.g., `&T` for
+ /// `KBox<T>`), and integer entries carry a copy of the [`Value`].
+ pub fn load(&self, index: usize) -> Option<BorrowedEntry<'_, T>> {
+ self.root.load(index, Self::levels() - 1)
+ }
+
+ /// Loads the entry at `index` for mutation.
+ ///
+ /// Returns [`None`] if the slot is empty. The returned [`BorrowedEntryMut`] is a value that
+ /// borrows from the array where pointer entries carry `T::BorrowedMut<'_>` (e.g., `&mut T` for
+ /// `KBox<T>`), and integer entries carry a copy of the [`Value`].
+ pub fn load_mut(&mut self, index: usize) -> Option<BorrowedEntryMut<'_, T>> {
+ self.root.load_mut(index, Self::levels() - 1)
+ }
+}
+
+/// The internal storage unit: a single `usize` encoding an [`Entry`] or an internal node pointer.
+///
+/// # Invariants
+///
+/// The encoded value `self.0` is one of:
+/// - `0`: the slot is empty.
+/// - An odd value `(v << 1) | 1`: an integer value `v` where `v <= usize::MAX >> 1`.
+/// - An even, non-zero value with bits `1:0 == 0b00`: a valid, non-null pointer previously returned
+/// by [`ForeignOwnable::into_foreign()`].
+/// - An even, non-zero value with bits `1:0 == 0b10` and value > `NODE_THRESHOLD`: a valid pointer
+/// to a live `Node<T, SHIFT, SIZE>` allocation, created by [`Slot::mk_node()`] from
+/// [`KBox::into_raw()`] tagged with `| 2`. The slot owns the [`KBox<Node>`] allocation.
+//
+// All entries with bits `1:0 == 0b10` are internal to the XArray implementation. The encoded value
+// distinguishes sub-types:
+//
+// - Offset 0..=62, encoded 2..=250: sibling entries.
+// The encoded value contains the offset of the canonical slot within the same node (multi-index
+// entries, CONFIG_XARRAY_MULTI).
+//
+// - Logical 256, encoded 1026: retry (XA_RETRY_ENTRY).
+// Tombstone signaling concurrent tree modification; RCU lock-free readers must restart.
+//
+// - Logical 257, encoded 1030: zero (XA_ZERO_ENTRY).
+// Placeholder marking a slot as occupied but logically empty (xa_reserve, XA_FLAGS_TRACK_FREE).
+//
+// - Encoded value > `NODE_THRESHOLD` (4096): node pointers (heap addresses tagged with | 2). Always
+// above this threshold.
+//
+// Note: C error entries (xa_is_err()) also encode as internal entries with values far above this
+// threshold. They are never stored in tree slots, only returned by the XArray state machine API. C
+// callers bypass this assumption; the FFI layer must reject error entries before they reach Rust,
+// where kind() would misclassify them as Node, causing as_node() to dereference an invalid
+// pointer.
+//
+// This implementation only creates node pointers via `mk_node()`. The other sub-types are reserved
+// for future RCU and multi-index support. See include/linux/xarray.h.
+#[repr(transparent)]
+struct Slot<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>(usize, PhantomData<T>);
+
+impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> Default for Slot<T, SHIFT, SIZE> {
+ fn default() -> Self {
+ Self::EMPTY
+ }
+}
+
+#[derive(Copy, Clone)]
+enum SlotType {
+ Empty,
+ Pointer,
+ Internal,
+ Node, // Internal with value > NODE_THRESHOLD.
+ Int,
+}
+
+impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> Slot<T, SHIFT, SIZE> {
+ // INVARIANT: `0` is the encoding for an empty slot.
+ const EMPTY: Self = Slot(0, PhantomData);
+
+ /// Encodes an [`Entry<T>`] into a [`Slot`] for tree storage.
+ fn encode(entry: Entry<T>) -> Self {
+ const_assert!(
+ T::FOREIGN_ALIGN >= 4,
+ "ForeignOwnable pointers must be 4-byte aligned"
+ );
+
+ match entry {
+ Entry::Int(v) => {
+ // INVARIANT: `*v <= usize::MAX >> 1` is guaranteed by `Value` (`Bounded`), so the
+ // shift cannot overflow and the encoded value is odd.
+ Slot((*v << 1) | 1, PhantomData)
+ }
+ Entry::Pointer(p) => {
+ let bits = p.into_foreign() as usize;
+ debug_assert!(bits != 0, "ForeignOwnable returned null");
+ debug_assert!(bits & 3 == 0, "ForeignOwnable pointer not 4-byte aligned");
+ // INVARIANT: `into_foreign()` guarantees a non-null pointer aligned to
+ // `T::FOREIGN_ALIGN`, and `const_assert!(T::FOREIGN_ALIGN >= 4)` above ensures
+ // bits `1:0 == 0b00`.
+ Slot(bits, PhantomData)
+ }
+ }
+ }
+
+ /// Decodes a [`Slot`] into an owned [`Entry<T>`], consuming the slot.
+ ///
+ /// Returns [`None`] for empty slots.
+ fn decode(self) -> Option<Entry<T>> {
+ let slot_type = self.kind();
+ let bits = mem::ManuallyDrop::new(self).0;
+
+ match slot_type {
+ SlotType::Empty => None,
+ // Undo the `(v << 1) | 1` encoding from `Slot::encode`.
+ SlotType::Int => Some(Entry::Int(Value::from_expr(bits >> 1))),
+ SlotType::Node | SlotType::Internal => {
+ debug_assert!(false, "attempt to decode internal/node entry");
+ None
+ }
+ SlotType::Pointer => {
+ // SAFETY:
+ // - By the type invariant, a slot classified `Pointer` holds a pointer returned by
+ // a previous call to `T::into_foreign()`.
+ // - `decode` takes the slot by value, so the caller has already removed it from
+ // the tree, and `ManuallyDrop` suppresses `Slot::drop`. This is therefore the
+ // only `from_foreign` call for this pointer.
+ Some(Entry::Pointer(unsafe {
+ T::from_foreign(bits as *mut c_void)
+ }))
+ }
+ }
+ }
+
+ /// Borrows the entry in this slot without consuming it.
+ ///
+ /// Returns [`None`] for empty slots. Pointer entries are borrowed via
+ /// [`ForeignOwnable::borrow()`].
+ fn borrow(&self) -> Option<BorrowedEntry<'_, T>> {
+ let slot_type = self.kind();
+ let bits = self.0;
+
+ match slot_type {
+ SlotType::Empty => None,
+ // Undo the `(v << 1) | 1` encoding from `Slot::encode`.
+ SlotType::Int => Some(BorrowedEntry::Int(Value::from_expr(bits >> 1))),
+ SlotType::Node | SlotType::Internal => {
+ debug_assert!(false, "attempt to borrow internal/node entry");
+ None
+ }
+ SlotType::Pointer => {
+ // SAFETY:
+ // - By the type invariant, a slot classified `Pointer` holds a pointer returned by
+ // a previous call to `T::into_foreign()`.
+ // - Every path that reaches `from_foreign` for this slot needs ownership of it
+ // (`decode`) or a unique borrow (`Slot::drop`), and reaching either from the
+ // array requires `&mut self` on the `XArray`. Neither can coexist with this
+ // shared borrow, so any `from_foreign` on this pointer happens after the borrow
+ // ends.
+ Some(BorrowedEntry::Pointer(unsafe {
+ T::borrow(bits as *mut c_void)
+ }))
+ }
+ }
+ }
+
+ /// Mutably borrows the entry in this slot without consuming it.
+ ///
+ /// Returns [`None`] for empty slots. Pointer entries are borrowed via
+ /// [`ForeignOwnable::borrow_mut()`]; integer entries are returned by value.
+ fn borrow_mut(&mut self) -> Option<BorrowedEntryMut<'_, T>> {
+ let slot_type = self.kind();
+ let bits = self.0;
+
+ match slot_type {
+ SlotType::Empty => None,
+ // Undo the `(v << 1) | 1` encoding from `Slot::encode`.
+ SlotType::Int => Some(BorrowedEntryMut::Int(Value::from_expr(bits >> 1))),
+ SlotType::Node | SlotType::Internal => {
+ debug_assert!(false, "attempt to borrow internal/node entry");
+ None
+ }
+ SlotType::Pointer => {
+ // SAFETY:
+ // - By the type invariant, a slot classified `Pointer` holds a pointer returned by
+ // a previous call to `T::into_foreign()`.
+ // - The returned value borrows `self` mutably, so no other `borrow()` or
+ // `borrow_mut()` on this slot can overlap it, and every path that reaches
+ // `from_foreign` for this slot (`decode`, `Slot::drop`) needs the slot by value
+ // or by unique borrow, so any `from_foreign` on this pointer happens after the
+ // borrow ends.
+ Some(BorrowedEntryMut::Pointer(unsafe {
+ T::borrow_mut(bits as *mut c_void)
+ }))
+ }
+ }
+ }
+
+ // The boundary between internal entries and node pointers.
+ const NODE_THRESHOLD: usize = 4096;
+
+ #[inline]
+ fn kind(&self) -> SlotType {
+ let slot = self.0;
+ if slot == 0 {
+ return SlotType::Empty;
+ }
+ match slot & 3 {
+ 2 if slot > Self::NODE_THRESHOLD => SlotType::Node,
+ 2 => SlotType::Internal,
+ 1 | 3 => SlotType::Int,
+ _ => SlotType::Pointer,
+ }
+ }
+
+ #[inline]
+ fn is_empty(&self) -> bool {
+ matches!(self.kind(), SlotType::Empty)
+ }
+
+ #[inline]
+ fn is_node(&self) -> bool {
+ matches!(self.kind(), SlotType::Node)
+ }
+
+ /// Returns a shared reference to the child node pointed to by this entry, or [`None`] if this
+ /// is not a node slot.
+ fn as_node(&self) -> Option<&Node<T, SHIFT, SIZE>> {
+ if !self.is_node() {
+ return None;
+ }
+ let ptr = (self.0 & !3) as *const Node<T, SHIFT, SIZE>;
+ // SAFETY:
+ // - By the type invariant, a slot classified `Node` holds `KBox::into_raw()` of a valid,
+ // live `KBox<Node<T, SHIFT, SIZE>>` allocation owned by this slot. `& !3` reverses the
+ // `| 2` tag.
+ // - The returned reference borrows `self`, so the slot cannot be modified and the
+ // allocation cannot be freed while it is live.
+ Some(unsafe { &*ptr })
+ }
+
+ /// Returns a mutable reference to the child node pointed to by this entry, or [`None`] if this
+ /// is not a node slot.
+ fn as_node_mut(&mut self) -> Option<&mut Node<T, SHIFT, SIZE>> {
+ if !self.is_node() {
+ return None;
+ }
+ let ptr = (self.0 & !3) as *mut Node<T, SHIFT, SIZE>;
+ // SAFETY:
+ // - By the type invariant, a slot classified `Node` holds `KBox::into_raw()` of a valid,
+ // live `KBox<Node<T, SHIFT, SIZE>>` allocation owned by this slot. `& !3` reverses the
+ // `| 2` tag.
+ // - The returned reference borrows `self` mutably, so no other reference to the allocation
+ // can exist and the slot cannot be modified while it is live.
+ Some(unsafe { &mut *ptr })
+ }
+
+ /// Creates a node slot from a heap-allocated [`Node`].
+ fn mk_node(node: KBox<Node<T, SHIFT, SIZE>>) -> Self {
+ let ptr = KBox::into_raw(node) as usize;
+ debug_assert!(ptr & 3 == 0, "Node pointer not aligned");
+ // INVARIANT: `ptr` owns a live `KBox<Node>` allocation from `into_raw()`, so it is at least
+ // 4-byte aligned (checked above) and `| 2` gives bits `1:0 == 0b10`. Kernel heap addresses
+ // are far above `NODE_THRESHOLD`, so the result classifies as node.
+ Slot(ptr | 2, PhantomData)
+ }
+}
+
+impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> Drop for Slot<T, SHIFT, SIZE> {
+ fn drop(&mut self) {
+ match self.kind() {
+ SlotType::Pointer => {
+ // SAFETY:
+ // - By the type invariant, a slot classified `Pointer` holds a pointer returned by
+ // a previous call to `T::into_foreign()`.
+ // - Every other `from_foreign` path (`decode`) consumes the slot through
+ // `ManuallyDrop`, which suppresses this destructor, so this is the only
+ // `from_foreign` call for this pointer.
+ drop(unsafe { T::from_foreign(self.0 as *mut c_void) });
+ }
+ SlotType::Node => {
+ let ptr = (self.0 & !3) as *mut Node<T, SHIFT, SIZE>;
+ // SAFETY:
+ // - By the type invariant, a slot classified `Node` holds a pointer produced by
+ // `KBox::into_raw()` in `Slot::mk_node()`, tagged with `| 2`; `& !3` above
+ // reverses the tag.
+ // - The slot is being dropped and passes that ownership to the reconstructed box.
+ // `decode()`, the only other consumer of a slot by value, never accepts node
+ // slots, so this is the only `KBox::from_raw()` for this pointer.
+ //
+ // Dropping the box drops the child's slots in turn, freeing the subtree. The
+ // recursion is bounded by the tree depth.
+ drop(unsafe { KBox::from_raw(ptr) });
+ }
+ SlotType::Internal => {
+ debug_assert!(false, "internal entry must not reach Slot::drop");
+ }
+ _ => {}
+ }
+ }
+}
+
+/// A single node in the radix tree. Each node contains `SIZE` slots, where each slot may be empty,
+/// contain a user entry (integer or pointer), or point to a child node at the next tree level.
+///
+/// # Invariants
+///
+/// - Interior nodes (level > 0): each slot is either `Empty` or a `Node` pointer created by
+/// [`Slot::mk_node()`] from a valid [`KBox<Node<T, SHIFT, SIZE>>`] allocation.
+/// - Leaf nodes (level == 0): each slot is either `Empty`, `Int`, or `Pointer`. No `Node` or
+/// `Internal` slots may appear at level 0.
+struct Node<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> {
+ slots: [Slot<T, SHIFT, SIZE>; SIZE],
+}
+
+impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize> Node<T, SHIFT, SIZE> {
+ // Bitmask for extracting the slot offset. Equivalent to XA_CHUNK_MASK.
+ const MASK: usize = SIZE - 1;
+
+ const fn new() -> Self {
+ // INVARIANT: every slot is `Slot::EMPTY`, so the node satisfies the type invariants at any
+ // level.
+ Self {
+ slots: [const { Slot::EMPTY }; SIZE],
+ }
+ }
+
+ /// Extracts the slot index for `index` at the given tree `level`.
+ ///
+ /// Equivalent to `get_offset()` (see `lib/xarray.c`).
+ #[inline]
+ fn slot_index(index: usize, level: usize) -> usize {
+ (index >> (level * SHIFT)) & Self::MASK
+ }
+
+ /// Returns `true` if every slot in this node is empty.
+ fn is_empty(&self) -> bool {
+ self.slots.iter().all(|slot| slot.is_empty())
+ }
+
+ /// Erases the entry at `index`.
+ ///
+ /// Returns the previous entry, or [`None`] if the slot was empty. Frees empty intermediate
+ /// nodes on the way back up.
+ fn erase(&mut self, index: usize, level: usize) -> Option<Entry<T>> {
+ let slot_index = Self::slot_index(index, level);
+ let slot = &mut self.slots[slot_index];
+ if level == 0 {
+ debug_assert!(!slot.is_node(), "Found node pointer at leaf level");
+ // INVARIANT: `mem::take` leaves `Slot::EMPTY` behind, which is permitted at leaf level.
+ let old = mem::take(slot);
+ return old.decode();
+ }
+
+ if let Some(child) = slot.as_node_mut() {
+ let old_value = child.erase(index, level - 1);
+ if child.is_empty() {
+ // INVARIANT: `mem::take` leaves `Slot::EMPTY` behind, which is permitted at
+ // interior level. Dropping the taken slot frees the child node.
+ drop(mem::take(slot));
+ }
+ old_value
+ } else {
+ // Values at intermediate levels indicate tree corruption.
+ debug_assert!(slot.is_empty(), "Non-null non-node entry");
+ None
+ }
+ }
+
+ /// Builds an unlinked subtree that holds `entry` at `index`, from `level` down to leaf.
+ ///
+ /// A failed node allocation at any level returns the [`StoreError`] carrying `entry`, and the
+ /// nodes built so far are dropped automatically on the way out. The caller links the subtree
+ /// into the main tree only after the whole build has succeeded.
+ fn mk_subtree(
+ index: usize,
+ entry: Entry<T>,
+ level: usize,
+ flags: Flags,
+ ) -> Result<KBox<Self>, StoreError<T>> {
+ let mut node = match KBox::new(Self::new(), flags) {
+ Ok(node) => node,
+ Err(error) => {
+ return Err(StoreError {
+ error: error.into(),
+ entry,
+ })
+ }
+ };
+ let slot_index = Self::slot_index(index, level);
+ if level == 0 {
+ // INVARIANT: `Slot::encode` returns an `Int` or `Pointer` slot, both of which are
+ // permitted at leaf level.
+ node.slots[slot_index] = Slot::encode(entry);
+ } else {
+ let child_node = Self::mk_subtree(index, entry, level - 1, flags)?;
+ // INVARIANT: `Slot::mk_node` returns a node slot holding a valid `KBox<Node>`, which
+ // is permitted at interior level. The child subtree holds `entry` at its leaf level.
+ node.slots[slot_index] = Slot::mk_node(child_node);
+ }
+ Ok(node)
+ }
+
+ /// Stores `entry` at `index`, allocating intermediate nodes as needed.
+ ///
+ /// Returns the previous entry, or [`None`] if the slot was empty.
+ fn store(
+ &mut self,
+ index: usize,
+ entry: Entry<T>,
+ level: usize,
+ flags: Flags,
+ ) -> Result<Option<Entry<T>>, StoreError<T>> {
+ let slot_index = Self::slot_index(index, level);
+ let slot = &mut self.slots[slot_index];
+ if level == 0 {
+ debug_assert!(!slot.is_node(), "Found node pointer at leaf level");
+ // INVARIANT: `Slot::encode` returns an `Int` or `Pointer` slot, both of which are
+ // permitted at leaf level.
+ let old = mem::replace(slot, Slot::encode(entry));
+ return Ok(old.decode());
+ }
+
+ if slot.is_empty() {
+ // Link the subtree only after every allocation has succeeded. A failed allocation drops
+ // the partial subtree and leaves the main tree untouched.
+ let child_node = Self::mk_subtree(index, entry, level - 1, flags)?;
+ // INVARIANT: `Slot::mk_node` returns a node slot holding a valid `KBox<Node>`, which
+ // is permitted at interior level. The subtree holds `entry` at its leaf level.
+ *slot = Slot::mk_node(child_node);
+ return Ok(None);
+ }
+
+ let Some(child_node) = slot.as_node_mut() else {
+ // Unreachable unless the tree is corrupt. Rust type safety prevents this; C callers
+ // bypass that guarantee, so the FFI layer must ensure tree integrity before reaching
+ // this path.
+ debug_assert!(false, "Non-null non-node entry");
+ return Err(StoreError {
+ error: EINVAL,
+ entry,
+ });
+ };
+ // Any code after this call would block tail-call elimination.
+ child_node.store(index, entry, level - 1, flags)
+ }
+
+ /// Loads the entry at `index`.
+ ///
+ /// Returns a borrowed view of the entry, or [`None`] if the slot is empty.
+ fn load(&self, index: usize, level: usize) -> Option<BorrowedEntry<'_, T>> {
+ let slot_index = Self::slot_index(index, level);
+ let slot = &self.slots[slot_index];
+ if level == 0 {
+ debug_assert!(!slot.is_node(), "Found node pointer at leaf level");
+ return slot.borrow();
+ }
+
+ // Values at intermediate levels indicate tree corruption: trap when debug assertions are
+ // enabled, load as absent otherwise. Same shape as `load_mut()`, where the borrow checker
+ // constraints it.
+ debug_assert!(slot.is_node() || slot.is_empty(), "Non-null non-node entry");
+ slot.as_node()?.load(index, level - 1)
+ }
+
+ /// Loads the entry at `index` for mutation.
+ ///
+ /// Returns [`None`] if the slot is empty.
+ fn load_mut(&mut self, index: usize, level: usize) -> Option<BorrowedEntryMut<'_, T>> {
+ let slot_index = Self::slot_index(index, level);
+ let slot = &mut self.slots[slot_index];
+ if level == 0 {
+ debug_assert!(!slot.is_node(), "Found node pointer at leaf level");
+ return slot.borrow_mut();
+ }
+
+ // Values at intermediate levels indicate tree corruption: trap when debug assertions are
+ // enabled, load as absent otherwise.
+ debug_assert!(slot.is_node() || slot.is_empty(), "Non-null non-node entry");
+ slot.as_node_mut()?.load_mut(index, level - 1)
+ }
+}
+
+#[macros::kunit_tests(rust_rxarray)]
+mod tests {
+ use super::*;
+ use kernel::alloc::flags;
+
+ // Dispatches a test across all SHIFT/SIZE configurations. Default arm passes <T, SHIFT, SIZE>
+ // with T = KBox<u64>; `ptr` arm passes <SHIFT, SIZE> only for pointer tests that must construct
+ // KBox<u64> values because ForeignOwnable has no constructor, so T must be concrete.
+ macro_rules! for_each_xarray {
+ ($fn:ident) => {
+ $fn::<KBox<u64>, 4, 16>();
+ $fn::<KBox<u64>, 6, 64>();
+ };
+ (ptr, $fn:ident) => {
+ $fn::<4, 16>();
+ $fn::<6, 64>();
+ };
+ }
+
+ // `XArray` carries no explicit `Send`/`Sync` impl: the `PhantomData<T>` in `Slot` makes the
+ // auto-derived bounds follow `T`. These assertions fail to compile if that ever stops holding.
+ const fn assert_send<T: Send>() {}
+ const fn assert_sync<T: Sync>() {}
+
+ fn assert_auto_traits_impl<
+ T: ForeignOwnable + Send + Sync,
+ const SHIFT: usize,
+ const SIZE: usize,
+ >() {
+ assert_send::<XArray<T, SHIFT, SIZE>>();
+ assert_sync::<XArray<T, SHIFT, SIZE>>();
+ }
+
+ #[test]
+ fn assert_auto_traits() {
+ for_each_xarray!(assert_auto_traits_impl);
+ }
+
+ fn new_is_empty_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let xa = XArray::<T, SHIFT, SIZE>::new();
+ assert!(xa.is_empty());
+ assert!(xa.load(0).is_none());
+ assert!(xa.load(usize::MAX).is_none());
+ }
+
+ #[test]
+ fn new_is_empty() {
+ for_each_xarray!(new_is_empty_impl);
+ }
+
+ fn store_load_value_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ let old = xa.store(0, Entry::int::<137>(), flags::GFP_KERNEL).unwrap();
+ assert!(old.is_none());
+ assert!(!xa.is_empty());
+
+ match xa.load(0) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 137),
+ _ => panic!("expected Int"),
+ };
+ }
+
+ #[test]
+ fn store_load_value() {
+ for_each_xarray!(store_load_value_impl);
+ }
+
+ fn overwrite_value_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(137, Entry::int::<1001>(), flags::GFP_KERNEL)
+ .unwrap();
+ let old = xa
+ .store(137, Entry::int::<2002>(), flags::GFP_KERNEL)
+ .unwrap();
+
+ match old {
+ Some(Entry::Int(v)) => assert_eq!(v, 1001),
+ _ => panic!("expected old Int"),
+ }
+ match xa.load(137) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 2002),
+ _ => panic!("expected Int"),
+ };
+ }
+
+ #[test]
+ fn overwrite_value() {
+ for_each_xarray!(overwrite_value_impl);
+ }
+
+ fn erase_value_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(137, Entry::int::<1001>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(138, Entry::int::<1002>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(139, Entry::int::<1003>(), flags::GFP_KERNEL)
+ .unwrap();
+
+ let old = xa.erase(138);
+ match old {
+ Some(Entry::Int(v)) => assert_eq!(v, 1002),
+ _ => panic!("expected erased Int"),
+ }
+ assert!(xa.load(138).is_none());
+
+ match xa.load(137) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 1001),
+ _ => panic!("expected Int at neighbor"),
+ }
+ match xa.load(139) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 1003),
+ _ => panic!("expected Int at neighbor"),
+ };
+ }
+
+ #[test]
+ fn erase_value() {
+ for_each_xarray!(erase_value_impl);
+ }
+
+ // Two paths: (1) empty tree with no nodes allocated, (2) intermediate nodes exist but the
+ // target leaf slot was never stored.
+ fn erase_nonexistent_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ assert!(xa.erase(999).is_none());
+
+ // Intermediate nodes exist for index 0; index 1 shares the same leaf node but its slot was
+ // never populated.
+ xa.store(0, Entry::int::<1>(), flags::GFP_KERNEL).unwrap();
+ assert!(xa.erase(1).is_none());
+ match xa.load(0) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 1),
+ _ => panic!("expected Int after erasing neighbor"),
+ };
+ }
+
+ #[test]
+ fn erase_nonexistent() {
+ for_each_xarray!(erase_nonexistent_impl);
+ }
+
+ // After erase frees intermediate nodes, re-store at the same index must re-allocate them.
+ fn erase_and_restore_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(137, Entry::int::<1001>(), flags::GFP_KERNEL)
+ .unwrap();
+
+ let erased = xa.erase(137);
+ match erased {
+ Some(Entry::Int(v)) => assert_eq!(v, 1001),
+ _ => panic!("expected erased Int"),
+ }
+ assert!(xa.load(137).is_none());
+
+ let old = xa
+ .store(137, Entry::int::<2002>(), flags::GFP_KERNEL)
+ .unwrap();
+ assert!(old.is_none());
+ match xa.load(137) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 2002),
+ _ => panic!("expected Int"),
+ };
+ }
+
+ #[test]
+ fn erase_and_restore() {
+ for_each_xarray!(erase_and_restore_impl);
+ }
+
+ // Stores at node boundaries to exercise tree structure:
+ // - SIZE-1: last slot in the first leaf node.
+ // - SIZE: first index requiring a second leaf node.
+ // - SIZE*SIZE-1: last index in the first level-1 subtree.
+ // - SIZE*SIZE: first index requiring a third tree level.
+ fn store_at_boundaries_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(SIZE - 1, Entry::int::<0xA>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(SIZE, Entry::int::<0xB>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(SIZE * SIZE - 1, Entry::int::<0xC>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(SIZE * SIZE, Entry::int::<0xD>(), flags::GFP_KERNEL)
+ .unwrap();
+
+ match xa.load(SIZE - 1) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0xA),
+ _ => panic!("expected Int at SIZE-1"),
+ }
+ match xa.load(SIZE) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0xB),
+ _ => panic!("expected Int at SIZE"),
+ }
+ match xa.load(SIZE * SIZE - 1) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0xC),
+ _ => panic!("expected Int at SIZE*SIZE-1"),
+ }
+ match xa.load(SIZE * SIZE) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0xD),
+ _ => panic!("expected Int at SIZE*SIZE"),
+ }
+ assert!(xa.load(0).is_none());
+ }
+
+ #[test]
+ fn store_at_boundaries() {
+ for_each_xarray!(store_at_boundaries_impl);
+ }
+
+ // For XArray6 this allocates 10 intermediate nodes; for XArray4, 15. Erasing verifies cascading
+ // cleanup frees all intermediate nodes back to the root.
+ fn store_at_max_index_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(usize::MAX, Entry::int::<0xFF>(), flags::GFP_KERNEL)
+ .unwrap();
+ match xa.load(usize::MAX) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0xFF),
+ _ => panic!("expected Int at usize::MAX"),
+ }
+ assert!(xa.load(0).is_none());
+
+ match xa.erase(usize::MAX) {
+ Some(Entry::Int(v)) => assert_eq!(v, 0xFF),
+ _ => panic!("expected erased Int at usize::MAX"),
+ }
+ assert!(xa.load(usize::MAX).is_none());
+ assert!(xa.is_empty());
+ }
+
+ #[test]
+ fn store_at_max_index() {
+ for_each_xarray!(store_at_max_index_impl);
+ }
+
+ // SIZE*3 entries spanning multiple leaf nodes. Erases even indices and verifies odd indices
+ // remain intact. Erase return values are not checked here; that path is covered by erase_value.
+ fn bulk_operations_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ let count = SIZE * 3;
+
+ for i in 0..count {
+ xa.store(i, Entry::try_int(i * 10).unwrap(), flags::GFP_KERNEL)
+ .unwrap();
+ }
+
+ for i in 0..count {
+ if i % 2 == 0 {
+ xa.erase(i);
+ }
+ }
+
+ for i in 0..count {
+ match xa.load(i) {
+ Some(BorrowedEntry::Int(v)) => {
+ assert_eq!(i % 2, 1);
+ assert_eq!(v, i * 10);
+ }
+ Some(BorrowedEntry::Pointer(_)) => {
+ panic!("unexpected Pointer in value-only tree")
+ }
+ None => assert_eq!(i % 2, 0),
+ }
+ }
+ }
+
+ #[test]
+ fn bulk_operations() {
+ for_each_xarray!(bulk_operations_impl);
+ }
+
+ // Stores entries in different subtrees (indices 0 and SIZE diverge at level 1), then erases
+ // both. The second erase cascades cleanup through intermediate nodes that no longer have any
+ // children.
+ fn erase_all_restores_empty_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(0, Entry::int::<1>(), flags::GFP_KERNEL).unwrap();
+ xa.store(SIZE, Entry::int::<2>(), flags::GFP_KERNEL)
+ .unwrap();
+ assert!(!xa.is_empty());
+
+ xa.erase(0);
+ assert!(!xa.is_empty());
+ xa.erase(SIZE);
+ assert!(xa.is_empty());
+ }
+
+ #[test]
+ fn erase_all_restores_empty() {
+ for_each_xarray!(erase_all_restores_empty_impl);
+ }
+
+ // Entry::int::<0>() encodes as (0 << 1) | 1 = 1; must not be confused with the empty slot
+ // encoding (0).
+ fn value_zero_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(0, Entry::int::<0>(), flags::GFP_KERNEL).unwrap();
+ assert!(!xa.is_empty());
+ match xa.load(0) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 0),
+ _ => panic!("expected Int"),
+ }
+
+ match xa.erase(0) {
+ Some(Entry::Int(v)) => assert_eq!(v, 0),
+ _ => panic!("expected erased Int"),
+ }
+ assert!(xa.load(0).is_none());
+ }
+
+ #[test]
+ fn value_zero() {
+ for_each_xarray!(value_zero_impl);
+ }
+
+ // Pointer-specific tests (ForeignOwnable path with KBox<u64>).
+
+ fn store_load_pointer_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let boxed = KBox::new(42u64, flags::GFP_KERNEL).unwrap();
+ let old = xa
+ .store(0, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+ assert!(old.is_none());
+
+ match xa.load(0) {
+ Some(BorrowedEntry::Pointer(val)) => assert_eq!(*val, 42u64),
+ _ => panic!("expected Pointer"),
+ }
+ }
+
+ #[test]
+ fn store_load_pointer() {
+ for_each_xarray!(ptr, store_load_pointer_impl);
+ }
+
+ // Erase returns the owned KBox, verifying that Slot::decode transfers ownership via
+ // ManuallyDrop without double-free or leak.
+ fn erase_returns_pointer_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let boxed = KBox::new(99u64, flags::GFP_KERNEL).unwrap();
+ xa.store(5, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+
+ match xa.erase(5) {
+ Some(Entry::Pointer(owned)) => assert_eq!(*owned, 99u64),
+ _ => panic!("expected owned Pointer"),
+ }
+ assert!(xa.load(5).is_none());
+ }
+
+ #[test]
+ fn erase_returns_pointer() {
+ for_each_xarray!(ptr, erase_returns_pointer_impl);
+ }
+
+ fn overwrite_pointer_with_value_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let boxed = KBox::new(42u64, flags::GFP_KERNEL).unwrap();
+ xa.store(0, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+
+ let old = xa.store(0, Entry::int::<137>(), flags::GFP_KERNEL).unwrap();
+ match old {
+ Some(Entry::Pointer(p)) => assert_eq!(*p, 42u64),
+ _ => panic!("expected old Pointer"),
+ }
+ match xa.load(0) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 137),
+ _ => panic!("expected Int"),
+ }
+ }
+
+ #[test]
+ fn overwrite_pointer_with_value() {
+ for_each_xarray!(ptr, overwrite_pointer_with_value_impl);
+ }
+
+ fn overwrite_value_with_pointer_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ xa.store(0, Entry::int::<137>(), flags::GFP_KERNEL).unwrap();
+
+ let boxed = KBox::new(42u64, flags::GFP_KERNEL).unwrap();
+ let old = xa
+ .store(0, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+ match old {
+ Some(Entry::Int(v)) => assert_eq!(v, 137),
+ _ => panic!("expected old Int"),
+ }
+ match xa.load(0) {
+ Some(BorrowedEntry::Pointer(val)) => assert_eq!(*val, 42u64),
+ _ => panic!("expected Pointer"),
+ }
+ }
+
+ #[test]
+ fn overwrite_value_with_pointer() {
+ for_each_xarray!(ptr, overwrite_value_with_pointer_impl);
+ }
+
+ fn mixed_values_and_pointers_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let boxed = KBox::new(42u64, flags::GFP_KERNEL).unwrap();
+ xa.store(0, Entry::int::<100>(), flags::GFP_KERNEL).unwrap();
+ xa.store(1, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+
+ match xa.load(0) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, 100),
+ _ => panic!("expected Int"),
+ }
+ match xa.load(1) {
+ Some(BorrowedEntry::Pointer(val)) => assert_eq!(*val, 42u64),
+ _ => panic!("expected Pointer"),
+ }
+ }
+
+ #[test]
+ fn mixed_values_and_pointers() {
+ for_each_xarray!(ptr, mixed_values_and_pointers_impl);
+ }
+
+ // Drops a tree with mixed Int (even indices) and Pointer (odd indices) entries. Correctness
+ // depends on KASAN/kmemleak detecting leaks or double-frees.
+ fn drop_frees_pointers_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let count = SIZE * 2;
+ for i in 0..count {
+ if i % 2 == 0 {
+ xa.store(i, Entry::try_int(i * 10).unwrap(), flags::GFP_KERNEL)
+ .unwrap();
+ } else {
+ let b = KBox::new(i as u64, flags::GFP_KERNEL).unwrap();
+ xa.store(i, Entry::Pointer(b), flags::GFP_KERNEL).unwrap();
+ }
+ }
+ }
+
+ #[test]
+ fn drop_frees_pointers() {
+ for_each_xarray!(ptr, drop_frees_pointers_impl);
+ }
+
+ // usize::MAX >> 1 is the maximum valid integer entry. Exercises both compile-time validation
+ // (Entry::int) and runtime validation (Entry::try_int).
+ fn value_encoding_boundary_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ let max = usize::MAX >> 1;
+
+ // Runtime: try_int accepts max, rejects max+1.
+ assert!(Entry::<T>::try_int(max).is_some());
+ assert!(Entry::<T>::try_int(max + 1).is_none());
+ assert!(Entry::<T>::try_int(usize::MAX).is_none());
+
+ // Compile-time: `Entry::int` validates via `const_assert!` in `Bounded::new`.
+ xa.store(0, Entry::int::<{ usize::MAX >> 1 }>(), flags::GFP_KERNEL)
+ .unwrap();
+ match xa.load(0) {
+ Some(BorrowedEntry::Int(v)) => assert_eq!(v, max),
+ _ => panic!("expected max value"),
+ };
+ }
+
+ #[test]
+ fn value_encoding_boundary() {
+ for_each_xarray!(value_encoding_boundary_impl);
+ }
+
+ fn overwrite_pointer_with_pointer_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let first = KBox::new(42u64, flags::GFP_KERNEL).unwrap();
+ xa.store(0, Entry::Pointer(first), flags::GFP_KERNEL)
+ .unwrap();
+
+ let second = KBox::new(99u64, flags::GFP_KERNEL).unwrap();
+ let old = xa
+ .store(0, Entry::Pointer(second), flags::GFP_KERNEL)
+ .unwrap();
+ match old {
+ Some(Entry::Pointer(p)) => assert_eq!(*p, 42u64),
+ _ => panic!("expected old Pointer"),
+ }
+ match xa.load(0) {
+ Some(BorrowedEntry::Pointer(val)) => assert_eq!(*val, 99u64),
+ _ => panic!("expected new Pointer"),
+ }
+ }
+
+ #[test]
+ fn overwrite_pointer_with_pointer() {
+ for_each_xarray!(ptr, overwrite_pointer_with_pointer_impl);
+ }
+
+ fn erase_at_boundaries_impl<T: ForeignOwnable, const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<T, SHIFT, SIZE>::new();
+ xa.store(SIZE - 1, Entry::int::<0xA>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(SIZE, Entry::int::<0xB>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(SIZE * SIZE - 1, Entry::int::<0xC>(), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(SIZE * SIZE, Entry::int::<0xD>(), flags::GFP_KERNEL)
+ .unwrap();
+
+ match xa.erase(SIZE * SIZE) {
+ Some(Entry::Int(v)) => assert_eq!(v, 0xD),
+ _ => panic!("expected erased Int at SIZE*SIZE"),
+ }
+ match xa.erase(SIZE * SIZE - 1) {
+ Some(Entry::Int(v)) => assert_eq!(v, 0xC),
+ _ => panic!("expected erased Int at SIZE*SIZE-1"),
+ }
+ match xa.erase(SIZE) {
+ Some(Entry::Int(v)) => assert_eq!(v, 0xB),
+ _ => panic!("expected erased Int at SIZE"),
+ }
+ match xa.erase(SIZE - 1) {
+ Some(Entry::Int(v)) => assert_eq!(v, 0xA),
+ _ => panic!("expected erased Int at SIZE-1"),
+ }
+ assert!(xa.is_empty());
+ }
+
+ #[test]
+ fn erase_at_boundaries() {
+ for_each_xarray!(erase_at_boundaries_impl);
+ }
+
+ // Erasing the sole pointer must return the owned KBox and free all intermediate nodes.
+ fn erase_last_pointer_cascading_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ let boxed = KBox::new(777u64, flags::GFP_KERNEL).unwrap();
+ xa.store(0, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+
+ match xa.erase(0) {
+ Some(Entry::Pointer(p)) => assert_eq!(*p, 777u64),
+ _ => panic!("expected owned Pointer"),
+ }
+ assert!(xa.is_empty());
+ }
+
+ #[test]
+ fn erase_last_pointer_cascading() {
+ for_each_xarray!(ptr, erase_last_pointer_cascading_impl);
+ }
+
+ fn load_mut_entries_impl<const SHIFT: usize, const SIZE: usize>() {
+ let mut xa = XArray::<KBox<u64>, SHIFT, SIZE>::new();
+ assert!(xa.load_mut(0).is_none());
+
+ let boxed = KBox::new(137u64, flags::GFP_KERNEL).unwrap();
+ xa.store(0, Entry::Pointer(boxed), flags::GFP_KERNEL)
+ .unwrap();
+ xa.store(1, Entry::int::<137>(), flags::GFP_KERNEL).unwrap();
+
+ match xa.load_mut(0) {
+ Some(BorrowedEntryMut::Pointer(val)) => *val = 131u64,
+ _ => panic!("expected Pointer"),
+ }
+ match xa.load(0) {
+ Some(BorrowedEntry::Pointer(val)) => assert_eq!(*val, 131u64),
+ _ => panic!("expected Pointer"),
+ }
+ match xa.load_mut(1) {
+ Some(BorrowedEntryMut::Int(val)) => assert_eq!(val, 137),
+ _ => panic!("expected Int"),
+ }
+ }
+
+ #[test]
+ fn load_mut_entries() {
+ for_each_xarray!(ptr, load_mut_entries_impl);
+ }
+
+ fn store_error_impl<const SHIFT: usize, const SIZE: usize>() {
+ let err = StoreError {
+ error: ENOMEM,
+ entry: Entry::<KBox<u64>>::int::<137>(),
+ };
+ match &err.entry {
+ Entry::Int(value) => assert_eq!(*value, 137),
+ _ => panic!("expected Int"),
+ }
+ let e: Error = err.into();
+ assert_eq!(e.to_errno(), ENOMEM.to_errno());
+ }
+
+ #[test]
+ fn store_error() {
+ for_each_xarray!(ptr, store_error_impl);
+ }
+}
--
2.55.0
next prev parent reply other threads:[~2026-09-23 21:00 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 21:00 [PATCH 0/3] Rust XArray Daniel Gomez
2026-09-23 21:00 ` Daniel Gomez [this message]
2026-09-23 21:00 ` [PATCH 2/3] rust: kernel: add bench Daniel Gomez
2026-09-23 21:00 ` [PATCH 3/3] lib/xarray_benchmark_rust: add module Daniel Gomez
2026-09-23 21:04 ` [PATCH 0/3] Rust XArray Daniel Gomez
2026-09-23 21:08 ` Daniel Almeida
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260923-rxarray-next-v1-1-92eedf185649@samsung.com \
--to=da.gomez@kernel.org \
--cc=Julia.Lawall@inria.fr \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=akpm@linux-foundation.org \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=corinn.tiffany@inria.fr \
--cc=da.gomez@samsung.com \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=gary@garyguo.net \
--cc=gost.dev@samsung.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=pstanner@redhat.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=willy@infradead.org \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®