[PATCH v5 1/2] rust: add `Alignment` type

Alexandre Courbot posted 2 patches 1 day, 11 hours ago
[PATCH v5 1/2] rust: add `Alignment` type
Posted by Alexandre Courbot 1 day, 11 hours ago
Alignment operations are very common in the kernel. Since they are
always performed using a power-of-two value, enforcing this invariant
through a dedicated type leads to fewer bugs and can improve the
generated code.

Introduce the `Alignment` type, inspired by the nightly Rust type of the
same name and providing the same interface, and a new `Alignable` trait
allowing unsigned integers to be aligned up or down.

Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
 rust/kernel/lib.rs |   2 +
 rust/kernel/ptr.rs | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 228 insertions(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index fcffc3988a90392f1d5fc19f15c75d9ba7104f9a..833bb070e9a6697dad93ba10c61f5dd52da70f35 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -17,6 +17,7 @@
 // the unstable features in use.
 //
 // Stable since Rust 1.79.0.
+#![feature(generic_nonzero)]
 #![feature(inline_const)]
 #![feature(pointer_is_aligned)]
 //
@@ -111,6 +112,7 @@
 pub mod platform;
 pub mod prelude;
 pub mod print;
+pub mod ptr;
 pub mod rbtree;
 pub mod regulator;
 pub mod revocable;
diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cf419d2ec61d3850b47c4d86ff2132f13b7b60f9
--- /dev/null
+++ b/rust/kernel/ptr.rs
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Types and functions to work with pointers and addresses.
+
+use core::fmt::Debug;
+use core::mem::align_of;
+use core::num::NonZero;
+
+use crate::build_error;
+
+/// Type representing an alignment, which is always a power of two.
+///
+/// It is used to validate that a given value is a valid alignment, and to perform masking and
+/// alignment operations.
+///
+/// This is a temporary substitute for the [`Alignment`] nightly type from the standard library,
+/// and to be eventually replaced by it.
+///
+/// [`Alignment`]: https://github.com/rust-lang/rust/issues/102070
+///
+/// # Invariants
+///
+/// An alignment is always a power of two.
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Alignment(NonZero<usize>);
+
+impl Alignment {
+    /// Validates that `ALIGN` is a power of two at build-time, and returns an [`Alignment`] of the
+    /// same value.
+    ///
+    /// A build error is triggered if `ALIGN` is not a power of two.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::Alignment;
+    ///
+    /// let v = Alignment::new::<16>();
+    /// assert_eq!(v.as_usize(), 16);
+    /// ```
+    #[inline(always)]
+    pub const fn new<const ALIGN: usize>() -> Self {
+        if ALIGN.is_power_of_two() {
+            // INVARIANT: `align` is a power of two.
+            // SAFETY: `align` is a power of two, and thus non-zero.
+            Self(unsafe { NonZero::new_unchecked(ALIGN) })
+        } else {
+            build_error!("Provided alignment is not a power of two.");
+        }
+    }
+
+    /// Validates that `align` is a power of two at runtime, and returns an
+    /// [`Alignment`] of the same value.
+    ///
+    /// Returns [`None`] if `align` is not a power of two.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::Alignment;
+    ///
+    /// assert_eq!(Alignment::new_checked(16), Some(Alignment::new::<16>()));
+    /// assert_eq!(Alignment::new_checked(15), None);
+    /// assert_eq!(Alignment::new_checked(1), Some(Alignment::new::<1>()));
+    /// assert_eq!(Alignment::new_checked(0), None);
+    /// ```
+    #[inline(always)]
+    pub const fn new_checked(align: usize) -> Option<Self> {
+        if align.is_power_of_two() {
+            // INVARIANT: `align` is a power of two.
+            // SAFETY: `align` is a power of two, and thus non-zero.
+            Some(Self(unsafe { NonZero::new_unchecked(align) }))
+        } else {
+            None
+        }
+    }
+
+    /// Returns the alignment of `T`.
+    ///
+    /// This is equivalent to [`align_of`], but with the return value provided as an [`Alignment`].
+    #[inline(always)]
+    pub const fn of<T>() -> Self {
+        // This cannot panic since alignments are always powers of two.
+        //
+        // We unfortunately cannot use `new` as it would require the `generic_const_exprs` feature.
+        const { Alignment::new_checked(align_of::<T>()).unwrap() }
+    }
+
+    /// Returns this alignment as a `usize`.
+    ///
+    /// It is guaranteed to be a power of two.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::Alignment;
+    ///
+    /// assert_eq!(Alignment::new::<16>().as_usize(), 16);
+    /// ```
+    #[inline(always)]
+    pub const fn as_usize(self) -> usize {
+        self.as_nonzero().get()
+    }
+
+    /// Returns this alignment as a [`NonZero`].
+    ///
+    /// It is guaranteed to be a power of two.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::Alignment;
+    ///
+    /// assert_eq!(Alignment::new::<16>().as_nonzero().get(), 16);
+    /// ```
+    #[inline(always)]
+    pub const fn as_nonzero(self) -> NonZero<usize> {
+        // Allow the compiler to know that the value is indeed a power of two. This can help
+        // optimize some operations down the line, like e.g. replacing divisions by bit shifts.
+        if !self.0.is_power_of_two() {
+            // SAFETY: per the invariants, `self.0` is always a power of two so this block will
+            // never be reached.
+            unsafe { core::hint::unreachable_unchecked() }
+        }
+        self.0
+    }
+
+    /// Returns the base-2 logarithm of the alignment.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::Alignment;
+    ///
+    /// assert_eq!(Alignment::of::<u8>().log2(), 0);
+    /// assert_eq!(Alignment::new::<16>().log2(), 4);
+    /// ```
+    #[inline(always)]
+    pub const fn log2(self) -> u32 {
+        self.0.ilog2()
+    }
+
+    /// Returns the mask for this alignment.
+    ///
+    /// This is equivalent to `!(self.as_usize() - 1)`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::Alignment;
+    ///
+    /// assert_eq!(Alignment::new::<0x10>().mask(), !0xf);
+    /// ```
+    #[inline(always)]
+    pub const fn mask(self) -> usize {
+        // No underflow can occur as the alignment is guaranteed to be a power of two, and thus is
+        // non-zero.
+        !(self.as_usize() - 1)
+    }
+}
+
+/// Trait for items that can be aligned against an [`Alignment`].
+pub trait Alignable: Sized {
+    /// Aligns `self` down to `alignment`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::{Alignable, Alignment};
+    ///
+    /// assert_eq!(0x2f_usize.align_down(Alignment::new::<0x10>()), 0x20);
+    /// assert_eq!(0x30usize.align_down(Alignment::new::<0x10>()), 0x30);
+    /// assert_eq!(0xf0u8.align_down(Alignment::new::<0x1000>()), 0x0);
+    /// ```
+    fn align_down(self, alignment: Alignment) -> Self;
+
+    /// Aligns `self` up to `alignment`, returning `None` if aligning would result in an overflow.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::ptr::{Alignable, Alignment};
+    ///
+    /// assert_eq!(0x4fusize.align_up(Alignment::new::<0x10>()), Some(0x50));
+    /// assert_eq!(0x40usize.align_up(Alignment::new::<0x10>()), Some(0x40));
+    /// assert_eq!(0x0usize.align_up(Alignment::new::<0x10>()), Some(0x0));
+    /// assert_eq!(u8::MAX.align_up(Alignment::new::<0x10>()), None);
+    /// assert_eq!(0x10u8.align_up(Alignment::new::<0x100>()), None);
+    /// assert_eq!(0x0u8.align_up(Alignment::new::<0x100>()), Some(0x0));
+    /// ```
+    fn align_up(self, alignment: Alignment) -> Option<Self>;
+}
+
+/// Implement [`Alignable`] for unsigned integer types.
+macro_rules! impl_alignable_uint {
+    ($($t:ty),*) => {
+        $(
+        impl Alignable for $t {
+            #[inline(always)]
+            fn align_down(self, alignment: Alignment) -> Self {
+                // The operands of `&` need to be of the same type so convert the alignment to
+                // `Self`. This means we need to compute the mask ourselves.
+                ::core::num::NonZero::<Self>::try_from(alignment.as_nonzero())
+                    .map(|align| self & !(align.get() - 1))
+                    // An alignment larger than `Self` always aligns down to `0`.
+                    .unwrap_or(0)
+            }
+
+            #[inline(always)]
+            fn align_up(self, alignment: Alignment) -> Option<Self> {
+                let aligned_down = self.align_down(alignment);
+                if self == aligned_down {
+                    Some(aligned_down)
+                } else {
+                    Self::try_from(alignment.as_usize())
+                        .ok()
+                        .and_then(|align| aligned_down.checked_add(align))
+                }
+            }
+        }
+        )*
+    };
+}
+
+impl_alignable_uint!(u8, u16, u32, u64, usize);

-- 
2.51.0
Re: [PATCH v5 1/2] rust: add `Alignment` type
Posted by Miguel Ojeda 1 day, 10 hours ago
On Mon, Sep 8, 2025 at 3:26 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> +        if ALIGN.is_power_of_two() {
> +            // INVARIANT: `align` is a power of two.
> +            // SAFETY: `align` is a power of two, and thus non-zero.
> +            Self(unsafe { NonZero::new_unchecked(ALIGN) })
> +        } else {
> +            build_error!("Provided alignment is not a power of two.");
> +        }

Would `build_assert!` at the top work instead?

> +    /// Returns this alignment as a `usize`.

Intra-doc link.

> +            // SAFETY: per the invariants, `self.0` is always a power of two so this block will

"Per".

I can pick this, with or without the user -- or do you need this in
Nova this cycle?

Thanks!

Cheers,
Miguel
Re: [PATCH v5 1/2] rust: add `Alignment` type
Posted by Alexandre Courbot 18 hours ago
Hi Miguel,

On Mon Sep 8, 2025 at 11:21 PM JST, Miguel Ojeda wrote:
> On Mon, Sep 8, 2025 at 3:26 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>>
>> +        if ALIGN.is_power_of_two() {
>> +            // INVARIANT: `align` is a power of two.
>> +            // SAFETY: `align` is a power of two, and thus non-zero.
>> +            Self(unsafe { NonZero::new_unchecked(ALIGN) })
>> +        } else {
>> +            build_error!("Provided alignment is not a power of two.");
>> +        }
>
> Would `build_assert!` at the top work instead?

Oops, it certainly would.

>
>> +    /// Returns this alignment as a `usize`.
>
> Intra-doc link.
>
>> +            // SAFETY: per the invariants, `self.0` is always a power of two so this block will
>
> "Per".
>
> I can pick this, with or without the user -- or do you need this in
> Nova this cycle?

With -rc6 approaching, I don't think we will merge any Nova code taking
advantage of this for this cycle, so please feel free to take this
patch. We can then merge patch 2 through drm-rust after -rc1 is
released.

Should I send a new revision with your fixes, or will you apply them?
Re: [PATCH v5 1/2] rust: add `Alignment` type
Posted by Miguel Ojeda 17 hours ago
On Tue, Sep 9, 2025 at 9:05 AM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> With -rc6 approaching, I don't think we will merge any Nova code taking
> advantage of this for this cycle, so please feel free to take this
> patch. We can then merge patch 2 through drm-rust after -rc1 is
> released.

Sure, sounds good. I can also take patch 2 already as a user of this
if you prefer (ideally with an Acked-by).

> Should I send a new revision with your fixes, or will you apply them?

I can, no worries, thanks!

Cheers,
Miguel
Re: [PATCH v5 1/2] rust: add `Alignment` type
Posted by Danilo Krummrich 16 hours ago
On Tue Sep 9, 2025 at 9:21 AM CEST, Miguel Ojeda wrote:
> On Tue, Sep 9, 2025 at 9:05 AM Alexandre Courbot <acourbot@nvidia.com> wrote:
>>
>> With -rc6 approaching, I don't think we will merge any Nova code taking
>> advantage of this for this cycle, so please feel free to take this
>> patch. We can then merge patch 2 through drm-rust after -rc1 is
>> released.
>
> Sure, sounds good. I can also take patch 2 already as a user of this
> if you prefer (ideally with an Acked-by).

Yeah, this way around make much more sense,

Acked-by: Danilo Krummrich <dakr@kernel.org>
Re: [PATCH v5 1/2] rust: add `Alignment` type
Posted by Alexandre Courbot 17 hours ago
On Tue Sep 9, 2025 at 4:21 PM JST, Miguel Ojeda wrote:
> On Tue, Sep 9, 2025 at 9:05 AM Alexandre Courbot <acourbot@nvidia.com> wrote:
>>
>> With -rc6 approaching, I don't think we will merge any Nova code taking
>> advantage of this for this cycle, so please feel free to take this
>> patch. We can then merge patch 2 through drm-rust after -rc1 is
>> released.
>
> Sure, sounds good. I can also take patch 2 already as a user of this
> if you prefer (ideally with an Acked-by).

Ah right, you will want to introduce a user for this new API. Sure, feel
free to take patch 2 as well!

Acked-by: Alexandre Courbot <acourbot@nvidia.com>