rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3] rust: optimize error type to use nonzero
@ 2024-10-03 23:16 Filipe Xavier
  2024-10-03 23:23 ` Miguel Ojeda
  0 siblings, 1 reply; 8+ messages in thread
From: Filipe Xavier @ 2024-10-03 23:16 UTC (permalink / raw)
  To: ojeda@kernel.org, aliceryhl@google.com, boqun.feng@gmail.com,
	Benno Lossin, gary@garyguo.net, alex.gaynor@gmail.com
  Cc: rust-for-linux@vger.kernel.org, Filipe Xavier

Optimize `Result<(), Error>` size by changing `Error` type to 
`NonZero*` for niche optimization.

This reduces the space used by the `Result` type, as the `NonZero*` 
type enables the compiler to apply more efficient memory layout.
For example, the `Result<(), Error>` changes size from 8 to 4 bytes.

Link: https://github.com/Rust-for-Linux/linux/issues/1120
Signed-off-by: Filipe Xavier <felipe_life@live.com>
---
 rust/kernel/error.rs | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index d7965ded2973..9e2910a6c1ea 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -9,6 +9,7 @@
 use alloc::alloc::LayoutError;
 
 use core::fmt;
+use core::num::NonZeroI32;
 use core::num::TryFromIntError;
 use core::str::Utf8Error;
 
@@ -20,7 +21,18 @@ macro_rules! declare_err {
             $(
             #[doc = $doc]
             )*
-            pub const $err: super::Error = super::Error(-(crate::bindings::$err as i32));
+            pub const $err: super::Error = {
+                let err_code: i32 = -(crate::bindings::$err as i32);
+                crate::build_assert!(
+                    err_code >= -(bindings::MAX_ERRNO as i32) && err_code < 0,
+                    "`err` must be within error code range (i.e. `>= -MAX_ERRNO && < 0`)"
+                );
+
+                // SAFETY: `err_code` is checked above to be in a valid range.
+                unsafe {
+                    super::Error::from_errno_unchecked(err_code)
+                }
+            };
         };
     }
 
@@ -88,7 +100,7 @@ macro_rules! declare_err {
 ///
 /// The value is a valid `errno` (i.e. `>= -MAX_ERRNO && < 0`).
 #[derive(Clone, Copy, PartialEq, Eq)]
-pub struct Error(core::ffi::c_int);
+pub struct Error(NonZeroI32);
 
 impl Error {
     /// Creates an [`Error`] from a kernel error code.
@@ -107,7 +119,8 @@ pub fn from_errno(errno: core::ffi::c_int) -> Error {
 
         // INVARIANT: The check above ensures the type invariant
         // will hold.
-        Error(errno)
+        // SAFETY: `errno` must be within error code range (i.e. `>= -MAX_ERRNO && < 0`).
+        unsafe { Error::from_errno_unchecked(errno) }
     }
 
     /// Creates an [`Error`] from a kernel error code.
@@ -115,21 +128,22 @@ pub fn from_errno(errno: core::ffi::c_int) -> Error {
     /// # Safety
     ///
     /// `errno` must be within error code range (i.e. `>= -MAX_ERRNO && < 0`).
-    unsafe fn from_errno_unchecked(errno: core::ffi::c_int) -> Error {
+    const unsafe fn from_errno_unchecked(errno: core::ffi::c_int) -> Error {
         // INVARIANT: The contract ensures the type invariant
         // will hold.
-        Error(errno)
+        // SAFETY: The caller guarantees `errno` is non-zero.
+        Error(unsafe { NonZeroI32::new_unchecked(errno) })
     }
 
     /// Returns the kernel error code.
     pub fn to_errno(self) -> core::ffi::c_int {
-        self.0
+        self.0.get()
     }
 
     #[cfg(CONFIG_BLOCK)]
     pub(crate) fn to_blk_status(self) -> bindings::blk_status_t {
         // SAFETY: `self.0` is a valid error due to its invariant.
-        unsafe { bindings::errno_to_blk_status(self.0) }
+        unsafe { bindings::errno_to_blk_status(self.0.get()) }
     }
 
     /// Returns the error encoded as a pointer.
@@ -137,7 +151,7 @@ pub fn to_ptr<T>(self) -> *mut T {
         #[cfg_attr(target_pointer_width = "32", allow(clippy::useless_conversion))]
         // SAFETY: `self.0` is a valid error due to its invariant.
         unsafe {
-            bindings::ERR_PTR(self.0.into()) as *mut _
+            bindings::ERR_PTR(self.0.get().into()) as *mut _
         }
     }
 
@@ -145,7 +159,7 @@ pub fn to_ptr<T>(self) -> *mut T {
     #[cfg(not(testlib))]
     pub fn name(&self) -> Option<&'static CStr> {
         // SAFETY: Just an FFI call, there are no extra safety requirements.
-        let ptr = unsafe { bindings::errname(-self.0) };
+        let ptr = unsafe { bindings::errname(-self.0.get()) };
         if ptr.is_null() {
             None
         } else {
-- 
2.46.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-03 23:16 [PATCH v3] rust: optimize error type to use nonzero Filipe Xavier
@ 2024-10-03 23:23 ` Miguel Ojeda
  2024-10-04 11:50   ` Filipe Xavier
  0 siblings, 1 reply; 8+ messages in thread
From: Miguel Ojeda @ 2024-10-03 23:23 UTC (permalink / raw)
  To: Filipe Xavier
  Cc: ojeda@kernel.org, aliceryhl@google.com, boqun.feng@gmail.com,
	Benno Lossin, gary@garyguo.net, alex.gaynor@gmail.com,
	rust-for-linux@vger.kernel.org

On Fri, Oct 4, 2024 at 1:16 AM Filipe Xavier <felipe_life@live.com> wrote:
>
> Optimize `Result<(), Error>` size by changing `Error` type to
> `NonZero*` for niche optimization.
>
> This reduces the space used by the `Result` type, as the `NonZero*`
> type enables the compiler to apply more efficient memory layout.
> For example, the `Result<(), Error>` changes size from 8 to 4 bytes.
>
> Link: https://github.com/Rust-for-Linux/linux/issues/1120
> Signed-off-by: Filipe Xavier <felipe_life@live.com>

Please note that, for new versions of a series, it is typically
expected a changelog below the `---` line, even if small. Otherwise
reviewers need to scan between the two versions to see what changed.

In addition, you have sent the new version, but you didn't reply or
have a comment here on the changes you did w.r.t. the feedback, so it
is hard to know why you changed/did in the new version.

For instance, you are using here a `build_assert!`, and that may be a
good idea, but I mentioned in v2 whether perhaps this is guaranteed by
const evaluation (or not). So given the `build_assert!`, I assume you
found a case where it is required, so it would be nice to mention
which one, for instance.

Thanks!

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-03 23:23 ` Miguel Ojeda
@ 2024-10-04 11:50   ` Filipe Xavier
  2024-10-04 12:57     ` Miguel Ojeda
  0 siblings, 1 reply; 8+ messages in thread
From: Filipe Xavier @ 2024-10-04 11:50 UTC (permalink / raw)
  To: ojeda@kernel.org, miguel.ojeda.sandonis@gmail.com
  Cc: rust-for-linux@vger.kernel.org

On Fri, Oct 3, 2024 at 11:48 PM Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote:
> Please note that, for new versions of a series, it is typically
> expected a changelog below the `---` line, even if small. Otherwise
> reviewers need to scan between the two versions to see what changed.
>
> In addition, you have sent the new version, but you didn't reply or
> have a comment here on the changes you did w.r.t. the feedback, so it
> is hard to know why you changed/did in the new version.
>
> For instance, you are using here a `build_assert!`, and that may be a
> good idea, but I mentioned in v2 whether perhaps this is guaranteed by
> const evaluation (or not). So given the `build_assert!`, I assume you
> found a case where it is required, so it would be nice to mention
> which one, for instance.

I'm sorry, actually it makes sense, I didn't know about the changelog, 
i'll send it next time.

About build_assert on macro, could assume that the C header file defines
these constants with non-zero values, but since the from_errno function 
checks this, I thought it would be interesting to validate it too. 
It makes sense?

---
Changes in v3:
- Adjusted safety comment of function from_errno.
- Adjusted macro declare_err to check if error code is valid and 
  its safety comment fixed.
Link to v2: https://lore.kernel.org/rust-for-linux/BL0PR02MB49141A172569679932EBA20CE9712@BL0PR02MB4914.namprd02.prod.outlook.com/

Changes in v2:
- Adjusted on commit message.
- Added safety comment on macro.
- Reverted signature of function from_errno.
- Removed explicit type on function to_ptr.
Link to v1: https://lore.kernel.org/rust-for-linux/BL0PR02MB49142BDF011175DADD6E79D0E9702@BL0PR02MB4914.namprd02.prod.outlook.com/
---

Thanks!

Filipe

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-04 11:50   ` Filipe Xavier
@ 2024-10-04 12:57     ` Miguel Ojeda
  2024-10-05 17:14       ` Filipe Xavier
  0 siblings, 1 reply; 8+ messages in thread
From: Miguel Ojeda @ 2024-10-04 12:57 UTC (permalink / raw)
  To: Filipe Xavier; +Cc: ojeda@kernel.org, rust-for-linux@vger.kernel.org

On Fri, Oct 4, 2024 at 1:50 PM Filipe Xavier <felipe_life@live.com> wrote:
>
> I'm sorry, actually it makes sense, I didn't know about the changelog,
> i'll send it next time.

No worries at all! It is part of learning how things are usually done.

> About build_assert on macro, could assume that the C header file defines
> these constants with non-zero values, but since the from_errno function
> checks this, I thought it would be interesting to validate it too.
> It makes sense?

Sorry, I am not exactly sure what you mean. If you need to trust the C
header file, then the macro would be unsafe and its callers would need
to explain that they indeed have to trust the C headers.

Instead, if it is possible to have a safe `const` constructor, then we
should just do that. Now, can `from_errno` be `const` to do so? If
not, can we refactor things to make it so somehow (perhaps a new
`const` private fallible constructor)?

Answering these questions should guide you a bit on a solution --
and the answers are typically what one would include in the commit
message, i.e. explaining why you picked the solution (i.e. the design
choices) you did.

Thanks!

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-04 12:57     ` Miguel Ojeda
@ 2024-10-05 17:14       ` Filipe Xavier
  2024-10-05 17:50         ` Miguel Ojeda
  0 siblings, 1 reply; 8+ messages in thread
From: Filipe Xavier @ 2024-10-05 17:14 UTC (permalink / raw)
  To: Miguel Ojeda, ojeda@kernel.org; +Cc: rust-for-linux@vger.kernel.org

> For instance, you are using here a `build_assert!`, and that may be a
> good idea, but I mentioned in v2 whether perhaps this is guaranteed
> by const evaluation (or not). So given the `build_assert!`, I assume you
> found a case where it is required, so it would be nice to mention
> which one, for instance.

Before the macro doesn't use unsafe anywhere, but now i need call a 
`NonZeroI32::new_unchecked`, i added the `build_assert!` to ensure
that the macro is safe, and improve the safety comment.

> Instead, if it is possible to have a safe `const` constructor, then we
> should just do that. Now, can `from_errno` be `const` to do so? If
> not, can we refactor things to make it so somehow (perhaps a new
> `const` private fallible constructor)?

It's possible, but i don't like how it ends up with EINVAL instead of a
compilation failure when passed an incorrect value.

`
macro_rules! declare_err {
        ($err:tt $(,)? $($doc:expr),+) => {
            $(
            #[doc = $doc]
            )*
            pub const $err: super::Error = {
                super::Error::from_errno_const(-(crate::bindings::$err as i32))
            };
        };
    }

impl Error {
    /// Creates an [`Error`] from a kernel error code.
    ///
    /// It is a bug to pass an out-of-range `errno`. `EINVAL` would
    /// be returned in such a case.
    pub fn from_errno(errno: core::ffi::c_int) -> Error {
        let result: Error = Error::from_errno_const(errno);

        if result == code::EINVAL {
            // TODO: Make it a `WARN_ONCE` once available.
            crate::pr_warn!(
                "attempted to create `Error` with out of range `errno`: {}",
                errno
            );
        }

        result
    }

    /// Creates an [`Error`] from a kernel error code.
    ///
    /// It is a bug to pass an out-of-range `errno`. `EINVAL` would
    /// be returned in such a case.
    const fn from_errno_const(errno: core::ffi::c_int) -> Error {
        if errno < -(bindings::MAX_ERRNO as i32) || errno >= 0 {
            return unsafe { Error::from_errno_unchecked(bindings::EINVAL as i32) };
        }
        
        // INVARIANT: The check above ensures the type invariant
        // will hold.
        // SAFETY: `errno` must be within error code range (i.e. `>= -MAX_ERRNO && < 0`).
        unsafe { Error::from_errno_unchecked(errno) }
    }
}`

Thanks!

Filipe

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-05 17:14       ` Filipe Xavier
@ 2024-10-05 17:50         ` Miguel Ojeda
  2024-10-05 18:52           ` Filipe Xavier
  0 siblings, 1 reply; 8+ messages in thread
From: Miguel Ojeda @ 2024-10-05 17:50 UTC (permalink / raw)
  To: Filipe Xavier; +Cc: ojeda@kernel.org, rust-for-linux@vger.kernel.org

On Sat, Oct 5, 2024 at 7:14 PM Filipe Xavier <felipe_life@live.com> wrote:
>
> Before the macro doesn't use unsafe anywhere, but now i need call a
> `NonZeroI32::new_unchecked`, i added the `build_assert!` to ensure
> that the macro is safe, and improve the safety comment.

Yeah, that is good, what I was saying is that the compiler should be
able to catch that case during CTFE (although I think it does not
guarantee it in the general case, but I don't know if it does in a
case like this). So I was curious.

> It's possible, but i don't like how it ends up with EINVAL instead of a
> compilation failure when passed an incorrect value.

Not necessarily -- you can return an `Option<Error>`, for instance,
from that new constructor.

(You may find that you want `feature(const_option)`; but you could do
it manually in a stable way too).

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-05 17:50         ` Miguel Ojeda
@ 2024-10-05 18:52           ` Filipe Xavier
  2024-10-05 19:03             ` Miguel Ojeda
  0 siblings, 1 reply; 8+ messages in thread
From: Filipe Xavier @ 2024-10-05 18:52 UTC (permalink / raw)
  To: Miguel Ojeda; +Cc: ojeda@kernel.org, rust-for-linux@vger.kernel.org

> Not necessarily -- you can return an `Option<Error>`, for instance,
> from that new constructor.

Nice, it worked here, what do you think about it?

---
`macro_rules! declare_err {
        ($err:tt $(,)? $($doc:expr),+) => {
            $(
            #[doc = $doc]
            )*
            pub const $err: super::Error = {
                match super::Error::from_errno_const(-(crate::bindings::$err as i32)) {
                    Some(err) => err,
                    None => panic!("Invalid errno in declare_err!"),
                }
            };
        };
    }

impl Error { ...

/// Creates an [`Error`] from a kernel error code.
    ///
    /// It is a bug to pass an out-of-range `errno`. `None` would
    /// be returned in such a case.
    const fn from_errno_const(errno: core::ffi::c_int) -> Option<Error> {
        if errno < -(bindings::MAX_ERRNO as i32) || errno >= 0 {
            return None;
        }

        // SAFETY: `errno` must be within error code range (i.e. `>= -MAX_ERRNO && < 0`).
        Some(unsafe { Error::from_errno_unchecked(errno) })
    }
`
---

Thanks!
Filipe

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] rust: optimize error type to use nonzero
  2024-10-05 18:52           ` Filipe Xavier
@ 2024-10-05 19:03             ` Miguel Ojeda
  0 siblings, 0 replies; 8+ messages in thread
From: Miguel Ojeda @ 2024-10-05 19:03 UTC (permalink / raw)
  To: Filipe Xavier; +Cc: ojeda@kernel.org, rust-for-linux@vger.kernel.org

On Sat, Oct 5, 2024 at 8:52 PM Filipe Xavier <felipe_life@live.com> wrote:
>
> Nice, it worked here, what do you think about it?

Yeah, something like that is what I was thinking, though let's see
what others say.

Since this returns a case that callers can distinguish thanks to
`Option`, I would perhaps avoid saying it is a bug to call it with an
invalid `errno`, and I would call it simply `try_from_errno`.

`// SAFETY` comments should explain why a precondition is true, not
just re-state the precondition, by the way.

Thanks!

Cheers,
Miguel

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2024-10-05 19:03 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-10-03 23:16 [PATCH v3] rust: optimize error type to use nonzero Filipe Xavier
2024-10-03 23:23 ` Miguel Ojeda
2024-10-04 11:50   ` Filipe Xavier
2024-10-04 12:57     ` Miguel Ojeda
2024-10-05 17:14       ` Filipe Xavier
2024-10-05 17:50         ` Miguel Ojeda
2024-10-05 18:52           ` Filipe Xavier
2024-10-05 19:03             ` Miguel Ojeda

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).