[PATCH 5/9] rust: use MaybeUninit::zeroed() in const context

Paolo Bonzini posted 9 patches 6 months, 2 weeks ago
There is a newer version of this series
[PATCH 5/9] rust: use MaybeUninit::zeroed() in const context
Posted by Paolo Bonzini 6 months, 2 weeks ago
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 docs/devel/rust.rst              |   4 --
 rust/hw/timer/hpet/src/fw_cfg.rs |   6 +-
 rust/qemu-api/src/zeroable.rs    | 104 +++++--------------------------
 3 files changed, 18 insertions(+), 96 deletions(-)

diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst
index 8167ff49aa9..13a002cfe69 100644
--- a/docs/devel/rust.rst
+++ b/docs/devel/rust.rst
@@ -83,10 +83,6 @@ are missing:
 * "Return position ``impl Trait`` in Traits" (1.75.0, blocker for including
   the pinned-init create).
 
-* ``MaybeUninit::zeroed()`` as a ``const`` function (1.75.0).  QEMU's
-  ``Zeroable`` trait can be implemented without ``MaybeUninit::zeroed()``,
-  so this would be just a cleanup.
-
 * ``c"" literals`` (stable in 1.77.0).  QEMU provides a ``c_str!()`` macro
   to define ``CStr`` constants easily
 
diff --git a/rust/hw/timer/hpet/src/fw_cfg.rs b/rust/hw/timer/hpet/src/fw_cfg.rs
index bef03727ea3..aa08d283519 100644
--- a/rust/hw/timer/hpet/src/fw_cfg.rs
+++ b/rust/hw/timer/hpet/src/fw_cfg.rs
@@ -4,7 +4,7 @@
 
 use std::ptr::addr_of_mut;
 
-use qemu_api::{cell::bql_locked, impl_zeroable, zeroable::Zeroable};
+use qemu_api::{cell::bql_locked, zeroable::Zeroable};
 
 /// Each `HPETState` represents a Event Timer Block. The v1 spec supports
 /// up to 8 blocks. QEMU only uses 1 block (in PC machine).
@@ -18,7 +18,7 @@ pub struct HPETFwEntry {
     pub min_tick: u16,
     pub page_prot: u8,
 }
-impl_zeroable!(HPETFwEntry);
+unsafe impl Zeroable for HPETFwEntry {}
 
 #[repr(C, packed)]
 #[derive(Copy, Clone, Default)]
@@ -26,7 +26,7 @@ pub struct HPETFwConfig {
     pub count: u8,
     pub hpet: [HPETFwEntry; HPET_MAX_NUM_EVENT_TIMER_BLOCK],
 }
-impl_zeroable!(HPETFwConfig);
+unsafe impl Zeroable for HPETFwConfig {}
 
 #[allow(non_upper_case_globals)]
 #[no_mangle]
diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs
index a3415a2ebcc..57d802db69b 100644
--- a/rust/qemu-api/src/zeroable.rs
+++ b/rust/qemu-api/src/zeroable.rs
@@ -4,89 +4,15 @@
 
 /// Encapsulates the requirement that
 /// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined
-/// behavior.  This trait in principle could be implemented as just:
-///
-/// ```
-/// pub unsafe trait Zeroable: Default {
-///     const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() };
-/// }
-/// ```
-///
-/// The need for a manual implementation is only because `zeroed()` cannot
-/// be used as a `const fn` prior to Rust 1.75.0. Once we can assume a new
-/// enough version of the compiler, we could provide a `#[derive(Zeroable)]`
-/// macro to check at compile-time that all struct fields are Zeroable, and
-/// use the above blanket implementation of the `ZERO` constant.
+/// behavior.
 ///
 /// # Safety
 ///
-/// Because the implementation of `ZERO` is manual, it does not make
-/// any assumption on the safety of `zeroed()`.  However, other users of the
-/// trait could use it that way.  Do not add this trait to a type unless
-/// all-zeroes is a valid value for the type.  In particular, remember that
-/// raw pointers can be zero, but references and `NonNull<T>` cannot
+/// Do not add this trait to a type unless all-zeroes is a valid value for the
+/// type.  In particular, raw pointers can be zero, but references and `NonNull<T>`
+/// cannot.
 pub unsafe trait Zeroable: Default {
-    const ZERO: Self;
-}
-
-/// A macro that acts similarly to [`core::mem::zeroed()`], only is const
-///
-/// ## Safety
-///
-/// Similar to `core::mem::zeroed()`, except this zeroes padding bits. Zeroed
-/// padding usually isn't relevant to safety, but might be if a C union is used.
-///
-/// Just like for `core::mem::zeroed()`, an all zero byte pattern might not
-/// be a valid value for a type, as is the case for references `&T` and `&mut
-/// T`. Reference types trigger a (denied by default) lint and cause immediate
-/// undefined behavior if the lint is ignored
-///
-/// ```rust compile_fail
-/// use const_zero::const_zero;
-/// // error: any use of this value will cause an error
-/// // note: `#[deny(const_err)]` on by default
-/// const STR: &str = unsafe{const_zero!(&'static str)};
-/// ```
-///
-/// `const_zero` does not work on unsized types:
-///
-/// ```rust compile_fail
-/// use const_zero::const_zero;
-/// // error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
-/// const BYTES: [u8] = unsafe{const_zero!([u8])};
-/// ```
-/// ## Differences with `core::mem::zeroed`
-///
-/// `const_zero` zeroes padding bits, while `core::mem::zeroed` doesn't
-#[macro_export]
-macro_rules! const_zero {
-    // This macro to produce a type-generic zero constant is taken from the
-    // const_zero crate (v0.1.1):
-    //
-    //     https://docs.rs/const-zero/latest/src/const_zero/lib.rs.html
-    //
-    // and used under MIT license
-    ($type_:ty) => {{
-        const TYPE_SIZE: ::core::primitive::usize = ::core::mem::size_of::<$type_>();
-        union TypeAsBytes {
-            bytes: [::core::primitive::u8; TYPE_SIZE],
-            inner: ::core::mem::ManuallyDrop<$type_>,
-        }
-        const ZERO_BYTES: TypeAsBytes = TypeAsBytes {
-            bytes: [0; TYPE_SIZE],
-        };
-        ::core::mem::ManuallyDrop::<$type_>::into_inner(ZERO_BYTES.inner)
-    }};
-}
-
-/// A wrapper to implement the `Zeroable` trait through the `const_zero` macro.
-#[macro_export]
-macro_rules! impl_zeroable {
-    ($type:ty) => {
-        unsafe impl $crate::zeroable::Zeroable for $type {
-            const ZERO: Self = unsafe { $crate::const_zero!($type) };
-        }
-    };
+    const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() };
 }
 
 // bindgen does not derive Default here
@@ -97,13 +23,13 @@ fn default() -> Self {
     }
 }
 
-impl_zeroable!(crate::bindings::Property__bindgen_ty_1);
-impl_zeroable!(crate::bindings::Property);
-impl_zeroable!(crate::bindings::VMStateFlags);
-impl_zeroable!(crate::bindings::VMStateField);
-impl_zeroable!(crate::bindings::VMStateDescription);
-impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1);
-impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2);
-impl_zeroable!(crate::bindings::MemoryRegionOps);
-impl_zeroable!(crate::bindings::MemTxAttrs);
-impl_zeroable!(crate::bindings::CharBackend);
+unsafe impl Zeroable for crate::bindings::Property__bindgen_ty_1 {}
+unsafe impl Zeroable for crate::bindings::Property {}
+unsafe impl Zeroable for crate::bindings::VMStateFlags {}
+unsafe impl Zeroable for crate::bindings::VMStateField {}
+unsafe impl Zeroable for crate::bindings::VMStateDescription {}
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_1 {}
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_2 {}
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps {}
+unsafe impl Zeroable for crate::bindings::MemTxAttrs {}
+unsafe impl Zeroable for crate::bindings::CharBackend {}
-- 
2.49.0
Re: [PATCH 5/9] rust: use MaybeUninit::zeroed() in const context
Posted by Manos Pitsidianakis 6 months, 2 weeks ago
On Fri, 02 May 2025 13:23, Paolo Bonzini <pbonzini@redhat.com> wrote:
>Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>---
> docs/devel/rust.rst              |   4 --
> rust/hw/timer/hpet/src/fw_cfg.rs |   6 +-
> rust/qemu-api/src/zeroable.rs    | 104 +++++--------------------------
> 3 files changed, 18 insertions(+), 96 deletions(-)

Reviewed-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>

BTW There's this TODO in qom.rs, ObjectImpl trait

>   /// `&mut T`.  TODO: add a bound of 
>   //[`Zeroable`](crate::zeroable::Zeroable)
>   /// to T; this is more easily done once Zeroable does not require a manual
>   /// implementation (Rust 1.75.0).
>   const CLASS_INIT: fn(&mut Self::Class);
Re: [PATCH 5/9] rust: use MaybeUninit::zeroed() in const context
Posted by Paolo Bonzini 6 months, 2 weeks ago
On 5/2/25 13:01, Manos Pitsidianakis wrote:
> On Fri, 02 May 2025 13:23, Paolo Bonzini <pbonzini@redhat.com> wrote:
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> ---
>> docs/devel/rust.rst              |   4 --
>> rust/hw/timer/hpet/src/fw_cfg.rs |   6 +-
>> rust/qemu-api/src/zeroable.rs    | 104 +++++--------------------------
>> 3 files changed, 18 insertions(+), 96 deletions(-)
> 
> Reviewed-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> 
> BTW There's this TODO in qom.rs, ObjectImpl trait
> 
>>    /// `&mut T`.  TODO: add a bound of
>>    //[`Zeroable`](crate::zeroable::Zeroable)
>>    /// to T; this is more easily done once Zeroable does not require a manual
>>    /// implementation (Rust 1.75.0).
>>    const CLASS_INIT: fn(&mut Self::Class);

Yes, good point.  When I wrote the TODO, my idea here was to have some 
kind of

	#[derive(Zeroable)]

macro so that the compiler can "confirm" the safety of implementing 
Zeroable by hand.

However, most of the time the class will be just a C-defined class 
(DeviceClass or SysBusDeviceClass), and then it's not even possible to 
add the derive attribute to the declaration.  So adding the bound to 
ObjectType::Class is feasible now that one can just add

     unsafe impl Zeroable for bindings::ObjectClass {}
     unsafe impl Zeroable for bindings::DeviceClass {}
     unsafe impl Zeroable for bindings::SysBusDeviceClass {}
     unsafe impl Zeroable for bindings::ResettableClass {}

etc.; in fact it was already possible when Zhao added the impl_zeroed! 
macro in commit aaf3778baaa ("rust/zeroable: Implement Zeroable with 
const_zero macro", 2025-01-28).

Paolo
Re: [PATCH 5/9] rust: use MaybeUninit::zeroed() in const context
Posted by Manos Pitsidianakis 6 months, 2 weeks ago
On Fri, May 2, 2025 at 3:23 PM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> On 5/2/25 13:01, Manos Pitsidianakis wrote:
> > On Fri, 02 May 2025 13:23, Paolo Bonzini <pbonzini@redhat.com> wrote:
> >> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> >> ---
> >> docs/devel/rust.rst              |   4 --
> >> rust/hw/timer/hpet/src/fw_cfg.rs |   6 +-
> >> rust/qemu-api/src/zeroable.rs    | 104 +++++--------------------------
> >> 3 files changed, 18 insertions(+), 96 deletions(-)
> >
> > Reviewed-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
> >
> > BTW There's this TODO in qom.rs, ObjectImpl trait
> >
> >>    /// `&mut T`.  TODO: add a bound of
> >>    //[`Zeroable`](crate::zeroable::Zeroable)
> >>    /// to T; this is more easily done once Zeroable does not require a manual
> >>    /// implementation (Rust 1.75.0).
> >>    const CLASS_INIT: fn(&mut Self::Class);
>
> Yes, good point.  When I wrote the TODO, my idea here was to have some
> kind of
>
>         #[derive(Zeroable)]
>
> macro so that the compiler can "confirm" the safety of implementing
> Zeroable by hand.
>
> However, most of the time the class will be just a C-defined class
> (DeviceClass or SysBusDeviceClass), and then it's not even possible to
> add the derive attribute to the declaration.

Yes this makes sense. A benefit of having `#[derive(Zeroable)]` is
avoiding blanket unsafe impls of the trait, since the derive macro
would work only if all fields implement Zeroable (so in a sense, it's
safe as long as the individual field type impls are safe,
transitively). And we can still do manual unsafe impls for everything
from the FFI boundary.

>  So adding the bound to
> ObjectType::Class is feasible now that one can just add
>
>      unsafe impl Zeroable for bindings::ObjectClass {}
>      unsafe impl Zeroable for bindings::DeviceClass {}
>      unsafe impl Zeroable for bindings::SysBusDeviceClass {}
>      unsafe impl Zeroable for bindings::ResettableClass {}
>
> etc.; in fact it was already possible when Zhao added the impl_zeroed!
> macro in commit aaf3778baaa ("rust/zeroable: Implement Zeroable with
> const_zero macro", 2025-01-28).
>
> Paolo
>