* [PATCH 3/5] rust: vmstate: convert to use builder pattern
2025-05-05 10:08 [PATCH preview 0/5] rust: allow minimum version of 1.83 Paolo Bonzini
2025-05-05 10:08 ` [PATCH 1/5] meson, cargo: require Rust 1.83.0 Paolo Bonzini
2025-05-05 10:08 ` [PATCH 2/5] rust: use inline const expressions Paolo Bonzini
@ 2025-05-05 10:08 ` Paolo Bonzini
2025-05-06 9:55 ` Zhao Liu
2025-05-05 10:08 ` [PATCH 4/5] rust: vmstate: use const_refs_to_static Paolo Bonzini
` (2 subsequent siblings)
5 siblings, 1 reply; 20+ messages in thread
From: Paolo Bonzini @ 2025-05-05 10:08 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust, Zhao Liu
Similar to MemoryRegionOps, the builder pattern has two advantages:
1) it makes it possible to build a VMStateDescription that knows which
types it will be invoked on; 2) it provides a way to wrap the callbacks
and let devices avoid "unsafe".
Unfortunately, building a static VMStateDescription requires the
builder methods to be "const", and because the VMStateFields are
*also* static, this requires const_refs_to_static. So this requires
Rust 1.83.0.
Co-developed-by: Zhao Liu <zhao1.liu@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
docs/devel/rust.rst | 2 +-
rust/hw/char/pl011/src/device.rs | 16 +-
rust/hw/char/pl011/src/device_class.rs | 117 ++++++--------
rust/hw/timer/hpet/src/hpet.rs | 163 ++++++++-----------
rust/qemu-api/src/qdev.rs | 6 +-
rust/qemu-api/src/vmstate.rs | 214 ++++++++++++++++++++++++-
rust/qemu-api/tests/tests.rs | 16 +-
rust/qemu-api/tests/vmstate_tests.rs | 124 +++++++-------
8 files changed, 413 insertions(+), 245 deletions(-)
diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst
index cc52adcfdbe..ed1c765e722 100644
--- a/docs/devel/rust.rst
+++ b/docs/devel/rust.rst
@@ -152,7 +152,7 @@ module status
``qom`` stable
``sysbus`` stable
``timer`` stable
-``vmstate`` proof of concept
+``vmstate`` stable
``zeroable`` stable
================ ======================
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 7c563ade9cd..38373f54e7c 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -2,7 +2,7 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use std::{ffi::CStr, mem::size_of, ptr::addr_of_mut};
+use std::{ffi::CStr, io, mem::size_of, ptr::addr_of_mut};
use qemu_api::{
chardev::{CharBackend, Chardev, Event},
@@ -172,8 +172,8 @@ impl DeviceImpl for PL011State {
fn properties() -> &'static [Property] {
&device_class::PL011_PROPERTIES
}
- fn vmsd() -> Option<&'static VMStateDescription> {
- Some(&device_class::VMSTATE_PL011)
+ fn vmsd() -> Option<VMStateDescription<Self>> {
+ Some(device_class::VMSTATE_PL011)
}
const REALIZE: Option<fn(&Self)> = Some(Self::realize);
}
@@ -457,10 +457,10 @@ pub fn put_fifo(&mut self, value: registers::Data) -> bool {
false
}
- pub fn post_load(&mut self) -> Result<(), ()> {
+ pub fn post_load(&mut self) -> Result<(), io::ErrorKind> {
/* Sanity-check input state */
if self.read_pos >= self.read_fifo.len() || self.read_count > self.read_fifo.len() {
- return Err(());
+ return Err(io::ErrorKind::InvalidInput);
}
if !self.fifo_enabled() && self.read_count > 0 && self.read_pos > 0 {
@@ -524,6 +524,10 @@ const fn clock_update(&self, _event: ClockEvent) {
/* pl011_trace_baudrate_change(s); */
}
+ pub fn clock_needed(&self) -> bool {
+ self.migrate_clock
+ }
+
fn post_init(&self) {
self.init_mmio(&self.iomem);
for irq in self.interrupts.iter() {
@@ -629,7 +633,7 @@ fn update(&self) {
}
}
- pub fn post_load(&self, _version_id: u32) -> Result<(), ()> {
+ pub fn post_load(&self, _version_id: u8) -> Result<(), io::ErrorKind> {
self.regs.borrow_mut().post_load()
}
}
diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs
index d328d846323..ed72bfad25f 100644
--- a/rust/hw/char/pl011/src/device_class.rs
+++ b/rust/hw/char/pl011/src/device_class.rs
@@ -2,86 +2,65 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
-use std::{
- ffi::{c_int, c_void},
- ptr::NonNull,
-};
-
use qemu_api::{
bindings::{qdev_prop_bool, qdev_prop_chr},
prelude::*,
- vmstate::VMStateDescription,
+ vmstate::{VMStateDescription, VMStateDescriptionBuilder},
vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_unused,
- zeroable::Zeroable,
};
use crate::device::{PL011Registers, PL011State};
-extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool {
- let state = NonNull::new(opaque).unwrap().cast::<PL011State>();
- unsafe { state.as_ref().migrate_clock }
-}
-
/// Migration subsection for [`PL011State`] clock.
-static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription {
- name: c"pl011/clock".as_ptr(),
- version_id: 1,
- minimum_version_id: 1,
- needed: Some(pl011_clock_needed),
- fields: vmstate_fields! {
- vmstate_clock!(PL011State, clock),
- },
- ..Zeroable::ZERO
-};
+static VMSTATE_PL011_CLOCK: VMStateDescription<PL011State> =
+ VMStateDescriptionBuilder::<PL011State>::new()
+ .name(c"pl011/clock")
+ .version_id(1)
+ .minimum_version_id(1)
+ .needed(&PL011State::clock_needed)
+ .fields(vmstate_fields! {
+ vmstate_clock!(PL011State, clock),
+ })
+ .build();
-extern "C" fn pl011_post_load(opaque: *mut c_void, version_id: c_int) -> c_int {
- let state = NonNull::new(opaque).unwrap().cast::<PL011State>();
- let result = unsafe { state.as_ref().post_load(version_id as u32) };
- if result.is_err() {
- -1
- } else {
- 0
- }
-}
+static VMSTATE_PL011_REGS: VMStateDescription<PL011Registers> =
+ VMStateDescriptionBuilder::<PL011Registers>::new()
+ .name(c"pl011/regs")
+ .version_id(2)
+ .minimum_version_id(2)
+ .fields(vmstate_fields! {
+ vmstate_of!(PL011Registers, flags),
+ vmstate_of!(PL011Registers, line_control),
+ vmstate_of!(PL011Registers, receive_status_error_clear),
+ vmstate_of!(PL011Registers, control),
+ vmstate_of!(PL011Registers, dmacr),
+ vmstate_of!(PL011Registers, int_enabled),
+ vmstate_of!(PL011Registers, int_level),
+ vmstate_of!(PL011Registers, read_fifo),
+ vmstate_of!(PL011Registers, ilpr),
+ vmstate_of!(PL011Registers, ibrd),
+ vmstate_of!(PL011Registers, fbrd),
+ vmstate_of!(PL011Registers, ifl),
+ vmstate_of!(PL011Registers, read_pos),
+ vmstate_of!(PL011Registers, read_count),
+ vmstate_of!(PL011Registers, read_trigger),
+ })
+ .build();
-static VMSTATE_PL011_REGS: VMStateDescription = VMStateDescription {
- name: c"pl011/regs".as_ptr(),
- version_id: 2,
- minimum_version_id: 2,
- fields: vmstate_fields! {
- vmstate_of!(PL011Registers, flags),
- vmstate_of!(PL011Registers, line_control),
- vmstate_of!(PL011Registers, receive_status_error_clear),
- vmstate_of!(PL011Registers, control),
- vmstate_of!(PL011Registers, dmacr),
- vmstate_of!(PL011Registers, int_enabled),
- vmstate_of!(PL011Registers, int_level),
- vmstate_of!(PL011Registers, read_fifo),
- vmstate_of!(PL011Registers, ilpr),
- vmstate_of!(PL011Registers, ibrd),
- vmstate_of!(PL011Registers, fbrd),
- vmstate_of!(PL011Registers, ifl),
- vmstate_of!(PL011Registers, read_pos),
- vmstate_of!(PL011Registers, read_count),
- vmstate_of!(PL011Registers, read_trigger),
- },
- ..Zeroable::ZERO
-};
-
-pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
- name: c"pl011".as_ptr(),
- version_id: 2,
- minimum_version_id: 2,
- post_load: Some(pl011_post_load),
- fields: vmstate_fields! {
- vmstate_unused!(core::mem::size_of::<u32>()),
- vmstate_struct!(PL011State, regs, &VMSTATE_PL011_REGS, BqlRefCell<PL011Registers>),
- },
- subsections: vmstate_subsections! {
- VMSTATE_PL011_CLOCK
- },
- ..Zeroable::ZERO
-};
+pub const VMSTATE_PL011: VMStateDescription<PL011State> =
+ VMStateDescriptionBuilder::<PL011State>::new()
+ .name(c"pl011")
+ .version_id(2)
+ .minimum_version_id(2)
+ .post_load(&PL011State::post_load)
+ .fields(vmstate_fields! {
+ vmstate_unused!(core::mem::size_of::<u32>()),
+ vmstate_struct!(PL011State, regs, &VMSTATE_PL011_REGS, BqlRefCell<PL011Registers>),
+ })
+ .subsections(vmstate_subsections! {
+ VMSTATE_PL011_CLOCK
+ })
+ .build();
qemu_api::declare_properties! {
PL011_PROPERTIES,
diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs
index 779681d6509..1ed1cb7dcaf 100644
--- a/rust/hw/timer/hpet/src/hpet.rs
+++ b/rust/hw/timer/hpet/src/hpet.rs
@@ -3,7 +3,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{
- ffi::{c_int, c_void, CStr},
+ ffi::CStr,
+ io,
pin::Pin,
ptr::{addr_of_mut, null_mut, NonNull},
slice::from_ref,
@@ -25,9 +26,8 @@
qom_isa,
sysbus::{SysBusDevice, SysBusDeviceImpl},
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
- vmstate::VMStateDescription,
+ vmstate::{VMStateDescription, VMStateDescriptionBuilder},
vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_validate,
- zeroable::Zeroable,
};
use crate::fw_cfg::HPETFwConfig;
@@ -211,6 +211,10 @@ pub struct HPETTimer {
last: u64,
}
+// SAFETY: Sync is not automatically derived due to the `state` field,
+// which is always dereferenced to a shared reference.
+unsafe impl Sync for HPETTimer {}
+
impl HPETTimer {
fn init(&mut self, index: u8, state: &HPETState) {
*self = HPETTimer {
@@ -843,7 +847,7 @@ fn write(&self, addr: hwaddr, value: u64, size: u32) {
}
}
- fn pre_save(&self) -> i32 {
+ fn pre_save(&self) -> Result<(), io::ErrorKind> {
if self.is_hpet_enabled() {
self.counter.set(self.get_ticks());
}
@@ -854,10 +858,10 @@ fn pre_save(&self) -> i32 {
* that was configured.
*/
self.num_timers_save.set(self.num_timers.get());
- 0
+ Ok(())
}
- fn post_load(&self, _version_id: u8) -> i32 {
+ fn post_load(&self, _version_id: u8) -> Result<(), io::ErrorKind> {
for timer in self.timers.iter().take(self.get_num_timers()) {
let mut t = timer.borrow_mut();
@@ -871,7 +875,7 @@ fn post_load(&self, _version_id: u8) -> i32 {
.set(ticks_to_ns(self.counter.get()) - CLOCK_VIRTUAL.get_ns());
}
- 0
+ Ok(())
}
fn is_rtc_irq_level_needed(&self) -> bool {
@@ -941,105 +945,74 @@ impl ObjectImpl for HPETState {
),
}
-unsafe extern "C" fn hpet_rtc_irq_level_needed(opaque: *mut c_void) -> bool {
- // SAFETY:
- // the pointer is convertible to a reference
- let state: &HPETState = unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_ref() };
- state.is_rtc_irq_level_needed()
-}
+static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription<HPETState> =
+ VMStateDescriptionBuilder::<HPETState>::new()
+ .name(c"hpet/rtc_irq_level")
+ .version_id(1)
+ .minimum_version_id(1)
+ .needed(&HPETState::is_rtc_irq_level_needed)
+ .fields(vmstate_fields! {
+ vmstate_of!(HPETState, rtc_irq_level),
+ })
+ .build();
-unsafe extern "C" fn hpet_offset_needed(opaque: *mut c_void) -> bool {
- // SAFETY:
- // the pointer is convertible to a reference
- let state: &HPETState = unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_ref() };
- state.is_offset_needed()
-}
+static VMSTATE_HPET_OFFSET: VMStateDescription<HPETState> =
+ VMStateDescriptionBuilder::<HPETState>::new()
+ .name(c"hpet/offset")
+ .version_id(1)
+ .minimum_version_id(1)
+ .needed(&HPETState::is_offset_needed)
+ .fields(vmstate_fields! {
+ vmstate_of!(HPETState, hpet_offset),
+ })
+ .build();
-unsafe extern "C" fn hpet_pre_save(opaque: *mut c_void) -> c_int {
- // SAFETY:
- // the pointer is convertible to a reference
- let state: &mut HPETState =
- unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_mut() };
- state.pre_save() as c_int
-}
-
-unsafe extern "C" fn hpet_post_load(opaque: *mut c_void, version_id: c_int) -> c_int {
- // SAFETY:
- // the pointer is convertible to a reference
- let state: &mut HPETState =
- unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_mut() };
- let version: u8 = version_id.try_into().unwrap();
- state.post_load(version) as c_int
-}
-
-static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription = VMStateDescription {
- name: c"hpet/rtc_irq_level".as_ptr(),
- version_id: 1,
- minimum_version_id: 1,
- needed: Some(hpet_rtc_irq_level_needed),
- fields: vmstate_fields! {
- vmstate_of!(HPETState, rtc_irq_level),
- },
- ..Zeroable::ZERO
-};
-
-static VMSTATE_HPET_OFFSET: VMStateDescription = VMStateDescription {
- name: c"hpet/offset".as_ptr(),
- version_id: 1,
- minimum_version_id: 1,
- needed: Some(hpet_offset_needed),
- fields: vmstate_fields! {
- vmstate_of!(HPETState, hpet_offset),
- },
- ..Zeroable::ZERO
-};
-
-static VMSTATE_HPET_TIMER: VMStateDescription = VMStateDescription {
- name: c"hpet_timer".as_ptr(),
- version_id: 1,
- minimum_version_id: 1,
- fields: vmstate_fields! {
- vmstate_of!(HPETTimer, index),
- vmstate_of!(HPETTimer, config),
- vmstate_of!(HPETTimer, cmp),
- vmstate_of!(HPETTimer, fsb),
- vmstate_of!(HPETTimer, period),
- vmstate_of!(HPETTimer, wrap_flag),
- vmstate_of!(HPETTimer, qemu_timer),
- },
- ..Zeroable::ZERO
-};
+static VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> =
+ VMStateDescriptionBuilder::<HPETTimer>::new()
+ .name(c"hpet_timer")
+ .version_id(1)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
+ vmstate_of!(HPETTimer, index),
+ vmstate_of!(HPETTimer, config),
+ vmstate_of!(HPETTimer, cmp),
+ vmstate_of!(HPETTimer, fsb),
+ vmstate_of!(HPETTimer, period),
+ vmstate_of!(HPETTimer, wrap_flag),
+ vmstate_of!(HPETTimer, qemu_timer),
+ })
+ .build();
const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match";
-static VMSTATE_HPET: VMStateDescription = VMStateDescription {
- name: c"hpet".as_ptr(),
- version_id: 2,
- minimum_version_id: 1,
- pre_save: Some(hpet_pre_save),
- post_load: Some(hpet_post_load),
- fields: vmstate_fields! {
- vmstate_of!(HPETState, config),
- vmstate_of!(HPETState, int_status),
- vmstate_of!(HPETState, counter),
- vmstate_of!(HPETState, num_timers_save).with_version_id(2),
- vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers),
- vmstate_struct!(HPETState, timers[0 .. num_timers], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0),
- },
- subsections: vmstate_subsections! {
- VMSTATE_HPET_RTC_IRQ_LEVEL,
- VMSTATE_HPET_OFFSET,
- },
- ..Zeroable::ZERO
-};
+const VMSTATE_HPET: VMStateDescription<HPETState> =
+ VMStateDescriptionBuilder::<HPETState>::new()
+ .name(c"hpet")
+ .version_id(2)
+ .minimum_version_id(1)
+ .pre_save(&HPETState::pre_save)
+ .post_load(&HPETState::post_load)
+ .fields(vmstate_fields! {
+ vmstate_of!(HPETState, config),
+ vmstate_of!(HPETState, int_status),
+ vmstate_of!(HPETState, counter),
+ vmstate_of!(HPETState, num_timers_save).with_version_id(2),
+ vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers),
+ vmstate_struct!(HPETState, timers[0 .. num_timers], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0),
+ })
+ .subsections(vmstate_subsections!(
+ VMSTATE_HPET_RTC_IRQ_LEVEL,
+ VMSTATE_HPET_OFFSET,
+ ))
+ .build();
impl DeviceImpl for HPETState {
fn properties() -> &'static [Property] {
&HPET_PROPERTIES
}
- fn vmsd() -> Option<&'static VMStateDescription> {
- Some(&VMSTATE_HPET)
+ fn vmsd() -> Option<VMStateDescription<Self>> {
+ Some(VMSTATE_HPET)
}
const REALIZE: Option<fn(&Self)> = Some(Self::realize);
diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs
index 6c93805a742..09555bbd0e7 100644
--- a/rust/qemu-api/src/qdev.rs
+++ b/rust/qemu-api/src/qdev.rs
@@ -120,7 +120,7 @@ fn properties() -> &'static [Property] {
/// A `VMStateDescription` providing the migration format for the device
/// Not a `const` because referencing statics in constants is unstable
/// until Rust 1.83.0.
- fn vmsd() -> Option<&'static VMStateDescription> {
+ fn vmsd() -> Option<VMStateDescription<Self>> {
None
}
}
@@ -169,7 +169,9 @@ pub fn class_init<T: DeviceImpl>(&mut self) {
self.realize = Some(rust_realize_fn::<T>);
}
if let Some(vmsd) = <T as DeviceImpl>::vmsd() {
- self.vmsd = vmsd;
+ // Give a 'static lifetime to the return value of vmsd().
+ // Temporary until vmsd() can be changed into a const.
+ self.vmsd = Box::leak(Box::new(vmsd.get()));
}
let prop = <T as DeviceImpl>::properties();
if !prop.is_empty() {
diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs
index c564bd70308..228d748b6b7 100644
--- a/rust/qemu-api/src/vmstate.rs
+++ b/rust/qemu-api/src/vmstate.rs
@@ -25,11 +25,16 @@
//! functionality that is missing from `vmstate_of!`.
use core::{marker::PhantomData, mem, ptr::NonNull};
-use std::ffi::{c_int, c_void};
+use std::ffi::{c_int, c_void, CStr};
-pub use crate::bindings::{VMStateDescription, VMStateField};
+pub use crate::bindings::{MigrationPriority, VMStateField};
use crate::{
- bindings::VMStateFlags, callbacks::FnCall, prelude::*, qom::Owned, zeroable::Zeroable,
+ bindings::{self, VMStateFlags},
+ callbacks::FnCall,
+ errno::{into_neg_errno, Errno},
+ prelude::*,
+ qom::Owned,
+ zeroable::Zeroable,
};
/// This macro is used to call a function with a generic argument bound
@@ -440,7 +445,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: the opaque was passed as a reference to `T`.
+ // SAFETY: assumes vmstate_struct! is used correctly
let owner: &T = unsafe { &*(opaque.cast::<T>()) };
let version: u8 = version_id.try_into().unwrap();
F::call((owner, version))
@@ -490,7 +495,7 @@ macro_rules! vmstate_struct {
},
size: ::core::mem::size_of::<$type>(),
flags: $crate::bindings::VMStateFlags::VMS_STRUCT,
- vmsd: $vmsd,
+ vmsd: $vmsd.as_ref(),
$(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)?
..$crate::zeroable::Zeroable::ZERO
} $(.with_varray_flag_unchecked(
@@ -594,11 +599,206 @@ macro_rules! vmstate_subsections {
($($subsection:expr),*$(,)*) => {{
static _SUBSECTIONS: $crate::vmstate::VMStateSubsectionsWrapper = $crate::vmstate::VMStateSubsectionsWrapper(&[
$({
- static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection;
+ static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection.get();
::core::ptr::addr_of!(_SUBSECTION)
}),*,
::core::ptr::null()
]);
- _SUBSECTIONS.0.as_ptr()
+ &_SUBSECTIONS
}}
}
+
+pub struct VMStateDescription<T>(bindings::VMStateDescription, PhantomData<fn(&T)>);
+
+// SAFETY: When a *const T is passed to the callbacks, the call itself
+// is done in a thread-safe manner. The invocation is okay as long as
+// T itself is `Sync`.
+unsafe impl<T: Sync> Sync for VMStateDescription<T> {}
+
+#[derive(Clone)]
+pub struct VMStateDescriptionBuilder<T>(bindings::VMStateDescription, PhantomData<fn(&T)>);
+
+unsafe extern "C" fn vmstate_pre_load_cb<
+ T,
+ F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>,
+>(
+ opaque: *mut c_void,
+) -> c_int {
+ // SAFETY: assumes vmstate_struct! is used correctly
+ let result = F::call((unsafe { &*(opaque.cast::<T>()) },));
+ into_neg_errno(result)
+}
+
+unsafe extern "C" fn vmstate_post_load_cb<
+ T,
+ F: for<'a> FnCall<(&'a T, u8), Result<(), impl Into<Errno>>>,
+>(
+ opaque: *mut c_void,
+ version_id: c_int,
+) -> c_int {
+ // SAFETY: assumes vmstate_struct! is used correctly
+ let owner: &T = unsafe { &*(opaque.cast::<T>()) };
+ let version: u8 = version_id.try_into().unwrap();
+ let result = F::call((owner, version));
+ into_neg_errno(result)
+}
+
+unsafe extern "C" fn vmstate_pre_save_cb<
+ T,
+ F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>,
+>(
+ opaque: *mut c_void,
+) -> c_int {
+ // SAFETY: assumes vmstate_struct! is used correctly
+ let result = F::call((unsafe { &*(opaque.cast::<T>()) },));
+ into_neg_errno(result)
+}
+
+unsafe extern "C" fn vmstate_post_save_cb<
+ T,
+ F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>,
+>(
+ opaque: *mut c_void,
+) -> c_int {
+ // SAFETY: assumes vmstate_struct! is used correctly
+ let result = F::call((unsafe { &*(opaque.cast::<T>()) },));
+ into_neg_errno(result)
+}
+
+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
+ 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
+ F::call((unsafe { &*(opaque.cast::<T>()) },))
+}
+
+impl<T> VMStateDescriptionBuilder<T> {
+ #[must_use]
+ pub const fn name(mut self, name_str: &CStr) -> Self {
+ self.0.name = ::std::ffi::CStr::as_ptr(name_str);
+ self
+ }
+
+ #[must_use]
+ pub const fn unmigratable(mut self) -> Self {
+ self.0.unmigratable = true;
+ self
+ }
+
+ #[must_use]
+ pub const fn early_setup(mut self) -> Self {
+ self.0.early_setup = true;
+ self
+ }
+
+ #[must_use]
+ pub const fn version_id(mut self, version: u8) -> Self {
+ self.0.version_id = version as c_int;
+ self
+ }
+
+ #[must_use]
+ pub const fn minimum_version_id(mut self, min_version: u8) -> Self {
+ self.0.minimum_version_id = min_version as c_int;
+ self
+ }
+
+ #[must_use]
+ pub const fn priority(mut self, priority: MigrationPriority) -> Self {
+ self.0.priority = priority;
+ self
+ }
+
+ #[must_use]
+ pub const fn pre_load<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>(
+ mut self,
+ _f: &F,
+ ) -> Self {
+ self.0.pre_load = Some(vmstate_pre_load_cb::<T, F>);
+ self
+ }
+
+ #[must_use]
+ pub const fn post_load<F: for<'a> FnCall<(&'a T, u8), Result<(), impl Into<Errno>>>>(
+ mut self,
+ _f: &F,
+ ) -> Self {
+ self.0.post_load = Some(vmstate_post_load_cb::<T, F>);
+ self
+ }
+
+ #[must_use]
+ pub const fn pre_save<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>(
+ mut self,
+ _f: &F,
+ ) -> Self {
+ self.0.pre_save = Some(vmstate_pre_save_cb::<T, F>);
+ self
+ }
+
+ #[must_use]
+ pub const fn post_save<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>(
+ mut self,
+ _f: &F,
+ ) -> Self {
+ self.0.post_save = Some(vmstate_post_save_cb::<T, F>);
+ self
+ }
+
+ #[must_use]
+ pub const fn needed<F: for<'a> FnCall<(&'a T,), bool>>(mut self, _f: &F) -> Self {
+ self.0.needed = Some(vmstate_needed_cb::<T, F>);
+ self
+ }
+
+ #[must_use]
+ pub const fn unplug_pending<F: for<'a> FnCall<(&'a T,), bool>>(mut self, _f: &F) -> Self {
+ self.0.dev_unplug_pending = Some(vmstate_dev_unplug_pending_cb::<T, F>);
+ self
+ }
+
+ #[must_use]
+ pub const fn fields(mut self, fields: *const VMStateField) -> Self {
+ self.0.fields = fields;
+ self
+ }
+
+ #[must_use]
+ pub const fn subsections(mut self, subs: &'static VMStateSubsectionsWrapper) -> Self {
+ self.0.subsections = subs.0.as_ptr();
+ self
+ }
+
+ #[must_use]
+ pub const fn build(self) -> VMStateDescription<T> {
+ VMStateDescription::<T>(self.0, PhantomData)
+ }
+
+ #[must_use]
+ pub const fn new() -> Self {
+ Self(bindings::VMStateDescription::ZERO, PhantomData)
+ }
+}
+
+impl<T> Default for VMStateDescriptionBuilder<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T> VMStateDescription<T> {
+ pub const fn get(&self) -> bindings::VMStateDescription {
+ self.0
+ }
+
+ pub const fn as_ref(&self) -> &bindings::VMStateDescription {
+ &self.0
+ }
+}
diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs
index a658a49fcfd..3264641d128 100644
--- a/rust/qemu-api/tests/tests.rs
+++ b/rust/qemu-api/tests/tests.rs
@@ -12,18 +12,16 @@
qdev::{DeviceImpl, DeviceState, Property, ResettablePhasesImpl},
qom::{ObjectImpl, ParentField},
sysbus::SysBusDevice,
- vmstate::VMStateDescription,
- zeroable::Zeroable,
+ vmstate::{VMStateDescription, VMStateDescriptionBuilder},
};
mod vmstate_tests;
// Test that macros can compile.
-pub static VMSTATE: VMStateDescription = VMStateDescription {
- name: c"name".as_ptr(),
- unmigratable: true,
- ..Zeroable::ZERO
-};
+pub const VMSTATE: VMStateDescription<DummyState> = VMStateDescriptionBuilder::<DummyState>::new()
+ .name(c"name")
+ .unmigratable()
+ .build();
#[repr(C)]
#[derive(qemu_api_macros::Object)]
@@ -72,8 +70,8 @@ impl DeviceImpl for DummyState {
fn properties() -> &'static [Property] {
&DUMMY_PROPERTIES
}
- fn vmsd() -> Option<&'static VMStateDescription> {
- Some(&VMSTATE)
+ fn vmsd() -> Option<VMStateDescription<Self>> {
+ Some(VMSTATE)
}
}
diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs
index ad0fc5cd5dd..12f852ef703 100644
--- a/rust/qemu-api/tests/vmstate_tests.rs
+++ b/rust/qemu-api/tests/vmstate_tests.rs
@@ -16,9 +16,8 @@
},
cell::{BqlCell, Opaque},
impl_vmstate_forward,
- vmstate::{VMStateDescription, VMStateField},
+ vmstate::{VMStateDescription, VMStateDescriptionBuilder, VMStateField},
vmstate_fields, vmstate_of, vmstate_struct, vmstate_unused, vmstate_validate,
- zeroable::Zeroable,
};
const FOO_ARRAY_MAX: usize = 3;
@@ -41,22 +40,22 @@ struct FooA {
elem: i8,
}
-static VMSTATE_FOOA: VMStateDescription = VMStateDescription {
- name: c"foo_a".as_ptr(),
- version_id: 1,
- minimum_version_id: 1,
- fields: vmstate_fields! {
+static VMSTATE_FOOA: VMStateDescription<FooA> = VMStateDescriptionBuilder::<FooA>::new()
+ .name(c"foo_a")
+ .version_id(1)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
vmstate_of!(FooA, elem),
vmstate_unused!(size_of::<i64>()),
vmstate_of!(FooA, arr[0 .. num]).with_version_id(0),
vmstate_of!(FooA, arr_mul[0 .. num_mul * 16]),
- },
- ..Zeroable::ZERO
-};
+ })
+ .build();
#[test]
fn test_vmstate_uint16() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) };
// 1st VMStateField ("elem") in VMSTATE_FOOA (corresponding to VMSTATE_UINT16)
assert_eq!(
@@ -76,7 +75,8 @@ fn test_vmstate_uint16() {
#[test]
fn test_vmstate_unused() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) };
// 2nd VMStateField ("unused") in VMSTATE_FOOA (corresponding to VMSTATE_UNUSED)
assert_eq!(
@@ -96,7 +96,8 @@ fn test_vmstate_unused() {
#[test]
fn test_vmstate_varray_uint16_unsafe() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) };
// 3rd VMStateField ("arr") in VMSTATE_FOOA (corresponding to
// VMSTATE_VARRAY_UINT16_UNSAFE)
@@ -117,7 +118,8 @@ fn test_vmstate_varray_uint16_unsafe() {
#[test]
fn test_vmstate_varray_multiply() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOA.as_ref().fields, 5) };
// 4th VMStateField ("arr_mul") in VMSTATE_FOOA (corresponding to
// VMSTATE_VARRAY_MULTIPLY)
@@ -171,24 +173,25 @@ fn validate_foob(_state: &FooB, _version_id: u8) -> bool {
true
}
-static VMSTATE_FOOB: VMStateDescription = VMStateDescription {
- name: c"foo_b".as_ptr(),
- 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),
- },
- ..Zeroable::ZERO
-};
+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();
#[test]
fn test_vmstate_bool_v() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) };
// 1st VMStateField ("val") in VMSTATE_FOOB (corresponding to VMSTATE_BOOL_V)
assert_eq!(
@@ -208,7 +211,8 @@ fn test_vmstate_bool_v() {
#[test]
fn test_vmstate_uint64() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) };
// 2nd VMStateField ("wrap") in VMSTATE_FOOB (corresponding to VMSTATE_U64)
assert_eq!(
@@ -228,7 +232,8 @@ fn test_vmstate_uint64() {
#[test]
fn test_vmstate_struct_varray_uint8() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) };
// 3rd VMStateField ("arr_a") in VMSTATE_FOOB (corresponding to
// VMSTATE_STRUCT_VARRAY_UINT8)
@@ -246,13 +251,14 @@ fn test_vmstate_struct_varray_uint8() {
foo_fields[2].flags.0,
VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_VARRAY_UINT8.0
);
- assert_eq!(foo_fields[2].vmsd, &VMSTATE_FOOA);
+ assert_eq!(foo_fields[2].vmsd, VMSTATE_FOOA.as_ref());
assert!(foo_fields[2].field_exists.is_none());
}
#[test]
fn test_vmstate_struct_varray_uint32_multiply() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) };
// 4th VMStateField ("arr_a_mul") in VMSTATE_FOOB (corresponding to
// (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32)
@@ -272,13 +278,14 @@ fn test_vmstate_struct_varray_uint32_multiply() {
| VMStateFlags::VMS_VARRAY_UINT32.0
| VMStateFlags::VMS_MULTIPLY_ELEMENTS.0
);
- assert_eq!(foo_fields[3].vmsd, &VMSTATE_FOOA);
+ assert_eq!(foo_fields[3].vmsd, VMSTATE_FOOA.as_ref());
assert!(foo_fields[3].field_exists.is_none());
}
#[test]
fn test_vmstate_macro_array() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) };
// 5th VMStateField ("arr_i64") in VMSTATE_FOOB (corresponding to
// VMSTATE_ARRAY)
@@ -299,7 +306,8 @@ fn test_vmstate_macro_array() {
#[test]
fn test_vmstate_struct_varray_uint8_wrapper() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOB.as_ref().fields, 7) };
let mut foo_b: FooB = Default::default();
let foo_b_p = std::ptr::addr_of_mut!(foo_b).cast::<c_void>();
@@ -335,26 +343,28 @@ struct FooC {
arr_ptr_wrap: FooCWrapper,
}
-static VMSTATE_FOOC: VMStateDescription = VMStateDescription {
- name: c"foo_c".as_ptr(),
- version_id: 3,
- minimum_version_id: 1,
- fields: vmstate_fields! {
+unsafe impl Sync for FooC {}
+
+static VMSTATE_FOOC: VMStateDescription<FooC> = VMStateDescriptionBuilder::<FooC>::new()
+ .name(c"foo_c")
+ .version_id(3)
+ .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, arr_ptr),
vmstate_of!(FooC, arr_ptr_wrap),
- },
- ..Zeroable::ZERO
-};
+ })
+ .build();
const PTR_SIZE: usize = size_of::<*mut ()>();
#[test]
fn test_vmstate_pointer() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) };
// 1st VMStateField ("ptr") in VMSTATE_FOOC (corresponding to VMSTATE_POINTER)
assert_eq!(
@@ -377,7 +387,8 @@ fn test_vmstate_pointer() {
#[test]
fn test_vmstate_macro_array_of_pointer() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) };
// 3rd VMStateField ("arr_ptr") in VMSTATE_FOOC (corresponding to
// VMSTATE_ARRAY_OF_POINTER)
@@ -401,7 +412,8 @@ fn test_vmstate_macro_array_of_pointer() {
#[test]
fn test_vmstate_macro_array_of_pointer_wrapped() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOC.as_ref().fields, 6) };
// 4th VMStateField ("arr_ptr_wrap") in VMSTATE_FOOC (corresponding to
// VMSTATE_ARRAY_OF_POINTER)
@@ -450,21 +462,21 @@ fn validate_food_2(_state: &FooD, _version_id: u8) -> bool {
true
}
-static VMSTATE_FOOD: VMStateDescription = VMStateDescription {
- name: c"foo_d".as_ptr(),
- version_id: 3,
- minimum_version_id: 1,
- fields: vmstate_fields! {
+static VMSTATE_FOOD: VMStateDescription<FooD> = VMStateDescriptionBuilder::<FooD>::new()
+ .name(c"foo_d")
+ .version_id(3)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
vmstate_validate!(FooD, c"foo_d_0", FooD::validate_food_0),
vmstate_validate!(FooD, c"foo_d_1", FooD::validate_food_1),
vmstate_validate!(FooD, c"foo_d_2", validate_food_2),
- },
- ..Zeroable::ZERO
-};
+ })
+ .build();
#[test]
fn test_vmstate_validate() {
- let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOD.fields, 4) };
+ let foo_fields: &[VMStateField] =
+ unsafe { slice::from_raw_parts(VMSTATE_FOOD.as_ref().fields, 4) };
let mut foo_d = FooD;
let foo_d_p = std::ptr::addr_of_mut!(foo_d).cast::<c_void>();
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 4/5] rust: vmstate: use const_refs_to_static
2025-05-05 10:08 [PATCH preview 0/5] rust: allow minimum version of 1.83 Paolo Bonzini
` (2 preceding siblings ...)
2025-05-05 10:08 ` [PATCH 3/5] rust: vmstate: convert to use builder pattern Paolo Bonzini
@ 2025-05-05 10:08 ` Paolo Bonzini
2025-05-07 7:59 ` Zhao Liu
2025-05-05 10:08 ` [PATCH 5/5] rust: qdev: const_refs_to_static Paolo Bonzini
2025-05-06 8:56 ` [PATCH preview 0/5] rust: allow minimum version of 1.83 Zhao Liu
5 siblings, 1 reply; 20+ messages in thread
From: Paolo Bonzini @ 2025-05-05 10:08 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
The VMStateDescriptionBuilder already needs const_refs_to_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.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
docs/devel/rust.rst | 5 -
rust/hw/char/pl011/src/device_class.rs | 14 +-
rust/hw/timer/hpet/src/hpet.rs | 8 +-
rust/qemu-api/src/assertions.rs | 4 -
rust/qemu-api/src/vmstate.rs | 232 +++++++------------------
rust/qemu-api/tests/vmstate_tests.rs | 65 ++++---
6 files changed, 116 insertions(+), 212 deletions(-)
diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst
index ed1c765e722..12c9bde4f5c 100644
--- a/docs/devel/rust.rst
+++ b/docs/devel/rust.rst
@@ -80,11 +80,6 @@ patches are welcome:
* ``&raw`` (stable in 1.82.0).
-* referencing statics in constants (stable in 1.83.0). For now use a const
- function; this is an important limitation for QEMU's migration stream
- architecture (VMState). Right now, VMState lacks type safety because
- it is hard to place the ``VMStateField`` definitions in traits.
-
Associated const equality would be nice to have for some users of
``callbacks::FnCall``, but is still experimental. Const assertions
are used instead.
diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs
index ed72bfad25f..e4a9499bda7 100644
--- a/rust/hw/char/pl011/src/device_class.rs
+++ b/rust/hw/char/pl011/src/device_class.rs
@@ -4,9 +4,9 @@
use qemu_api::{
bindings::{qdev_prop_bool, qdev_prop_chr},
- prelude::*,
+ impl_vmstate_struct,
vmstate::{VMStateDescription, VMStateDescriptionBuilder},
- vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_unused,
+ vmstate_fields, vmstate_of, vmstate_subsections, vmstate_unused,
};
use crate::device::{PL011Registers, PL011State};
@@ -19,11 +19,12 @@
.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)
@@ -45,7 +46,8 @@
vmstate_of!(PL011Registers, read_count),
vmstate_of!(PL011Registers, read_trigger),
})
- .build();
+ .build()
+);
pub const VMSTATE_PL011: VMStateDescription<PL011State> =
VMStateDescriptionBuilder::<PL011State>::new()
@@ -55,7 +57,7 @@
.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
diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs
index 1ed1cb7dcaf..be3b9afa316 100644
--- a/rust/hw/timer/hpet/src/hpet.rs
+++ b/rust/hw/timer/hpet/src/hpet.rs
@@ -16,6 +16,7 @@
qdev_prop_uint32, qdev_prop_uint8,
},
cell::{BqlCell, BqlRefCell},
+ impl_vmstate_struct,
irq::InterruptSource,
memory::{
hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED,
@@ -27,7 +28,7 @@
sysbus::{SysBusDevice, SysBusDeviceImpl},
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
vmstate::{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;
@@ -967,7 +968,7 @@ impl ObjectImpl for HPETState {
})
.build();
-static VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> =
+const VMSTATE_HPET_TIMER: VMStateDescription<HPETTimer> =
VMStateDescriptionBuilder::<HPETTimer>::new()
.name(c"hpet_timer")
.version_id(1)
@@ -982,6 +983,7 @@ impl ObjectImpl for HPETState {
vmstate_of!(HPETTimer, qemu_timer),
})
.build();
+impl_vmstate_struct!(HPETTimer, VMSTATE_HPET_TIMER);
const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match";
@@ -998,7 +1000,7 @@ impl ObjectImpl for HPETState {
vmstate_of!(HPETState, counter),
vmstate_of!(HPETState, num_timers_save).with_version_id(2),
vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers),
- vmstate_struct!(HPETState, timers[0 .. num_timers], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0),
+ vmstate_of!(HPETState, timers[0 .. num_timers], HPETState::validate_num_timers).with_version_id(0),
})
.subsections(vmstate_subsections!(
VMSTATE_HPET_RTC_IRQ_LEVEL,
diff --git a/rust/qemu-api/src/assertions.rs b/rust/qemu-api/src/assertions.rs
index a2d38c877df..80db0de099f 100644
--- a/rust/qemu-api/src/assertions.rs
+++ b/rust/qemu-api/src/assertions.rs
@@ -95,10 +95,6 @@ fn types_must_be_equal<T, U>(_: &T)
($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
diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs
index 228d748b6b7..959d0a01fe3 100644
--- a/rust/qemu-api/src/vmstate.rs
+++ b/rust/qemu-api/src/vmstate.rs
@@ -11,10 +11,11 @@
//! migration format for a struct. This is based on the [`VMState`] trait,
//! which is defined by all migrateable types.
//!
-//! * [`impl_vmstate_forward`](crate::impl_vmstate_forward) and
+//! * [`impl_vmstate_forward`](crate::impl_vmstate_forward),
+//! * [`impl_vmstate_struct`](crate::impl_vmstate_struct),
//! [`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)
+//! the definition of the [`VMState`] trait; they are 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
@@ -24,7 +25,11 @@
//! `include/migration/vmstate.h`. These are not type-safe and only provide
//! functionality that is missing from `vmstate_of!`.
-use core::{marker::PhantomData, mem, ptr::NonNull};
+use core::{
+ marker::PhantomData,
+ mem,
+ ptr::{addr_of, NonNull},
+};
use std::ffi::{c_int, c_void, CStr};
pub use crate::bindings::{MigrationPriority, VMStateField};
@@ -33,6 +38,7 @@
callbacks::FnCall,
errno::{into_neg_errno, Errno},
prelude::*,
+ qdev,
qom::Owned,
zeroable::Zeroable,
};
@@ -74,70 +80,6 @@ const fn phantom__<T>(_: &T) -> ::core::marker::PhantomData<T> { ::core::marker:
};
}
-/// 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).
///
@@ -148,12 +90,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;
@@ -168,12 +104,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 {
@@ -201,7 +131,8 @@ pub const fn vmstate_varray_flag<T: VMState>(_: PhantomData<T>) -> VMStateFlags
///
/// In order to support other types, the trait `VMState` must be implemented
/// for them. The macros
-/// [`impl_vmstate_bitsized!`](crate::impl_vmstate_bitsized)
+/// [`impl_vmstate_bitsized!`](crate::impl_vmstate_bitsized),
+/// [`impl_vmstate_struct!`](crate::impl_vmstate_struct),
/// and [`impl_vmstate_forward!`](crate::impl_vmstate_forward) help with this.
#[macro_export]
macro_rules! vmstate_of {
@@ -215,11 +146,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,
@@ -320,8 +246,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);
}
@@ -333,7 +257,6 @@ unsafe impl $crate::vmstate::VMState for $tuple {
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
@@ -354,10 +277,6 @@ unsafe impl<$base> VMState for $type where $base: VMState $($where)* {
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
@@ -375,8 +294,8 @@ unsafe impl $crate::vmstate::VMState for $type {
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
@@ -397,6 +316,21 @@ unsafe impl VMState for $type {
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.
@@ -404,7 +338,6 @@ unsafe impl VMState for $type {
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();
}
};
@@ -423,7 +356,6 @@ unsafe impl<$base> VMState for $type where $base: VMState $($where)* {
// 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);
}
@@ -445,7 +377,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))
@@ -473,76 +405,6 @@ const fn phantom__<T>(_: &T) -> ::core::marker::PhantomData<T> {
}};
}
-// 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.
@@ -577,6 +439,30 @@ macro_rules! vmstate_validate {
};
}
+/// Helper macro to allow using a struct in [`vmstate_field!`]
+///
+/// # 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.get();
+
+ $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`].
///
@@ -624,7 +510,7 @@ unsafe impl<T: Sync> Sync for VMStateDescription<T> {}
>(
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)
}
@@ -636,7 +522,7 @@ unsafe impl<T: Sync> Sync for VMStateDescription<T> {}
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));
@@ -649,7 +535,7 @@ unsafe impl<T: Sync> Sync for VMStateDescription<T> {}
>(
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 +546,7 @@ unsafe impl<T: Sync> Sync for VMStateDescription<T> {}
>(
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)
}
@@ -668,14 +554,14 @@ unsafe impl<T: Sync> Sync for VMStateDescription<T> {}
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>()) },))
}
diff --git a/rust/qemu-api/tests/vmstate_tests.rs b/rust/qemu-api/tests/vmstate_tests.rs
index 12f852ef703..c73dba35fd3 100644
--- a/rust/qemu-api/tests/vmstate_tests.rs
+++ b/rust/qemu-api/tests/vmstate_tests.rs
@@ -15,9 +15,9 @@
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 @@ struct 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 @@ unsafe impl Sync for 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\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, PTR_SIZE);
+ 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 {
--
2.49.0
^ permalink raw reply related [flat|nested] 20+ messages in thread