[PATCH v8 03/12] rust: num: add cv! macro to create values from constant expressions

Eliot Courtney <[email protected]>
Newsgroups dev.linux.lists.nova-gpu,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.rust-for-linux
Message-ID <[email protected]>
Currently, using NonZero/Bounded constants is quite verbose. It's
unfortunate because it disincentivizes using it in interface boundaries.
Introduce a macro to make it nicer to use. The macro `cv!` (for constant
value) takes a const integer expression and widens it to i128 (at build
time only) before passing it as a const generic value to a new trait
function `FromConst::from_const`. The trait is implemented by NonZero,
Bounded, and Alignment and lets values of each be constructed from
constants without a verbose turbofish syntax. For example,
`const { NonZero::new(1).unwrap() }` can be written as `cv!(1)`.

Suggested-by: Gary Guo <[email protected]>
Signed-off-by: Eliot Courtney <[email protected]>
---
 rust/kernel/num.rs         | 89 ++++++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/num/bounded.rs | 21 ++++++++++-
 rust/kernel/ptr.rs         | 13 +++++++
 3 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs
index 8532b511384c..29bf903a4a74 100644
--- a/rust/kernel/num.rs
+++ b/rust/kernel/num.rs
@@ -2,11 +2,100 @@
 
 //! Additional numerical features for the kernel.
 
+use crate::const_assert;
 use core::ops;
 
 pub mod bounded;
 pub use bounded::*;
 
+/// Creates a value from an integer constant expression, with validity checked at build time.
+///
+/// This works for any type that implements [`FromConst`], with the target type inferred from
+/// the context.
+///
+/// # Examples
+///
+/// ```
+/// use core::num::NonZero;
+/// use kernel::num::Bounded;
+/// use kernel::num::cv;
+/// use kernel::ptr::Alignment;
+///
+/// let v: NonZero<usize> = cv!(8);
+/// assert_eq!(v.get(), 8);
+///
+/// // Any integer constant expression works, not only literals.
+/// let m: NonZero<usize> = cv!(usize::MAX);
+/// assert_eq!(m.get(), usize::MAX);
+///
+/// let b: Bounded<u32, 4> = cv!(15);
+/// assert_eq!(b.get(), 15);
+///
+/// let a: Alignment = cv!(4096);
+/// assert_eq!(a.as_usize(), 4096);
+/// ```
+#[macro_export]
+#[doc(hidden)]
+macro_rules! cv {
+    ($v:expr) => {
+        $crate::num::FromConst::from_const::<
+            {
+                #[allow(unused_comparisons, unused_assignments, clippy::as_underscore)]
+                {
+                    let v = $v;
+                    let r = v as i128;
+                    // Pin `back` to `v`'s type so `as _` casts back to the source type.
+                    let mut back = v;
+                    back = r as _;
+
+                    ::core::assert!(
+                        back == v && (v < 0) == (r < 0),
+                        "value cannot be losslessly widened to `i128`"
+                    );
+
+                    r
+                }
+            },
+        >()
+    };
+}
+#[doc(inline)]
+pub use cv;
+
+/// Types that can be created from an integer constant expression validated at build time.
+// TODO: make this a `const` trait once they are stable. This will let cv! be used in const
+// contexts.
+pub trait FromConst: Sized {
+    /// Creates the value that corresponds to the constant `V`.
+    ///
+    /// Fails the build if `V` is not a valid value for `Self`.
+    fn from_const<const V: i128>() -> Self;
+}
+
+/// Implements [`FromConst`] for [`NonZero`](core::num::NonZero).
+macro_rules! impl_from_const_nonzero {
+    ($($type:ty)*) => {
+        $(
+        impl FromConst for core::num::NonZero<$type> {
+            #[inline]
+            fn from_const<const V: i128>() -> Self {
+                const_assert!(
+                    V >= <$type>::MIN as i128 && V <= <$type>::MAX as i128,
+                    "Constant cannot be represented by the underlying type."
+                );
+
+                const { core::num::NonZero::new(V as $type).unwrap() }
+            }
+        }
+        )*
+    };
+}
+
+impl_from_const_nonzero!(
+    u8 u16 u32 u64 usize
+    i8 i16 i32 i64 isize
+);
+
 /// Designates unsigned primitive types.
 pub enum Unsigned {}
 
diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
index dafe77782d79..b04bba3fa0cc 100644
--- a/rust/kernel/num/bounded.rs
+++ b/rust/kernel/num/bounded.rs
@@ -13,7 +13,10 @@
 };
 
 use kernel::{
-    num::Integer,
+    num::{
+        FromConst,
+        Integer, //
+    },
     prelude::*, //
 };
 
@@ -262,6 +265,22 @@ pub const fn new<const VALUE: $type>() -> Self {
                 unsafe { Self::__new(VALUE) }
             }
         }
+
+        impl<const N: u32> FromConst for Bounded<$type, N> {
+            #[inline]
+            fn from_const<const V: i128>() -> Self {
+                const_assert!(
+                    V >= <$type>::MIN as i128 && V <= <$type>::MAX as i128,
+                    "Constant cannot be represented by the underlying type."
+                );
+                // Statically assert that `V` fits within the set number of bits.
+                const_assert!(fits_within!(V as $type, $type, N));
+
+                // SAFETY: the asserts above confirmed that `V` can be represented within `N`
+                // bits.
+                unsafe { Self::__new(V as $type) }
+            }
+        }
         )*
     };
 }
diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs
index 82acb531b17b..3dcf415bcec3 100644
--- a/rust/kernel/ptr.rs
+++ b/rust/kernel/ptr.rs
@@ -166,6 +166,19 @@ pub const fn mask(self) -> usize {
     }
 }
 
+impl crate::num::FromConst for Alignment {
+    #[inline]
+    fn from_const<const V: i128>() -> Self {
+        const_assert!(
+            V > 0 && V <= usize::MAX as i128,
+            "Constant cannot be represented as an Alignment."
+        );
+
+        // The unwrap fails the build if `V` is not a power of two.
+        const { Alignment::new_checked(V as usize).unwrap() }
+    }
+}
+
 /// Trait for items that can be aligned against an [`Alignment`].
 pub trait Alignable: Sized {
     /// Aligns `self` down to `alignment`.

-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.