rust: vmstate: use const_refs_to_static

The VMStateDescriptionBuilder already needs const_refs_static, so
use it to remove the need for vmstate_clock! and vmstate_struct!,
as well as to simplify the implementation for scalars.

If the consts in the VMState trait can reference to static
VMStateDescription, scalars do not need the info_enum_to_ref!
indirection and structs can implement the VMState trait themselves.

Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
Link: https://lore.kernel.org/r/20250908105005.2119297-9-pbonzini@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Paolo Bonzini
2025-09-08 12:49:40 +02:00
parent 7da9ee9207
commit dcdee1e718
6 changed files with 113 additions and 217 deletions

View File

@ -2,14 +2,11 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{
ffi::CStr,
mem::size_of
};
use std::{ffi::CStr, mem::size_of};
use qemu_api::{
chardev::{CharBackend, Chardev, Event},
impl_vmstate_forward,
impl_vmstate_forward, impl_vmstate_struct,
irq::{IRQState, InterruptSource},
log::Log,
log_mask_ln,
@ -21,7 +18,7 @@ use qemu_api::{
sysbus::{SysBusDevice, SysBusDeviceImpl},
uninit_field_mut,
vmstate::{self, VMStateDescription, VMStateDescriptionBuilder},
vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_unused,
vmstate_fields, vmstate_of, vmstate_subsections, vmstate_unused,
};
use crate::registers::{self, Interrupt, RegisterOffset};
@ -725,11 +722,12 @@ static VMSTATE_PL011_CLOCK: VMStateDescription<PL011State> =
.minimum_version_id(1)
.needed(&PL011State::clock_needed)
.fields(vmstate_fields! {
vmstate_clock!(PL011State, clock),
vmstate_of!(PL011State, clock),
})
.build();
static VMSTATE_PL011_REGS: VMStateDescription<PL011Registers> =
impl_vmstate_struct!(
PL011Registers,
VMStateDescriptionBuilder::<PL011Registers>::new()
.name(c"pl011/regs")
.version_id(2)
@ -751,7 +749,8 @@ static VMSTATE_PL011_REGS: VMStateDescription<PL011Registers> =
vmstate_of!(PL011Registers, read_count),
vmstate_of!(PL011Registers, read_trigger),
})
.build();
.build()
);
pub const VMSTATE_PL011: VMStateDescription<PL011State> =
VMStateDescriptionBuilder::<PL011State>::new()
@ -761,7 +760,7 @@ pub const VMSTATE_PL011: VMStateDescription<PL011State> =
.post_load(&PL011State::post_load)
.fields(vmstate_fields! {
vmstate_unused!(core::mem::size_of::<u32>()),
vmstate_struct!(PL011State, regs, &VMSTATE_PL011_REGS, BqlRefCell<PL011Registers>),
vmstate_of!(PL011State, regs),
})
.subsections(vmstate_subsections! {
VMSTATE_PL011_CLOCK

View File

@ -16,6 +16,7 @@ use qemu_api::{
qdev_prop_uint32, qdev_prop_usize,
},
cell::{BqlCell, BqlRefCell},
impl_vmstate_struct,
irq::InterruptSource,
memory::{
hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED,
@ -28,7 +29,7 @@ use qemu_api::{
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
uninit_field_mut,
vmstate::{self, VMStateDescription, VMStateDescriptionBuilder},
vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_validate,
vmstate_fields, vmstate_of, vmstate_subsections, vmstate_validate,
};
use crate::fw_cfg::HPETFwConfig;
@ -964,7 +965,7 @@ static VMSTATE_HPET_OFFSET: VMStateDescription<HPETState> =
})
.build();
static VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> =
const VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> =
VMStateDescriptionBuilder::<HPETTimer>::new()
.name(c"hpet_timer")
.version_id(1)
@ -979,6 +980,7 @@ static VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> =
vmstate_of!(HPETTimer, qemu_timer),
})
.build();
impl_vmstate_struct!(HPETTimer, VMSTATE_HPET_TIMER);
const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match";
@ -995,7 +997,7 @@ const VMSTATE_HPET: VMStateDescription<HPETState> =
vmstate_of!(HPETState, counter),
vmstate_of!(HPETState, num_timers_save),
vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers),
vmstate_struct!(HPETState, timers[0 .. num_timers_save], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0),
vmstate_of!(HPETState, timers[0 .. num_timers_save], HPETState::validate_num_timers).with_version_id(0),
})
.subsections(vmstate_subsections!(
VMSTATE_HPET_RTC_IRQ_LEVEL,

View File

@ -95,10 +95,6 @@ macro_rules! assert_field_type {
($t:ty, $i:tt, $ti:ty) => {
$crate::assert_field_type!(@internal v, $ti, $t, v.$i);
};
($t:ty, $i:tt, $ti:ty, num = $num:ident) => {
$crate::assert_field_type!(@internal v, $ti, $t, v.$i[0]);
};
}
/// Assert that an expression matches a pattern. This can also be

View File

@ -11,10 +11,11 @@
//! migration format for a struct. This is based on the [`VMState`] trait,
//! which is defined by all migratable types.
//!
//! * [`impl_vmstate_forward`](crate::impl_vmstate_forward) and
//! [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), which help with
//! the definition of the [`VMState`] trait (respectively for transparent
//! structs and for `bilge`-defined types)
//! * [`impl_vmstate_forward`](crate::impl_vmstate_forward),
//! [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), and
//! [`impl_vmstate_struct`](crate::impl_vmstate_struct), which help with the
//! definition of the [`VMState`] trait (respectively for transparent structs,
//! nested structs and `bilge`-defined types)
//!
//! * helper macros to declare a device model state struct, in particular
//! [`vmstate_subsections`](crate::vmstate_subsections) and
@ -31,7 +32,7 @@ use std::{
fmt, io,
marker::PhantomData,
mem,
ptr::NonNull,
ptr::{addr_of, NonNull},
};
pub use crate::bindings::{MigrationPriority, VMStateField};
@ -40,6 +41,7 @@ use crate::{
callbacks::FnCall,
errno::{into_neg_errno, Errno},
prelude::*,
qdev,
qom::Owned,
zeroable::Zeroable,
};
@ -81,70 +83,6 @@ macro_rules! call_func_with_field {
};
}
/// Workaround for lack of `const_refs_static`: references to global variables
/// can be included in a `static`, but not in a `const`; unfortunately, this
/// is exactly what would go in the `VMStateField`'s `info` member.
///
/// This enum contains the contents of the `VMStateField`'s `info` member,
/// but as an `enum` instead of a pointer.
#[allow(non_camel_case_types)]
pub enum VMStateFieldType {
null,
vmstate_info_bool,
vmstate_info_int8,
vmstate_info_int16,
vmstate_info_int32,
vmstate_info_int64,
vmstate_info_uint8,
vmstate_info_uint16,
vmstate_info_uint32,
vmstate_info_uint64,
vmstate_info_timer,
}
/// Workaround for lack of `const_refs_static`. Converts a `VMStateFieldType`
/// to a `*const VMStateInfo`, for inclusion in a `VMStateField`.
#[macro_export]
macro_rules! info_enum_to_ref {
($e:expr) => {
unsafe {
match $e {
$crate::vmstate::VMStateFieldType::null => ::core::ptr::null(),
$crate::vmstate::VMStateFieldType::vmstate_info_bool => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_bool)
}
$crate::vmstate::VMStateFieldType::vmstate_info_int8 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_int8)
}
$crate::vmstate::VMStateFieldType::vmstate_info_int16 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_int16)
}
$crate::vmstate::VMStateFieldType::vmstate_info_int32 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_int32)
}
$crate::vmstate::VMStateFieldType::vmstate_info_int64 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_int64)
}
$crate::vmstate::VMStateFieldType::vmstate_info_uint8 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_uint8)
}
$crate::vmstate::VMStateFieldType::vmstate_info_uint16 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_uint16)
}
$crate::vmstate::VMStateFieldType::vmstate_info_uint32 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32)
}
$crate::vmstate::VMStateFieldType::vmstate_info_uint64 => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_uint64)
}
$crate::vmstate::VMStateFieldType::vmstate_info_timer => {
::core::ptr::addr_of!($crate::bindings::vmstate_info_timer)
}
}
}
};
}
/// A trait for types that can be included in a device's migration stream. It
/// provides the base contents of a `VMStateField` (minus the name and offset).
///
@ -155,12 +93,6 @@ macro_rules! info_enum_to_ref {
/// to implement it except via macros that do it for you, such as
/// `impl_vmstate_bitsized!`.
pub unsafe trait VMState {
/// The `info` member of a `VMStateField` is a pointer and as such cannot
/// yet be included in the [`BASE`](VMState::BASE) associated constant;
/// this is only allowed by Rust 1.83.0 and newer. For now, include the
/// member as an enum which is stored in a separate constant.
const SCALAR_TYPE: VMStateFieldType = VMStateFieldType::null;
/// The base contents of a `VMStateField` (minus the name and offset) for
/// the type that is implementing the trait.
const BASE: VMStateField;
@ -175,12 +107,6 @@ pub unsafe trait VMState {
};
}
/// Internal utility function to retrieve a type's `VMStateFieldType`;
/// used by [`vmstate_of!`](crate::vmstate_of).
pub const fn vmstate_scalar_type<T: VMState>(_: PhantomData<T>) -> VMStateFieldType {
T::SCALAR_TYPE
}
/// Internal utility function to retrieve a type's `VMStateField`;
/// used by [`vmstate_of!`](crate::vmstate_of).
pub const fn vmstate_base<T: VMState>(_: PhantomData<T>) -> VMStateField {
@ -207,9 +133,9 @@ pub const fn vmstate_varray_flag<T: VMState>(_: PhantomData<T>) -> VMStateFlags
/// * an array of any of the above
///
/// In order to support other types, the trait `VMState` must be implemented
/// for them. The macros
/// [`impl_vmstate_bitsized!`](crate::impl_vmstate_bitsized)
/// and [`impl_vmstate_forward!`](crate::impl_vmstate_forward) help with this.
/// for them. The macros [`impl_vmstate_forward`](crate::impl_vmstate_forward),
/// [`impl_vmstate_bitsized`](crate::impl_vmstate_bitsized), and
/// [`impl_vmstate_struct`](crate::impl_vmstate_struct) help with this.
#[macro_export]
macro_rules! vmstate_of {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])? $(, $test_fn:expr)? $(,)?) => {
@ -222,11 +148,6 @@ macro_rules! vmstate_of {
$(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)?
// The calls to `call_func_with_field!` are the magic that
// computes most of the VMStateField from the type of the field.
info: $crate::info_enum_to_ref!($crate::call_func_with_field!(
$crate::vmstate::vmstate_scalar_type,
$struct_name,
$field_name
)),
..$crate::call_func_with_field!(
$crate::vmstate::vmstate_base,
$struct_name,
@ -327,8 +248,6 @@ macro_rules! impl_vmstate_forward {
// the first field of the tuple
($tuple:ty) => {
unsafe impl $crate::vmstate::VMState for $tuple {
const SCALAR_TYPE: $crate::vmstate::VMStateFieldType =
$crate::call_func_with_field!($crate::vmstate::vmstate_scalar_type, $tuple, 0);
const BASE: $crate::bindings::VMStateField =
$crate::call_func_with_field!($crate::vmstate::vmstate_base, $tuple, 0);
}
@ -340,7 +259,6 @@ macro_rules! impl_vmstate_forward {
macro_rules! impl_vmstate_transparent {
($type:ty where $base:tt: VMState $($where:tt)*) => {
unsafe impl<$base> VMState for $type where $base: VMState $($where)* {
const SCALAR_TYPE: VMStateFieldType = <$base as VMState>::SCALAR_TYPE;
const BASE: VMStateField = VMStateField {
size: mem::size_of::<$type>(),
..<$base as VMState>::BASE
@ -361,10 +279,6 @@ impl_vmstate_transparent!(crate::cell::Opaque<T> where T: VMState);
macro_rules! impl_vmstate_bitsized {
($type:ty) => {
unsafe impl $crate::vmstate::VMState for $type {
const SCALAR_TYPE: $crate::vmstate::VMStateFieldType =
<<<$type as ::bilge::prelude::Bitsized>::ArbitraryInt
as ::bilge::prelude::Number>::UnderlyingType
as $crate::vmstate::VMState>::SCALAR_TYPE;
const BASE: $crate::bindings::VMStateField =
<<<$type as ::bilge::prelude::Bitsized>::ArbitraryInt
as ::bilge::prelude::Number>::UnderlyingType
@ -382,8 +296,8 @@ macro_rules! impl_vmstate_bitsized {
macro_rules! impl_vmstate_scalar {
($info:ident, $type:ty$(, $varray_flag:ident)?) => {
unsafe impl VMState for $type {
const SCALAR_TYPE: VMStateFieldType = VMStateFieldType::$info;
const BASE: VMStateField = VMStateField {
info: addr_of!(bindings::$info),
size: mem::size_of::<$type>(),
flags: VMStateFlags::VMS_SINGLE,
..Zeroable::ZERO
@ -404,6 +318,21 @@ impl_vmstate_scalar!(vmstate_info_uint32, u32, VMS_VARRAY_UINT32);
impl_vmstate_scalar!(vmstate_info_uint64, u64);
impl_vmstate_scalar!(vmstate_info_timer, crate::timer::Timer);
macro_rules! impl_vmstate_c_struct {
($type:ty, $vmsd:expr) => {
unsafe impl VMState for $type {
const BASE: VMStateField = $crate::bindings::VMStateField {
vmsd: addr_of!($vmsd),
size: mem::size_of::<$type>(),
flags: VMStateFlags::VMS_STRUCT,
..Zeroable::ZERO
};
}
};
}
impl_vmstate_c_struct!(qdev::Clock, bindings::vmstate_clock);
// Pointer types using the underlying type's VMState plus VMS_POINTER
// Note that references are not supported, though references to cells
// could be allowed.
@ -411,7 +340,6 @@ impl_vmstate_scalar!(vmstate_info_timer, crate::timer::Timer);
macro_rules! impl_vmstate_pointer {
($type:ty where $base:tt: VMState $($where:tt)*) => {
unsafe impl<$base> VMState for $type where $base: VMState $($where)* {
const SCALAR_TYPE: VMStateFieldType = <T as VMState>::SCALAR_TYPE;
const BASE: VMStateField = <$base as VMState>::BASE.with_pointer_flag();
}
};
@ -430,7 +358,6 @@ impl_vmstate_pointer!(Owned<T> where T: VMState + ObjectType);
// VMS_ARRAY/VMS_ARRAY_OF_POINTER
unsafe impl<T: VMState, const N: usize> VMState for [T; N] {
const SCALAR_TYPE: VMStateFieldType = <T as VMState>::SCALAR_TYPE;
const BASE: VMStateField = <T as VMState>::BASE.with_array_flag(N);
}
@ -452,7 +379,7 @@ pub extern "C" fn rust_vms_test_field_exists<T, F: for<'a> FnCall<(&'a T, u8), b
opaque: *mut c_void,
version_id: c_int,
) -> bool {
// SAFETY: assumes vmstate_struct! is used correctly
// SAFETY: the function is used in T's implementation of VMState
let owner: &T = unsafe { &*(opaque.cast::<T>()) };
let version: u8 = version_id.try_into().unwrap();
F::call((owner, version))
@ -480,76 +407,6 @@ macro_rules! vmstate_exist_fn {
}};
}
// FIXME: including the `vmsd` field in a `const` is not possible without
// the const_refs_static feature (stabilized in Rust 1.83.0). Without it,
// it is not possible to use VMS_STRUCT in a transparent manner using
// `vmstate_of!`. While VMSTATE_CLOCK can at least try to be type-safe,
// VMSTATE_STRUCT includes $type only for documentation purposes; it
// is checked against $field_name and $struct_name, but not against $vmsd
// which is what really would matter.
#[doc(alias = "VMSTATE_STRUCT")]
#[macro_export]
macro_rules! vmstate_struct {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?, $vmsd:expr, $type:ty $(, $test_fn:expr)? $(,)?) => {
$crate::bindings::VMStateField {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
$(num_offset: ::std::mem::offset_of!($struct_name, $num),)?
offset: {
$crate::assert_field_type!($struct_name, $field_name, $type $(, num = $num)?);
::std::mem::offset_of!($struct_name, $field_name)
},
size: ::core::mem::size_of::<$type>(),
flags: $crate::bindings::VMStateFlags::VMS_STRUCT,
vmsd: $vmsd.as_ref(),
$(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)?
..$crate::zeroable::Zeroable::ZERO
} $(.with_varray_flag_unchecked(
$crate::call_func_with_field!(
$crate::vmstate::vmstate_varray_flag,
$struct_name,
$num
)
)
$(.with_varray_multiply($factor))?)?
};
}
#[doc(alias = "VMSTATE_CLOCK")]
#[macro_export]
macro_rules! vmstate_clock {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?) => {{
$crate::bindings::VMStateField {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
offset: {
$crate::assert_field_type!(
$struct_name,
$field_name,
$crate::qom::Owned<$crate::qdev::Clock> $(, num = $num)?
);
::std::mem::offset_of!($struct_name, $field_name)
},
size: ::core::mem::size_of::<*const $crate::qdev::Clock>(),
flags: $crate::bindings::VMStateFlags(
$crate::bindings::VMStateFlags::VMS_STRUCT.0
| $crate::bindings::VMStateFlags::VMS_POINTER.0,
),
vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) },
..$crate::zeroable::Zeroable::ZERO
} $(.with_varray_flag_unchecked(
$crate::call_func_with_field!(
$crate::vmstate::vmstate_varray_flag,
$struct_name,
$num
)
)
$(.with_varray_multiply($factor))?)?
}};
}
/// Helper macro to declare a list of
/// ([`VMStateField`](`crate::bindings::VMStateField`)) into a static and return
/// a pointer to the array of values it created.
@ -584,6 +441,30 @@ macro_rules! vmstate_validate {
};
}
/// Helper macro to allow using a struct in [`vmstate_of!`]
///
/// # Safety
///
/// The [`VMStateDescription`] constant `$vmsd` must be an accurate
/// description of the struct.
#[macro_export]
macro_rules! impl_vmstate_struct {
($type:ty, $vmsd:expr) => {
unsafe impl $crate::vmstate::VMState for $type {
const BASE: $crate::bindings::VMStateField = {
static VMSD: &$crate::bindings::VMStateDescription = $vmsd.as_ref();
$crate::bindings::VMStateField {
vmsd: ::core::ptr::addr_of!(*VMSD),
size: ::core::mem::size_of::<$type>(),
flags: $crate::bindings::VMStateFlags::VMS_STRUCT,
..$crate::zeroable::Zeroable::ZERO
}
};
}
};
}
/// A transparent wrapper type for the `subsections` field of
/// [`VMStateDescription`].
///
@ -648,7 +529,7 @@ unsafe extern "C" fn vmstate_no_version_cb<
>(
opaque: *mut c_void,
) -> c_int {
// SAFETY: assumes vmstate_struct! is used correctly
// SAFETY: the function is used in T's implementation of VMState
let result = F::call((unsafe { &*(opaque.cast::<T>()) },));
into_neg_errno(result)
}
@ -660,7 +541,7 @@ unsafe extern "C" fn vmstate_post_load_cb<
opaque: *mut c_void,
version_id: c_int,
) -> c_int {
// SAFETY: assumes vmstate_struct! is used correctly
// SAFETY: the function is used in T's implementation of VMState
let owner: &T = unsafe { &*(opaque.cast::<T>()) };
let version: u8 = version_id.try_into().unwrap();
let result = F::call((owner, version));
@ -670,14 +551,14 @@ unsafe extern "C" fn vmstate_post_load_cb<
unsafe extern "C" fn vmstate_needed_cb<T, F: for<'a> FnCall<(&'a T,), bool>>(
opaque: *mut c_void,
) -> bool {
// SAFETY: assumes vmstate_struct! is used correctly
// SAFETY: the function is used in T's implementation of VMState
F::call((unsafe { &*(opaque.cast::<T>()) },))
}
unsafe extern "C" fn vmstate_dev_unplug_pending_cb<T, F: for<'a> FnCall<(&'a T,), bool>>(
opaque: *mut c_void,
) -> bool {
// SAFETY: assumes vmstate_struct! is used correctly
// SAFETY: the function is used in T's implementation of VMState
F::call((unsafe { &*(opaque.cast::<T>()) },))
}

View File

@ -15,9 +15,9 @@ use qemu_api::{
vmstate_info_uint64, vmstate_info_uint8, vmstate_info_unused_buffer, VMStateFlags,
},
cell::{BqlCell, Opaque},
impl_vmstate_forward,
impl_vmstate_forward, impl_vmstate_struct,
vmstate::{VMStateDescription, VMStateDescriptionBuilder, VMStateField},
vmstate_fields, vmstate_of, vmstate_struct, vmstate_unused, vmstate_validate,
vmstate_fields, vmstate_of, vmstate_unused, vmstate_validate,
};
const FOO_ARRAY_MAX: usize = 3;
@ -52,6 +52,8 @@ static VMSTATE_FOOA: VMStateDescription<FooA> = VMStateDescriptionBuilder::<FooA
})
.build();
impl_vmstate_struct!(FooA, VMSTATE_FOOA);
#[test]
fn test_vmstate_uint16() {
let foo_fields: &[VMStateField] =
@ -173,20 +175,19 @@ fn validate_foob(_state: &FooB, _version_id: u8) -> bool {
true
}
static VMSTATE_FOOB: VMStateDescription<FooB> =
VMStateDescriptionBuilder::<FooB>::new()
.name(c"foo_b")
.version_id(2)
.minimum_version_id(1)
.fields(vmstate_fields! {
vmstate_of!(FooB, val).with_version_id(2),
vmstate_of!(FooB, wrap),
vmstate_struct!(FooB, arr_a[0 .. num_a], &VMSTATE_FOOA, FooA).with_version_id(1),
vmstate_struct!(FooB, arr_a_mul[0 .. num_a_mul * 32], &VMSTATE_FOOA, FooA).with_version_id(2),
vmstate_of!(FooB, arr_i64),
vmstate_struct!(FooB, arr_a_wrap[0 .. num_a_wrap], &VMSTATE_FOOA, FooA, validate_foob),
})
.build();
static VMSTATE_FOOB: VMStateDescription<FooB> = VMStateDescriptionBuilder::<FooB>::new()
.name(c"foo_b")
.version_id(2)
.minimum_version_id(1)
.fields(vmstate_fields! {
vmstate_of!(FooB, val).with_version_id(2),
vmstate_of!(FooB, wrap),
vmstate_of!(FooB, arr_a[0 .. num_a]).with_version_id(1),
vmstate_of!(FooB, arr_a_mul[0 .. num_a_mul * 32]).with_version_id(2),
vmstate_of!(FooB, arr_i64),
vmstate_of!(FooB, arr_a_wrap[0 .. num_a_wrap], validate_foob),
})
.build();
#[test]
fn test_vmstate_bool_v() {
@ -351,9 +352,7 @@ static VMSTATE_FOOC: VMStateDescription<FooC> = VMStateDescriptionBuilder::<FooC
.minimum_version_id(1)
.fields(vmstate_fields! {
vmstate_of!(FooC, ptr).with_version_id(2),
// FIXME: Currently vmstate_struct doesn't support the pointer to structure.
// VMSTATE_STRUCT_POINTER: vmstate_struct!(FooC, ptr_a, VMSTATE_FOOA, NonNull<FooA>)
vmstate_unused!(size_of::<NonNull<FooA>>()),
vmstate_of!(FooC, ptr_a),
vmstate_of!(FooC, arr_ptr),
vmstate_of!(FooC, arr_ptr_wrap),
})
@ -385,6 +384,31 @@ fn test_vmstate_pointer() {
assert!(foo_fields[0].field_exists.is_none());
}
#[test]
fn test_vmstate_struct_pointer() {
let foo_fields: &[VMStateField] =
unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) };
// 2st VMStateField ("ptr_a") in VMSTATE_FOOC (corresponding to
// VMSTATE_STRUCT_POINTER)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(),
b"ptr_a\0"
);
assert_eq!(foo_fields[1].offset, PTR_SIZE);
assert_eq!(foo_fields[1].num_offset, 0);
assert_eq!(foo_fields[1].vmsd, VMSTATE_FOOA.as_ref());
assert_eq!(foo_fields[1].version_id, 0);
assert_eq!(foo_fields[1].size, size_of::<FooA>());
assert_eq!(foo_fields[1].num, 0);
assert_eq!(
foo_fields[1].flags.0,
VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0
);
assert!(foo_fields[1].info.is_null());
assert!(foo_fields[1].field_exists.is_none());
}
#[test]
fn test_vmstate_macro_array_of_pointer() {
let foo_fields: &[VMStateField] =
@ -444,8 +468,7 @@ fn test_vmstate_macro_array_of_pointer_wrapped() {
// * VMSTATE_FOOD:
// - VMSTATE_VALIDATE
// Add more member fields when vmstate_of/vmstate_struct support "test"
// parameter.
// Add more member fields when vmstate_of support "test" parameter.
struct FooD;
impl FooD {