On Sat, Sep 20, 2025 at 04:29:56PM +0200, Paolo Bonzini wrote:
> Date: Sat, 20 Sep 2025 16:29:56 +0200
> From: Paolo Bonzini <pbonz...@redhat.com>
> Subject: [PATCH 5/7] rust: qemu-macros: add ToMigrationState derive macro
> X-Mailer: git-send-email 2.51.0
> 
> Add a macro that recursively builds the "migrated" version
> of a struct.
> 
> Signed-off-by: Paolo Bonzini <pbonz...@redhat.com>
> ---
>  rust/migration/meson.build              |   2 +-
>  rust/migration/src/lib.rs               |   2 +
>  rust/migration/src/migratable.rs        |  12 +-
>  rust/qemu-macros/src/lib.rs             |  88 +++++++
>  rust/qemu-macros/src/migration_state.rs | 296 ++++++++++++++++++++++++
>  rust/qemu-macros/src/tests.rs           | 112 ++++++++-
>  6 files changed, 507 insertions(+), 5 deletions(-)
>  create mode 100644 rust/qemu-macros/src/migration_state.rs

...

> diff --git a/rust/migration/src/migratable.rs 
> b/rust/migration/src/migratable.rs
> index d09eeb35f11..fa25317eea8 100644
> --- a/rust/migration/src/migratable.rs
> +++ b/rust/migration/src/migratable.rs
> @@ -79,6 +79,10 @@
>  /// # dev2.restore_migrated_state_mut(*mig, 1).unwrap();
>  /// # assert_eq!(dev2, dev);
>  /// ```
> +///
> +/// More commonly, the trait is derived through the
> +/// [`derive(ToMigrationState)`](qemu_macros::ToMigrationState) procedural
> +/// macro.
>  pub trait ToMigrationState {
>      /// The type used to represent the migrated state.
>      type Migrated: Default + VMState;
> @@ -305,13 +309,17 @@ fn restore_migrated_state(
>  /// It manages the lifecycle of migration state and provides automatic
>  /// conversion between runtime and migration representations.
>  ///
> -/// ```ignore
> +/// ```
>  /// # use std::sync::Mutex;
> -/// # use migration::Migratable;
> +/// # use migration::{Migratable, ToMigrationState, VMState, VMStateField};
>  ///
> +/// #[derive(ToMigrationState)]
>  /// pub struct DeviceRegs {
>  ///     status: u32,
>  /// }
> +/// # unsafe impl VMState for DeviceRegsMigration {
> +/// #     const BASE: VMStateField = ::common::Zeroable::ZERO;
> +/// # }

Outdated comment? Looks like the DeviceRegsMigration definition is
missing.

>  /// pub struct SomeDevice {
>  ///     // ...

...

> +/// Derive macro for generating migration state structures and trait
> +/// implementations.
> +///
> +/// This macro generates a migration state struct and implements the
> +/// `ToMigrationState` trait for the annotated struct, enabling state
> +/// serialization and restoration.  Note that defining a `VMStateDescription`
> +/// for the migration state struct is left to the user.
> +///
> +/// # Container attributes
> +///
> +/// The following attributes can be applied to the struct:
> +///
> +/// - `#[migration_state(rename = CustomName)]` - Customizes the name of the
> +///   generated migration struct. By default, the generated struct is named
> +///   `{OriginalName}Migration`.
> +///
> +/// # Field attributes
> +///
> +/// The following attributes can be applied to individual fields:
> +///
> +/// - `#[migration_state(omit)]` - Excludes the field from the migration 
> state
> +///   entirely.
> +///
> +/// - `#[migration_state(into(Type))]` - Converts the field using `.into()`
> +///   during both serialization and restoration.
> +///
> +/// - `#[migration_state(try_into(Type))]` - Converts the field using
> +///   `.try_into()` during both serialization and restoration. Returns
> +///   `InvalidError` on conversion failure.

Good idea. These conversion modes are very useful, and inspiring.

It may be not necessary for #[property] to integrate into()/try_into()
mode, but the below conversion is ugly:

#[property(rename = "msi", bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8, default = 
false)]

conversion should happen within the macro parsing process. But unfortunately,
try_into() is not const, maybe I could do this for bit property:

diff --git a/rust/qemu-macros/src/lib.rs b/rust/qemu-macros/src/lib.rs
index c459f9bcb42f..e67df57c3712 100644
--- a/rust/qemu-macros/src/lib.rs
+++ b/rust/qemu-macros/src/lib.rs
@@ -275,7 +275,10 @@ macro_rules! str_to_c_str {
                 name: ::std::ffi::CStr::as_ptr(#prop_name),
                 info: #qdev_prop,
                 offset: ::core::mem::offset_of!(#name, #field_name) as isize,
-                bitnr: #bitnr,
+                bitnr: {
+                    const _: () = assert!(#bitnr <= u8::MAX as _, "bit exceeds 
u8 range");
+                    #bitnr as u8
+                },
                 set_default: #set_default,
                 defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: 
#defval as u64 },
                 ..::common::Zeroable::ZERO

> +/// - `#[migration_state(clone)]` - Clones the field value.

How about emphasizing the use case?

"Clones the field value, especially for the types don't implement `Copy`."

> +/// Fields without any attributes use `ToMigrationState` recursively; note 
> that
> +/// this is a simple copy for types that implement `Copy`.
> +///
> +/// # Attribute compatibility
> +///
> +/// - `omit` cannot be used with any other attributes
> +/// - only one of `into(Type)`, `try_into(Type)` can be used, but they can be
> +///   coupled with `clone`.
> +///

...

The implementation of the entire macro is great.

> +#[test]
> +fn test_derive_to_migration_state() {

...

> +        quote! {
> +            #[derive(Default)]
> +            pub struct CustomMigration {
> +                pub shared_data: String,
> +                pub converted_field: Cow<'static, str>,
> +                pub fallible_field: i8,
> +                pub nested_field: <NestedStruct as 
> ToMigrationState>::Migrated,
> +                pub simple_field: <u32 as ToMigrationState>::Migrated,
> +            }

In the production code, CustomMigration still needs to implement VMState
trait, so that String & Cow<'static, str> also need to implement VMState
trait. This seems like the thing that we are currently missing.

For test, it's enough to show how the macro works.

Reviewed-by: Zhao Liu <zhao1....@intel.com>


Reply via email to