Alexandre Courbot
2025-Oct-26 14:39 UTC
[PATCH 5/7] gpu: nova-core: add extra conversion functions and traits
The core library's `From` implementations do not cover conversions
that are not portable or future-proof. For instance, even though it is
safe today, `From<usize>` is not implemented for `u64` because of the
possibility to support larger-than-64bit architectures in the future.
However, the kernel supports a narrower set of architectures, and in the
case of Nova we only support 64-bit. This makes it helpful and desirable
to provide more infallible conversions, lest we need to rely on the `as`
keyword and carry the risk of silently losing data.
Thus, introduce a new module `num` that provides safe const functions
performing more conversions allowed by the build target, as well as
`FromAs` and `IntoAs` traits that are just extensions of `From` and
`Into` to conversions that are known to be lossless.
Suggested-by: Danilo Krummrich <dakr at kernel.org>
Link: https://lore.kernel.org/rust-for-linux/DDK4KADWJHMG.1FUPL3SDR26XF at
kernel.org/
Signed-off-by: Alexandre Courbot <acourbot at nvidia.com>
---
drivers/gpu/nova-core/nova_core.rs | 1 +
drivers/gpu/nova-core/num.rs | 159 +++++++++++++++++++++++++++++++++++++
2 files changed, 160 insertions(+)
diff --git a/drivers/gpu/nova-core/nova_core.rs
b/drivers/gpu/nova-core/nova_core.rs
index e130166c1086..9180ec9c27ef 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -13,6 +13,7 @@
mod gfw;
mod gpu;
mod gsp;
+mod num;
mod regs;
mod vbios;
diff --git a/drivers/gpu/nova-core/num.rs b/drivers/gpu/nova-core/num.rs
new file mode 100644
index 000000000000..6b9d4e89dbf5
--- /dev/null
+++ b/drivers/gpu/nova-core/num.rs
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Numerical helpers functions and traits.
+//!
+//! This is essentially a staging module for code to mature until it can be
moved to the `kernel`
+//! crate.
+
+/// Infallibly converts a `usize` to `u64`.
+///
+/// This conversion is always lossless as Linux only supports 32-bit and 64-bit
platforms, thus a
+/// `usize` is always smaller than or of the same size as a `u64`.
+///
+/// Prefer this over the `as` keyword to ensure no lossy conversions are
performed.
+///
+/// This is for use from a `const` context. For non `const` use, prefer the
[`FromAs`] and
+/// [`IntoAs`] traits.
+pub(crate) const fn usize_as_u64(value: usize) -> u64 {
+ kernel::static_assert!(size_of::<u64>() >=
size_of::<usize>());
+
+ value as u64
+}
+
+#[cfg(CONFIG_32BIT)]
+/// Infallibly converts a `usize` to `u32` on 32-bit platforms.
+///
+/// This conversion is always lossless on 32-bit platforms, where a `usize` is
the same size as a
+/// `u32`.
+///
+/// Prefer this over the `as` keyword to ensure no lossy conversions are
performed.
+///
+/// This is for use from a `const` context. For non `const` use, prefer the
[`FromAs`] and
+/// [`IntoAs`] traits.
+pub(crate) const fn usize_as_u32(value: usize) -> u32 {
+ kernel::static_assert!(size_of::<u32>() >=
size_of::<usize>());
+
+ value as u32
+}
+
+/// Infallibly converts a `u32` to `usize`.
+///
+/// This conversion is always lossless as Linux only supports 32-bit and 64-bit
platforms, thus a
+/// `u32` is always smaller than or of the same size as a `usize`.
+///
+/// Prefer this over the `as` keyword to ensure no lossy conversions are
performed.
+///
+/// This is for use from a `const` context. For non `const` use, prefer the
[`FromAs`] and
+/// [`IntoAs`] traits.
+pub(crate) const fn u32_as_usize(value: u32) -> usize {
+ kernel::static_assert!(size_of::<usize>() >=
size_of::<u32>());
+
+ value as usize
+}
+
+#[cfg(CONFIG_64BIT)]
+/// Infallibly converts a `u64` to `usize` on 64-bit platforms.
+///
+/// This conversion is always lossless on 64-bit platforms, where a `usize` is
the same size as a
+/// `u64`.
+///
+/// Prefer this over the `as` keyword to ensure no lossy conversions are
performed.
+///
+/// This is for use from a `const` context. For non `const` use, prefer the
[`FromAs`] and
+/// [`IntoAs`] traits.
+pub(crate) const fn u64_as_usize(value: u64) -> usize {
+ kernel::static_assert!(size_of::<usize>() >=
size_of::<u64>());
+
+ value as usize
+}
+
+/// Extension trait providing guaranteed lossless conversion to `Self` from
`T`.
+///
+/// The standard library's `From` implementations do not cover conversions
that are not portable or
+/// future-proof. For instance, even though it is safe today,
`From<usize>` is not implemented for
+/// `u64` because of the possibility to support larger-than-64bit architectures
in the future.
+///
+/// The workaround is to either deal with the error handling of `TryFrom` for
an operation that
+/// technically cannot fail, or to use the `as` keyword, which can silently
strip data if the
+/// destination type is smaller than the source.
+///
+/// Both options are hardly acceptable for the kernel. It is also a much more
architecture
+/// dependent environment, supporting only 32 and 64 bit architectures, with
some modules
+/// explicitly depending on a specific bus witdth that could greatly benefit
from infallible
+/// conversion operations.
+///
+/// Thus this extension trait that provides, for the architecture the kernel is
built for, safe
+/// conversion between types for which such conversion is lossless.
+///
+/// In other words, this trait is implemented if, for the current build target
and with `t: T`, the
+/// `t as Self` operation is completely lossless.
+///
+/// Prefer this over the `as` keyword to ensure no lossy conversions are
performed.
+///
+/// If you need to perform a conversion in `const` context, use
[`u64_as_usize`],
+/// [`u32_as_usize`], [`usize_as_u64`], or [`usize_as_u32`].
+///
+/// # Examples
+///
+/// ```
+/// use crate::num::FromAs;
+///
+/// assert_eq!(usize::from_as(0xf00u32), 0xf00u32 as usize);
+/// ```
+pub(crate) trait FromAs<T> {
+ /// Create a `Self` from `value`. This operation is guaranteed to be
lossless.
+ fn from_as(value: T) -> Self;
+}
+
+impl FromAs<usize> for u64 {
+ fn from_as(value: usize) -> Self {
+ usize_as_u64(value)
+ }
+}
+
+#[cfg(CONFIG_32BIT)]
+impl FromAs<usize> for u32 {
+ fn from_as(value: usize) -> Self {
+ usize_as_u32(value)
+ }
+}
+
+impl FromAs<u32> for usize {
+ fn from_as(value: u32) -> Self {
+ u32_as_usize(value)
+ }
+}
+
+#[cfg(CONFIG_64BIT)]
+impl FromAs<u64> for usize {
+ fn from_as(value: u64) -> Self {
+ u64_as_usize(value)
+ }
+}
+
+/// Counterpart to the [`FromAs`] trait, i.e. this trait is to [`FromAs`] what
[`Into`] is to
+/// [`From`].
+///
+/// See the documentation of [`FromAs`] for the motivation.
+///
+/// # Examples
+///
+/// ```
+/// use crate::num::IntoAs;
+///
+/// assert_eq!(0xf00u32.into_as(), 0xf00u32 as usize);
+/// ```
+pub(crate) trait IntoAs<T> {
+ /// Convert `self` into a `T`. This operation is guaranteed to be lossless.
+ fn into_as(self) -> T;
+}
+
+/// Reverse operation for types implementing [`FromAs`].
+impl<S, T> IntoAs<T> for S
+where
+ T: FromAs<S>,
+{
+ fn into_as(self) -> T {
+ T::from_as(self)
+ }
+}
--
2.51.0
Danilo Krummrich
2025-Oct-26 15:40 UTC
[PATCH 5/7] gpu: nova-core: add extra conversion functions and traits
On 10/26/25 3:39 PM, Alexandre Courbot wrote:> +#[cfg(CONFIG_32BIT)] > +/// Infallibly converts a `usize` to `u32` on 32-bit platforms. > +/// > +/// This conversion is always lossless on 32-bit platforms, where a `usize` is the same size as a > +/// `u32`. > +/// > +/// Prefer this over the `as` keyword to ensure no lossy conversions are performed. > +/// > +/// This is for use from a `const` context. For non `const` use, prefer the [`FromAs`] and > +/// [`IntoAs`] traits. > +pub(crate) const fn usize_as_u32(value: usize) -> u32 { > + kernel::static_assert!(size_of::<u32>() >= size_of::<usize>()); > + > + value as u32 > +}Given that nova-core depends on CONFIG_64BIT, I assume you added this predicting that we'll move this to the kernel crate. For core code (and maybe other drivers) I think we also want the same thing for signed types, but I think it's also fine to add them when moving things into the kernel crate.
Miguel Ojeda
2025-Oct-26 16:44 UTC
[PATCH 5/7] gpu: nova-core: add extra conversion functions and traits
On Sun, Oct 26, 2025 at 3:40?PM Alexandre Courbot <acourbot at nvidia.com> wrote:> > +/// Infallibly converts a `usize` to `u64`. > +/// > +/// This conversion is always lossless as Linux only supports 32-bit and 64-bit platforms, thus a > +/// `usize` is always smaller than or of the same size as a `u64`. > +/// > +/// Prefer this over the `as` keyword to ensure no lossy conversions are performed. > +/// > +/// This is for use from a `const` context. For non `const` use, prefer the [`FromAs`] and > +/// [`IntoAs`] traits. > +pub(crate) const fn usize_as_u64(value: usize) -> u64 { > + kernel::static_assert!(size_of::<u64>() >= size_of::<usize>()); > + > + value as u64 > +}Since you have the static asserts, this is fine today. However, we may actually get 128-bit architectures in the not-so-distant future -- Matthew suggests to be ready by 2035: https://lwn.net/Articles/908026/ So this one in particular may actually not be true "soon" -- we also had related discussions about these assumptions, e.g.: https://lore.kernel.org/rust-for-linux/CANiq72m9AeqFKHrRniQ5Nr9vPv2MmUMHFTuuj5ydmqo+OYn60A at mail.gmail.com/ So we should consider having the `cfg` already to prevent people from assuming it will be always available, and likely a note in its docs, i.e. we may introducing trouble to port later on to new architectures. Similarly, the docs of the trait may need rewording. What do you think? Regarding the `.into_as()` name, it makes sense, but it can be a bit surprising when reading out of context... The standalone functions are super clear, in comparison. But I am not sure what could be better. `into_in_this_arch()` or similar could emphasize that this will only work in certain architectures, i.e. it is "an `into()` for this arch" rather than the general one. That would go well with the idea that you didn't implement it for other obvious types, which I guess was to avoid developers using this instead of `into()` by mistake, right? (By the way, please use intra-doc links on the primitives too.) Thanks! Cheers, Miguel
Alexandre Courbot
2025-Oct-27 12:20 UTC
[PATCH 5/7] gpu: nova-core: add extra conversion functions and traits
On Mon Oct 27, 2025 at 1:44 AM JST, Miguel Ojeda wrote:> On Sun, Oct 26, 2025 at 3:40?PM Alexandre Courbot <acourbot at nvidia.com> wrote: >> >> +/// Infallibly converts a `usize` to `u64`. >> +/// >> +/// This conversion is always lossless as Linux only supports 32-bit and 64-bit platforms, thus a >> +/// `usize` is always smaller than or of the same size as a `u64`. >> +/// >> +/// Prefer this over the `as` keyword to ensure no lossy conversions are performed. >> +/// >> +/// This is for use from a `const` context. For non `const` use, prefer the [`FromAs`] and >> +/// [`IntoAs`] traits. >> +pub(crate) const fn usize_as_u64(value: usize) -> u64 { >> + kernel::static_assert!(size_of::<u64>() >= size_of::<usize>()); >> + >> + value as u64 >> +} > > Since you have the static asserts, this is fine today. > > However, we may actually get 128-bit architectures in the > not-so-distant future -- Matthew suggests to be ready by 2035: > > https://lwn.net/Articles/908026/ > > So this one in particular may actually not be true "soon" -- we also > had related discussions about these assumptions, e.g.: > > https://lore.kernel.org/rust-for-linux/CANiq72m9AeqFKHrRniQ5Nr9vPv2MmUMHFTuuj5ydmqo+OYn60A at mail.gmail.com/ > > So we should consider having the `cfg` already to prevent people from > assuming it will be always available, and likely a note in its docs, > i.e. we may introducing trouble to port later on to new architectures. > Similarly, the docs of the trait may need rewording. > > What do you think?Do you mean adding a `#[cfg(any(CONFIG_32BIT, CONFIG_64BIT))]`? That sounds like a good idea. The static asserts will break whenever one of these functions needs to be protected by more conditional compilation anyway, but for consistency I agree it would make sense to add it now.> > Regarding the `.into_as()` name, it makes sense, but it can be a bit > surprising when reading out of context... The standalone functions are > super clear, in comparison. But I am not sure what could be better. > `into_in_this_arch()` or similar could emphasize that this will only > work in certain architectures, i.e. it is "an `into()` for this arch" > rather than the general one. > That would go well with the idea that you didn't implement it for > other obvious types, which I guess was to avoid developers using this > instead of `into()` by mistake, right?Exactly, the trait implementation is limited to conversions not already covered by `From` (because if there is a `From` implementation, it is obviously the preferred way to do it). The const functions, by contrast, need to cover all safe conversions as we cannot use `From` in a const context yet. I am happy to take suggestions for naming (I also think the current name is not great) - we could also consider dropping the trait altogether, but I find it more convenient for non-const contexts.> > (By the way, please use intra-doc links on the primitives too.)Thanks, it never occured to me that we could. ^_^;
John Hubbard
2025-Oct-27 18:46 UTC
[PATCH 5/7] gpu: nova-core: add extra conversion functions and traits
On 10/26/25 9:44 AM, Miguel Ojeda wrote:> On Sun, Oct 26, 2025 at 3:40?PM Alexandre Courbot <acourbot at nvidia.com> wrote:...> Regarding the `.into_as()` name, it makes sense, but it can be a bit > surprising when reading out of context... The standalone functions are > super clear, in comparison. But I am not sure what could be better. > `into_in_this_arch()` or similar could emphasize that this will only > work in certain architectures, i.e. it is "an `into()` for this arch" > rather than the general one. > That would go well with the idea that you didn't implement it for > other obvious types, which I guess was to avoid developers using this > instead of `into()` by mistake, right? >Exactly: the into-as, from-as naming suffers from *appearing* to be familiar and readable, but actually, the naming gives no hint as to what it is really doing--nor how it is subtly different from the basic from/as/into standard conversions. Instead, we need to add something (almost anything) to the name, to make it clearly different from the from/as/into. into_for_arch() goes in that direction, for example. thanks, -- John Hubbard