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 <[email protected]>
> 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 <[email protected]>
> ---
> 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 <[email protected]>