* [PATCH 0/5] rust: make instance_init implementations use safe Rust
@ 2025-06-09 15:44 Paolo Bonzini
2025-06-09 15:44 ` [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection Paolo Bonzini
` (4 more replies)
0 siblings, 5 replies; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-09 15:44 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
This is a step towards safe bindings to instance_init: making the
implementation safe, though ensuring that *all* fields as initialized
is still up to the implementor. This helps making it clear what we
want from crates like pinned-init (the Linux one) or its inspiration
pin-init.
This series has two concoctions that are a bit more advanced.
The one in the first patch is probably going to be temporary
once instance_init takes its final form, but it's very useful while
we're stuck with piece-by-piece initialization.
The fourth patch instead is an adaptation of the technique in
GhostCell (https://plv.mpi-sws.org/rustbelt/ghostcell/), which allows
to isolate an object within the invocation of a function. This one
probably will stay, together with the ParentInit struct that (in one
shape or another) acts as the "proof" that instance_init has been
called on the parent classes.
Paolo
Paolo Bonzini (5):
rust: qemu_api: introduce MaybeUninit field projection
rust: hpet: fully initialize object after instance_init
rust: qom: introduce ParentInit
rust: qom: make ParentInit lifetime-invariant
rust: qom: change instance_init to take a ParentInit<>
rust/hw/char/pl011/src/device.rs | 34 +++---
rust/hw/timer/hpet/src/device.rs | 56 +++++-----
rust/qemu-api/meson.build | 1 +
rust/qemu-api/src/lib.rs | 1 +
rust/qemu-api/src/memory.rs | 12 +--
rust/qemu-api/src/qdev.rs | 51 +++++----
rust/qemu-api/src/qom.rs | 175 ++++++++++++++++++++++++++++++-
rust/qemu-api/src/uninit.rs | 85 +++++++++++++++
8 files changed, 341 insertions(+), 74 deletions(-)
create mode 100644 rust/qemu-api/src/uninit.rs
--
2.49.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection
2025-06-09 15:44 [PATCH 0/5] rust: make instance_init implementations use safe Rust Paolo Bonzini
@ 2025-06-09 15:44 ` Paolo Bonzini
2025-06-11 11:09 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 2/5] rust: hpet: fully initialize object after instance_init Paolo Bonzini
` (3 subsequent siblings)
4 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-09 15:44 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
Add a macro that makes it possible to convert a MaybeUninit<> into
another MaybeUninit<> for a single field within it. Furthermore, it is
possible to use the resulting MaybeUninitField<> in APIs that take the
parent object, such as memory_region_init_io().
This allows removing some of the undefined behavior from instance_init()
functions, though this may not be the definitive implementation.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/qemu-api/meson.build | 1 +
rust/qemu-api/src/lib.rs | 1 +
rust/qemu-api/src/uninit.rs | 85 +++++++++++++++++++++++++++++++++++++
3 files changed, 87 insertions(+)
create mode 100644 rust/qemu-api/src/uninit.rs
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index cac8595a148..33653b4a28e 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -28,6 +28,7 @@ _qemu_api_rs = static_library(
'src/qom.rs',
'src/sysbus.rs',
'src/timer.rs',
+ 'src/uninit.rs',
'src/vmstate.rs',
'src/zeroable.rs',
],
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index 93902fc94bc..c78198f0f41 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -27,6 +27,7 @@
pub mod qom;
pub mod sysbus;
pub mod timer;
+pub mod uninit;
pub mod vmstate;
pub mod zeroable;
diff --git a/rust/qemu-api/src/uninit.rs b/rust/qemu-api/src/uninit.rs
new file mode 100644
index 00000000000..a7bed6935b1
--- /dev/null
+++ b/rust/qemu-api/src/uninit.rs
@@ -0,0 +1,85 @@
+//! Access fields of a [`MaybeUninit`](std::mem::MaybeUninit)
+
+use std::{
+ mem::MaybeUninit,
+ ops::{Deref, DerefMut},
+};
+
+pub struct MaybeUninitField<'a, T, U> {
+ parent: &'a mut MaybeUninit<T>,
+ child: *mut U,
+}
+
+impl<'a, T, U> MaybeUninitField<'a, T, U> {
+ #[doc(hidden)]
+ pub fn new(parent: &'a mut MaybeUninit<T>, child: *mut U) -> Self {
+ MaybeUninitField { parent, child }
+ }
+
+ /// Return a constant pointer to the containing object of the field.
+ ///
+ /// Because the `MaybeUninitField` remembers the containing object,
+ /// it is possible to use it in foreign APIs that initialize the
+ /// child.
+ pub fn parent(f: &Self) -> *const T {
+ f.parent.as_ptr()
+ }
+
+ /// Return a mutable pointer to the containing object.
+ ///
+ /// Because the `MaybeUninitField` remembers the containing object,
+ /// it is possible to use it in foreign APIs that initialize the
+ /// child.
+ pub fn parent_mut(f: &mut Self) -> *mut T {
+ f.parent.as_mut_ptr()
+ }
+}
+
+impl<'a, T, U> Deref for MaybeUninitField<'a, T, U> {
+ type Target = MaybeUninit<U>;
+
+ fn deref(&self) -> &MaybeUninit<U> {
+ // SAFETY: self.child was obtained by dereferencing a valid mutable
+ // reference; the content of the memory may be invalid or uninitialized
+ // but MaybeUninit<_> makes no assumption on it
+ unsafe { &*(self.child.cast()) }
+ }
+}
+
+impl<'a, T, U> DerefMut for MaybeUninitField<'a, T, U> {
+ fn deref_mut(&mut self) -> &mut MaybeUninit<U> {
+ // SAFETY: self.child was obtained by dereferencing a valid mutable
+ // reference; the content of the memory may be invalid or uninitialized
+ // but MaybeUninit<_> makes no assumption on it
+ unsafe { &mut *(self.child.cast()) }
+ }
+}
+
+/// ```
+/// #[derive(Debug)]
+/// struct S {
+/// x: u32,
+/// y: u32,
+/// }
+///
+/// # use std::mem::MaybeUninit;
+/// # use qemu_api::{assert_match, uninit_field_mut};
+///
+/// let mut s: MaybeUninit<S> = MaybeUninit::zeroed();
+/// uninit_field_mut!(s, x).write(5);
+/// let s = unsafe { s.assume_init() };
+/// assert_match!(s, S { x: 5, y: 0 });
+/// ```
+#[macro_export]
+macro_rules! uninit_field_mut {
+ ($container:expr, $($field:tt)+) => {{
+ let container__: &mut ::std::mem::MaybeUninit<_> = &mut $container;
+ let container_ptr__ = container__.as_mut_ptr();
+
+ // SAFETY: the container is not used directly, only through a MaybeUninit<>,
+ // so the safety is delegated to the caller and to final invocation of
+ // assume_init()
+ let target__ = unsafe { std::ptr::addr_of_mut!((*container_ptr__).$($field)+) };
+ $crate::uninit::MaybeUninitField::new(container__, target__)
+ }};
+}
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/5] rust: hpet: fully initialize object after instance_init
2025-06-09 15:44 [PATCH 0/5] rust: make instance_init implementations use safe Rust Paolo Bonzini
2025-06-09 15:44 ` [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection Paolo Bonzini
@ 2025-06-09 15:44 ` Paolo Bonzini
2025-06-11 9:43 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 3/5] rust: qom: introduce ParentInit Paolo Bonzini
` (2 subsequent siblings)
4 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-09 15:44 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
The array of BqlRefCell<HPETTimer> is not initialized yet at the
end of instance_init. In particular, the "state" field is NonNull
and therefore it is invalid to have it as zero bytes.
Note that MaybeUninit is necessary because assigning to self.timers[index]
would trigger Drop of the old value.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/hw/timer/hpet/src/device.rs | 42 +++++++++++++++++++-------------
1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs
index 735b2fbef79..340ca1d355d 100644
--- a/rust/hw/timer/hpet/src/device.rs
+++ b/rust/hw/timer/hpet/src/device.rs
@@ -4,6 +4,7 @@
use std::{
ffi::{c_int, c_void, CStr},
+ mem::MaybeUninit,
pin::Pin,
ptr::{addr_of_mut, null_mut, NonNull},
slice::from_ref,
@@ -25,6 +26,7 @@
qom_isa,
sysbus::{SysBusDevice, SysBusDeviceImpl},
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
+ uninit_field_mut,
vmstate::VMStateDescription,
vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_validate,
zeroable::Zeroable,
@@ -212,13 +214,13 @@ pub struct HPETTimer {
}
impl HPETTimer {
- fn init(&mut self, index: u8, state: &HPETState) {
- *self = HPETTimer {
+ fn new(index: u8, state: *const HPETState) -> HPETTimer {
+ HPETTimer {
index,
// SAFETY: the HPETTimer will only be used after the timer
// is initialized below.
qemu_timer: unsafe { Timer::new() },
- state: NonNull::new((state as *const HPETState).cast_mut()).unwrap(),
+ state: NonNull::new(state.cast_mut()).unwrap(),
config: 0,
cmp: 0,
fsb: 0,
@@ -226,19 +228,15 @@ fn init(&mut self, index: u8, state: &HPETState) {
period: 0,
wrap_flag: 0,
last: 0,
- };
+ }
+ }
+ fn init_timer_with_cell(cell: &BqlRefCell<Self>) {
+ let mut timer = cell.borrow_mut();
// SAFETY: HPETTimer is only used as part of HPETState, which is
// always pinned.
- let qemu_timer = unsafe { Pin::new_unchecked(&mut self.qemu_timer) };
- qemu_timer.init_full(
- None,
- CLOCK_VIRTUAL,
- Timer::NS,
- 0,
- timer_handler,
- &state.timers[self.index as usize],
- )
+ let qemu_timer = unsafe { Pin::new_unchecked(&mut timer.qemu_timer) };
+ qemu_timer.init_full(None, CLOCK_VIRTUAL, Timer::NS, 0, timer_handler, cell);
}
fn get_state(&self) -> &HPETState {
@@ -607,9 +605,18 @@ fn handle_legacy_irq(&self, irq: u32, level: u32) {
}
}
- fn init_timer(&self) {
- for (index, timer) in self.timers.iter().enumerate() {
- timer.borrow_mut().init(index.try_into().unwrap(), self);
+ fn init_timers(this: &mut MaybeUninit<Self>) {
+ let state = this.as_ptr();
+ for index in 0..HPET_MAX_TIMERS {
+ let mut timer = uninit_field_mut!(*this, timers[index]);
+
+ // Initialize in two steps, to avoid calling Timer::init_full on a
+ // temporary that can be moved.
+ let timer = timer.write(BqlRefCell::new(HPETTimer::new(
+ index.try_into().unwrap(),
+ state,
+ )));
+ HPETTimer::init_timer_with_cell(timer);
}
}
@@ -710,6 +717,8 @@ unsafe fn init(&mut self) {
"hpet",
HPET_REG_SPACE_LEN,
);
+
+ Self::init_timers(unsafe { &mut *((self as *mut Self).cast::<MaybeUninit<Self>>()) });
}
fn post_init(&self) {
@@ -731,7 +740,6 @@ fn realize(&self) -> qemu_api::Result<()> {
self.hpet_id.set(HPETFwConfig::assign_hpet_id()?);
- self.init_timer();
// 64-bit General Capabilities and ID Register; LegacyReplacementRoute.
self.capability.set(
HPET_CAP_REV_ID_VALUE << HPET_CAP_REV_ID_SHIFT |
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/5] rust: qom: introduce ParentInit
2025-06-09 15:44 [PATCH 0/5] rust: make instance_init implementations use safe Rust Paolo Bonzini
2025-06-09 15:44 ` [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection Paolo Bonzini
2025-06-09 15:44 ` [PATCH 2/5] rust: hpet: fully initialize object after instance_init Paolo Bonzini
@ 2025-06-09 15:44 ` Paolo Bonzini
2025-06-11 15:25 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant Paolo Bonzini
2025-06-09 15:44 ` [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<> Paolo Bonzini
4 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-09 15:44 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
This is a smart pointer for MaybeUninit; it can be upcasted to the
already-initialized parent classes, or dereferenced to a MaybeUninit
for the class that is being initialized.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/qemu-api/src/qom.rs | 86 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 85 insertions(+), 1 deletion(-)
diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs
index 14f98fee60a..21c271cd2f9 100644
--- a/rust/qemu-api/src/qom.rs
+++ b/rust/qemu-api/src/qom.rs
@@ -95,7 +95,7 @@
use std::{
ffi::{c_void, CStr},
fmt,
- mem::ManuallyDrop,
+ mem::{ManuallyDrop, MaybeUninit},
ops::{Deref, DerefMut},
ptr::NonNull,
};
@@ -206,6 +206,90 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
}
}
+/// This struct knows that the superclasses of the object have already been
+/// initialized.
+pub struct ParentInit<'a, T>(&'a mut MaybeUninit<T>);
+
+impl<'a, T> ParentInit<'a, T> {
+ #[inline]
+ pub fn with(obj: &'a mut MaybeUninit<T>, f: impl FnOnce(ParentInit<'a, T>)) {
+ let parent_init = ParentInit(obj);
+ f(parent_init)
+ }
+}
+
+impl<T: ObjectType> ParentInit<'_, T> {
+ /// Return the receiver as a mutable raw pointer to Object.
+ pub fn as_object_mut_ptr(&self) -> *mut bindings::Object {
+ self.as_object_ptr().cast_mut()
+ }
+
+ /// Return the receiver as a const raw pointer to Object.
+ /// This is preferrable to `as_object_mut_ptr()` if a C
+ /// function only needs a `const Object *`.
+ pub fn as_object_ptr(&self) -> *const bindings::Object {
+ self.0.as_ptr().cast()
+ }
+}
+
+impl<'a, T: ObjectImpl> ParentInit<'a, T> {
+ /// Convert from a derived type to one of its parent types, which
+ /// have already been initialized.
+ ///
+ /// # Safety
+ ///
+ /// Structurally this is always a safe operation; the [`IsA`] trait
+ /// provides static verification trait that `Self` dereferences to `U` or
+ /// a child of `U`, and only parent types of `T` are allowed.
+ ///
+ /// However, while the fields of the resulting reference are initialized,
+ /// calls might use uninitialized fields of the subclass. It is your
+ /// responsibility to avoid this.
+ pub unsafe fn upcast<U: ObjectType>(&self) -> &'a U
+ where
+ T::ParentType: IsA<U>,
+ {
+ // SAFETY: soundness is declared via IsA<U>, which is an unsafe trait;
+ // the parent has been initialized before `instance_init `is called
+ unsafe { &*(self.0.as_ptr().cast::<U>()) }
+ }
+
+ /// Convert from a derived type to one of its parent types, which
+ /// have already been initialized.
+ ///
+ /// # Safety
+ ///
+ /// Structurally this is always a safe operation; the [`IsA`] trait
+ /// provides static verification trait that `Self` dereferences to `U` or
+ /// a child of `U`, and only parent types of `T` are allowed.
+ ///
+ /// However, while the fields of the resulting reference are initialized,
+ /// calls might use uninitialized fields of the subclass. It is your
+ /// responsibility to avoid this.
+ pub unsafe fn upcast_mut<U: ObjectType>(&mut self) -> &'a mut U
+ where
+ T::ParentType: IsA<U>,
+ {
+ // SAFETY: soundness is declared via IsA<U>, which is an unsafe trait;
+ // the parent has been initialized before `instance_init `is called
+ unsafe { &mut *(self.0.as_mut_ptr().cast::<U>()) }
+ }
+}
+
+impl<T> Deref for ParentInit<'_, T> {
+ type Target = MaybeUninit<T>;
+
+ fn deref(&self) -> &Self::Target {
+ self.0
+ }
+}
+
+impl<T> DerefMut for ParentInit<'_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0
+ }
+}
+
unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut bindings::Object) {
let mut state = NonNull::new(obj).unwrap().cast::<T>();
// SAFETY: obj is an instance of T, since rust_instance_init<T>
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
2025-06-09 15:44 [PATCH 0/5] rust: make instance_init implementations use safe Rust Paolo Bonzini
` (2 preceding siblings ...)
2025-06-09 15:44 ` [PATCH 3/5] rust: qom: introduce ParentInit Paolo Bonzini
@ 2025-06-09 15:44 ` Paolo Bonzini
2025-06-12 9:21 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<> Paolo Bonzini
4 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-09 15:44 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
This is the trick that allows the parent-field initializer to be used
only for the object that it's meant to be initialized. This way,
the owner of a MemoryRegion must be the object that embeds it.
More information is in the comments; it's best explained with a simplified
example.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/qemu-api/src/qom.rs | 88 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 84 insertions(+), 4 deletions(-)
diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs
index 21c271cd2f9..1481ef20f0c 100644
--- a/rust/qemu-api/src/qom.rs
+++ b/rust/qemu-api/src/qom.rs
@@ -95,6 +95,7 @@
use std::{
ffi::{c_void, CStr},
fmt,
+ marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
ops::{Deref, DerefMut},
ptr::NonNull,
@@ -208,12 +209,91 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
/// This struct knows that the superclasses of the object have already been
/// initialized.
-pub struct ParentInit<'a, T>(&'a mut MaybeUninit<T>);
+///
+/// The declaration of `ParentInit` is.. *"a kind of magic"*. It uses a
+/// technique that is found in several crates, the main ones probably being
+/// `ghost-cell` (in fact it was introduced by the [`GhostCell` paper](https://plv.mpi-sws.org/rustbelt/ghostcell/))
+/// and `generativity`.
+///
+/// The `PhantomData` makes the `ParentInit` type *invariant* with respect to
+/// the lifetime argument `'init`. This, together with the `for<'...>` in
+/// `[ParentInit::with]`, block any attempt of the compiler to be creative when
+/// operating on types of type `ParentInit` and to extend their lifetimes. In
+/// particular, it ensures that the `ParentInit` cannot be made to outlive the
+/// `rust_instance_init()` function that creates it, and therefore that the
+/// `&'init T` reference is valid.
+///
+/// This implementation of the same concept, without the QOM baggage, can help
+/// understanding the effect:
+///
+/// ```
+/// use std::marker::PhantomData;
+///
+/// #[derive(PartialEq, Eq)]
+/// pub struct Jail<'closure, T: Copy>(&'closure T, PhantomData<fn(&'closure ()) -> &'closure ()>);
+///
+/// impl<'closure, T: Copy> Jail<'closure, T> {
+/// fn get(&self) -> T {
+/// *self.0
+/// }
+///
+/// #[inline]
+/// fn with<U>(v: T, f: impl for<'id> FnOnce(Jail<'id, T>) -> U) -> U {
+/// let parent_init = Jail(&v, PhantomData);
+/// f(parent_init)
+/// }
+/// }
+/// ```
+///
+/// It's impossible to escape the `Jail`; `token1` cannot be moved out of the
+/// closure:
+///
+/// ```ignore
+/// let x = 42;
+/// let escape = Jail::with(&x, |token1| {
+/// println!("{}", token1.get());
+/// token1
+/// });
+/// // fails to compile:
+/// println!("{}", escape.get());
+/// ```
+///
+/// Likewise, in the QOM case the `ParentInit` cannot be moved out of
+/// `instance_init()`. Without this trick it would be possible to stash a
+/// `ParentInit` and use it later to access uninitialized memory.
+///
+/// Here is another example, showing how separately-created "identities" stay
+/// isolated:
+///
+/// ```ignore
+/// impl<'closure, T: Copy> Clone for Jail<'closure, T> {
+/// fn clone(&self) -> Jail<'closure, T> {
+/// Jail(self.0, PhantomData)
+/// }
+/// }
+///
+/// fn main() {
+/// Jail::with(42, |token1| {
+/// // this works and returns true: the clone has the same "identity"
+/// println!("{}", token1 == token1.clone());
+/// Jail::with(42, |token2| {
+/// // here the outer token remains accessible...
+/// println!("{}", token1.get());
+/// // ... but the two are separate: this fails to compile:
+/// println!("{}", token1 == token2);
+/// });
+/// });
+/// }
+/// ```
+pub struct ParentInit<'init, T>(
+ &'init mut MaybeUninit<T>,
+ PhantomData<fn(&'init ()) -> &'init ()>,
+);
-impl<'a, T> ParentInit<'a, T> {
+impl<'init, T> ParentInit<'init, T> {
#[inline]
- pub fn with(obj: &'a mut MaybeUninit<T>, f: impl FnOnce(ParentInit<'a, T>)) {
- let parent_init = ParentInit(obj);
+ pub fn with(obj: &'init mut MaybeUninit<T>, f: impl for<'id> FnOnce(ParentInit<'id, T>)) {
+ let parent_init = ParentInit(obj, PhantomData);
f(parent_init)
}
}
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<>
2025-06-09 15:44 [PATCH 0/5] rust: make instance_init implementations use safe Rust Paolo Bonzini
` (3 preceding siblings ...)
2025-06-09 15:44 ` [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant Paolo Bonzini
@ 2025-06-09 15:44 ` Paolo Bonzini
2025-06-12 15:25 ` Zhao Liu
4 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-09 15:44 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-rust
This removes undefined behavior associated to writing to uninitialized
fields, and makes it possible to remove "unsafe" from the instance_init
implementation.
However, the init function itself is still unsafe, because it must promise
(as a sort as MaybeUninit::assume_init) that all fields have been
initialized.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/hw/char/pl011/src/device.rs | 34 ++++++++++-----------
rust/hw/timer/hpet/src/device.rs | 16 ++++------
rust/qemu-api/src/memory.rs | 12 ++++----
rust/qemu-api/src/qdev.rs | 51 ++++++++++++++++++++------------
rust/qemu-api/src/qom.rs | 9 ++++--
5 files changed, 65 insertions(+), 57 deletions(-)
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index be8387f6f2d..2d416cd9a3c 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, mem::size_of};
use qemu_api::{
chardev::{CharBackend, Chardev, Event},
@@ -11,9 +11,10 @@
memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder},
prelude::*,
qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl},
- qom::{ObjectImpl, Owned, ParentField},
+ qom::{ObjectImpl, Owned, ParentField, ParentInit},
static_assert,
sysbus::{SysBusDevice, SysBusDeviceImpl},
+ uninit_field_mut,
vmstate::VMStateDescription,
};
@@ -163,7 +164,7 @@ impl PL011Impl for PL011State {
impl ObjectImpl for PL011State {
type ParentType = SysBusDevice;
- const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init);
+ const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
}
@@ -488,7 +489,7 @@ impl PL011State {
/// `PL011State` type. It must not be called more than once on the same
/// location/instance. All its fields are expected to hold uninitialized
/// values with the sole exception of `parent_obj`.
- unsafe fn init(&mut self) {
+ unsafe fn init(mut this: ParentInit<Self>) {
static PL011_OPS: MemoryRegionOps<PL011State> = MemoryRegionOpsBuilder::<PL011State>::new()
.read(&PL011State::read)
.write(&PL011State::write)
@@ -496,28 +497,23 @@ unsafe fn init(&mut self) {
.impl_sizes(4, 4)
.build();
- // SAFETY:
- //
- // self and self.iomem are guaranteed to be valid at this point since callers
- // must make sure the `self` reference is valid.
+ // SAFETY: this and this.iomem are guaranteed to be valid at this point
MemoryRegion::init_io(
- unsafe { &mut *addr_of_mut!(self.iomem) },
- addr_of_mut!(*self),
+ &mut uninit_field_mut!(*this, iomem),
&PL011_OPS,
"pl011",
0x1000,
);
- self.regs = Default::default();
+ uninit_field_mut!(*this, regs).write(Default::default());
- // SAFETY:
- //
- // self.clock is not initialized at this point; but since `Owned<_>` is
- // not Drop, we can overwrite the undefined value without side effects;
- // it's not sound but, because for all PL011State instances are created
- // by QOM code which calls this function to initialize the fields, at
- // leastno code is able to access an invalid self.clock value.
- self.clock = self.init_clock_in("clk", &Self::clock_update, ClockEvent::ClockUpdate);
+ let clock = DeviceState::init_clock_in(
+ &mut this,
+ "clk",
+ &Self::clock_update,
+ ClockEvent::ClockUpdate,
+ );
+ uninit_field_mut!(*this, clock).write(clock);
}
const fn clock_update(&self, _event: ClockEvent) {
diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs
index 340ca1d355d..a281927781e 100644
--- a/rust/hw/timer/hpet/src/device.rs
+++ b/rust/hw/timer/hpet/src/device.rs
@@ -21,8 +21,8 @@
hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED,
},
prelude::*,
- qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl},
- qom::{ObjectImpl, ObjectType, ParentField},
+ qdev::{DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl},
+ qom::{ObjectImpl, ObjectType, ParentField, ParentInit},
qom_isa,
sysbus::{SysBusDevice, SysBusDeviceImpl},
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
@@ -697,7 +697,7 @@ fn set_counter_reg(&self, shift: u32, len: u32, val: u64) {
.set(self.counter.get().deposit(shift, len, val));
}
- unsafe fn init(&mut self) {
+ unsafe fn init(mut this: ParentInit<Self>) {
static HPET_RAM_OPS: MemoryRegionOps<HPETState> =
MemoryRegionOpsBuilder::<HPETState>::new()
.read(&HPETState::read)
@@ -707,18 +707,14 @@ unsafe fn init(&mut self) {
.impl_sizes(4, 8)
.build();
- // SAFETY:
- // self and self.iomem are guaranteed to be valid at this point since callers
- // must make sure the `self` reference is valid.
MemoryRegion::init_io(
- unsafe { &mut *addr_of_mut!(self.iomem) },
- addr_of_mut!(*self),
+ &mut uninit_field_mut!(*this, iomem),
&HPET_RAM_OPS,
"hpet",
HPET_REG_SPACE_LEN,
);
- Self::init_timers(unsafe { &mut *((self as *mut Self).cast::<MaybeUninit<Self>>()) });
+ Self::init_timers(&mut this);
}
fn post_init(&self) {
@@ -900,7 +896,7 @@ unsafe impl ObjectType for HPETState {
impl ObjectImpl for HPETState {
type ParentType = SysBusDevice;
- const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init);
+ const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
}
diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs
index 9ef2694bd62..e40fad6cf19 100644
--- a/rust/qemu-api/src/memory.rs
+++ b/rust/qemu-api/src/memory.rs
@@ -16,6 +16,7 @@
callbacks::FnCall,
cell::Opaque,
prelude::*,
+ uninit::MaybeUninitField,
zeroable::Zeroable,
};
@@ -147,7 +148,7 @@ impl MemoryRegion {
#[inline(always)]
unsafe fn do_init_io(
slot: *mut bindings::MemoryRegion,
- owner: *mut Object,
+ owner: *mut bindings::Object,
ops: &'static bindings::MemoryRegionOps,
name: &'static str,
size: u64,
@@ -156,7 +157,7 @@ unsafe fn do_init_io(
let cstr = CString::new(name).unwrap();
memory_region_init_io(
slot,
- owner.cast::<bindings::Object>(),
+ owner,
ops,
owner.cast::<c_void>(),
cstr.as_ptr(),
@@ -166,16 +167,15 @@ unsafe fn do_init_io(
}
pub fn init_io<T: IsA<Object>>(
- &mut self,
- owner: *mut T,
+ this: &mut MaybeUninitField<'_, T, Self>,
ops: &'static MemoryRegionOps<T>,
name: &'static str,
size: u64,
) {
unsafe {
Self::do_init_io(
- self.0.as_mut_ptr(),
- owner.cast::<Object>(),
+ this.as_mut_ptr().cast(),
+ MaybeUninitField::parent_mut(this).cast(),
&ops.0,
name,
size,
diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs
index 0610959f467..a2a95a4938f 100644
--- a/rust/qemu-api/src/qdev.rs
+++ b/rust/qemu-api/src/qdev.rs
@@ -19,7 +19,7 @@
error::{Error, Result},
irq::InterruptSource,
prelude::*,
- qom::{ObjectClass, ObjectImpl, Owned},
+ qom::{ObjectClass, ObjectImpl, Owned, ParentInit},
vmstate::VMStateDescription,
};
@@ -247,15 +247,9 @@ unsafe impl ObjectType for DeviceState {
}
qom_isa!(DeviceState: Object);
-/// Trait for methods exposed by the [`DeviceState`] class. The methods can be
-/// called on all objects that have the trait `IsA<DeviceState>`.
-///
-/// The trait should only be used through the blanket implementation,
-/// which guarantees safety via `IsA`.
-pub trait DeviceMethods: ObjectDeref
-where
- Self::Target: IsA<DeviceState>,
-{
+/// Initialization methods take a [`ParentInit`] and can be called as
+/// associated functions.
+impl DeviceState {
/// Add an input clock named `name`. Invoke the callback with
/// `self` as the first parameter for the events that are requested.
///
@@ -266,12 +260,15 @@ pub trait DeviceMethods: ObjectDeref
/// which Rust code has a reference to a child object) it would be
/// possible for this function to return a `&Clock` too.
#[inline]
- fn init_clock_in<F: for<'a> FnCall<(&'a Self::Target, ClockEvent)>>(
- &self,
+ pub fn init_clock_in<T: DeviceImpl, F: for<'a> FnCall<(&'a T, ClockEvent)>>(
+ this: &mut ParentInit<T>,
name: &str,
_cb: &F,
events: ClockEvent,
- ) -> Owned<Clock> {
+ ) -> Owned<Clock>
+ where
+ T::ParentType: IsA<DeviceState>,
+ {
fn do_init_clock_in(
dev: &DeviceState,
name: &str,
@@ -287,10 +284,10 @@ fn do_init_clock_in(
unsafe {
let cstr = CString::new(name).unwrap();
let clk = bindings::qdev_init_clock_in(
- dev.as_mut_ptr(),
+ dev.0.as_mut_ptr(),
cstr.as_ptr(),
cb,
- dev.as_void_ptr(),
+ dev.0.as_void_ptr(),
events.0,
);
@@ -307,12 +304,12 @@ fn do_init_clock_in(
// SAFETY: the opaque is "this", which is indeed a pointer to T
F::call((unsafe { &*(opaque.cast::<T>()) }, event))
}
- Some(rust_clock_cb::<Self::Target, F>)
+ Some(rust_clock_cb::<T, F>)
} else {
None
};
- do_init_clock_in(self.upcast(), name, cb, events)
+ do_init_clock_in(unsafe { this.upcast_mut() }, name, cb, events)
}
/// Add an output clock named `name`.
@@ -324,16 +321,32 @@ fn do_init_clock_in(
/// which Rust code has a reference to a child object) it would be
/// possible for this function to return a `&Clock` too.
#[inline]
- fn init_clock_out(&self, name: &str) -> Owned<Clock> {
+ pub fn init_clock_out<T: DeviceImpl>(this: &mut ParentInit<T>, name: &str) -> Owned<Clock>
+ where
+ T::ParentType: IsA<DeviceState>,
+ {
unsafe {
let cstr = CString::new(name).unwrap();
- let clk = bindings::qdev_init_clock_out(self.upcast().as_mut_ptr(), cstr.as_ptr());
+ // TODO: introduce a type guaranteeing that the DeviceState part has been
+ // initialized
+ let dev: &mut DeviceState = this.upcast_mut();
+ let clk = bindings::qdev_init_clock_out(dev.0.as_mut_ptr(), cstr.as_ptr());
let clk: &Clock = Clock::from_raw(clk);
Owned::from(clk)
}
}
+}
+/// Trait for methods exposed by the [`DeviceState`] class. The methods can be
+/// called on all objects that have the trait `IsA<DeviceState>`.
+///
+/// The trait should only be used through the blanket implementation,
+/// which guarantees safety via `IsA`.
+pub trait DeviceMethods: ObjectDeref
+where
+ Self::Target: IsA<DeviceState>,
+{
fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) {
assert!(bql_locked());
let c_propname = CString::new(propname).unwrap();
diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs
index 1481ef20f0c..80a50108e65 100644
--- a/rust/qemu-api/src/qom.rs
+++ b/rust/qemu-api/src/qom.rs
@@ -371,12 +371,15 @@ fn deref_mut(&mut self) -> &mut Self::Target {
}
unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut bindings::Object) {
- let mut state = NonNull::new(obj).unwrap().cast::<T>();
+ let mut state = NonNull::new(obj).unwrap().cast::<MaybeUninit<T>>();
+
// SAFETY: obj is an instance of T, since rust_instance_init<T>
// is called from QOM core as the instance_init function
// for class T
unsafe {
- T::INSTANCE_INIT.unwrap()(state.as_mut());
+ ParentInit::with(state.as_mut(), |parent_init| {
+ T::INSTANCE_INIT.unwrap()(parent_init);
+ });
}
}
@@ -643,7 +646,7 @@ pub trait ObjectImpl: ObjectType + IsA<Object> {
///
/// FIXME: The argument is not really a valid reference. `&mut
/// MaybeUninit<Self>` would be a better description.
- const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = None;
+ const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = None;
/// Function that is called to finish initialization of an object, once
/// `INSTANCE_INIT` functions have been called.
--
2.49.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 2/5] rust: hpet: fully initialize object after instance_init
2025-06-09 15:44 ` [PATCH 2/5] rust: hpet: fully initialize object after instance_init Paolo Bonzini
@ 2025-06-11 9:43 ` Zhao Liu
0 siblings, 0 replies; 15+ messages in thread
From: Zhao Liu @ 2025-06-11 9:43 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
On Mon, Jun 09, 2025 at 05:44:20PM +0200, Paolo Bonzini wrote:
> Date: Mon, 9 Jun 2025 17:44:20 +0200
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 2/5] rust: hpet: fully initialize object after instance_init
> X-Mailer: git-send-email 2.49.0
>
> The array of BqlRefCell<HPETTimer> is not initialized yet at the
> end of instance_init. In particular, the "state" field is NonNull
> and therefore it is invalid to have it as zero bytes.
>
> Note that MaybeUninit is necessary because assigning to self.timers[index]
> would trigger Drop of the old value.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> rust/hw/timer/hpet/src/device.rs | 42 +++++++++++++++++++-------------
> 1 file changed, 25 insertions(+), 17 deletions(-)
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection
2025-06-09 15:44 ` [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection Paolo Bonzini
@ 2025-06-11 11:09 ` Zhao Liu
0 siblings, 0 replies; 15+ messages in thread
From: Zhao Liu @ 2025-06-11 11:09 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
On Mon, Jun 09, 2025 at 05:44:19PM +0200, Paolo Bonzini wrote:
> Date: Mon, 9 Jun 2025 17:44:19 +0200
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection
> X-Mailer: git-send-email 2.49.0
>
> Add a macro that makes it possible to convert a MaybeUninit<> into
> another MaybeUninit<> for a single field within it. Furthermore, it is
> possible to use the resulting MaybeUninitField<> in APIs that take the
> parent object, such as memory_region_init_io().
>
> This allows removing some of the undefined behavior from instance_init()
> functions, though this may not be the definitive implementation.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> rust/qemu-api/meson.build | 1 +
> rust/qemu-api/src/lib.rs | 1 +
> rust/qemu-api/src/uninit.rs | 85 +++++++++++++++++++++++++++++++++++++
> 3 files changed, 87 insertions(+)
> create mode 100644 rust/qemu-api/src/uninit.rs
...
> +impl<'a, T, U> Deref for MaybeUninitField<'a, T, U> {
> + type Target = MaybeUninit<U>;
> +
> + fn deref(&self) -> &MaybeUninit<U> {
> + // SAFETY: self.child was obtained by dereferencing a valid mutable
> + // reference; the content of the memory may be invalid or uninitialized
> + // but MaybeUninit<_> makes no assumption on it
> + unsafe { &*(self.child.cast()) }
> + }
> +}
> +
> +impl<'a, T, U> DerefMut for MaybeUninitField<'a, T, U> {
> + fn deref_mut(&mut self) -> &mut MaybeUninit<U> {
> + // SAFETY: self.child was obtained by dereferencing a valid mutable
> + // reference; the content of the memory may be invalid or uninitialized
> + // but MaybeUninit<_> makes no assumption on it
> + unsafe { &mut *(self.child.cast()) }
> + }
> +}
Nice trick.
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 3/5] rust: qom: introduce ParentInit
2025-06-09 15:44 ` [PATCH 3/5] rust: qom: introduce ParentInit Paolo Bonzini
@ 2025-06-11 15:25 ` Zhao Liu
0 siblings, 0 replies; 15+ messages in thread
From: Zhao Liu @ 2025-06-11 15:25 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
On Mon, Jun 09, 2025 at 05:44:21PM +0200, Paolo Bonzini wrote:
> Date: Mon, 9 Jun 2025 17:44:21 +0200
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 3/5] rust: qom: introduce ParentInit
> X-Mailer: git-send-email 2.49.0
>
> This is a smart pointer for MaybeUninit; it can be upcasted to the
> already-initialized parent classes, or dereferenced to a MaybeUninit
> for the class that is being initialized.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> rust/qemu-api/src/qom.rs | 86 +++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 85 insertions(+), 1 deletion(-)
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
2025-06-12 9:21 ` Zhao Liu
@ 2025-06-12 9:07 ` Paolo Bonzini
2025-06-12 9:41 ` Zhao Liu
2025-06-12 10:31 ` Zhao Liu
1 sibling, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-12 9:07 UTC (permalink / raw)
To: Zhao Liu; +Cc: qemu-devel, qemu-rust
On Thu, Jun 12, 2025 at 11:00 AM Zhao Liu <zhao1.liu@intel.com> wrote:
> > +/// It's impossible to escape the `Jail`; `token1` cannot be moved out of the
> > +/// closure:
> > +///
> > +/// ```ignore
> > +/// let x = 42;
> > +/// let escape = Jail::with(&x, |token1| {
> > +/// println!("{}", token1.get());
> > +/// token1
>
> This line will fail to compile (the below comment "// fails to compile" seems
> to indicate that println! will fail):
>
> error: lifetime may not live long enough
> --> src/main.rs:22:9
> |
> 20 | let escape = Jail::with(x, |token1| {
> | ------- return type of closure is Jail<'2, i32>
> | |
> | has type `Jail<'1, i32>`
> 21 | println!("{}", token1.get());
> 22 | token1
> | ^^^^^^ returning this value requires that `'1` must outlive `'2`
Right, I put it there because '2 lives until the second println!. The
problem is not so much that it's returning token1, it's that the
println uses it.
I can see that it's confusing, maybe:
// Because "escape" is used after the closure has returned, the
// compiler cannot find a type for the "let escape" assignment.
Paolo
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
2025-06-09 15:44 ` [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant Paolo Bonzini
@ 2025-06-12 9:21 ` Zhao Liu
2025-06-12 9:07 ` Paolo Bonzini
2025-06-12 10:31 ` Zhao Liu
0 siblings, 2 replies; 15+ messages in thread
From: Zhao Liu @ 2025-06-12 9:21 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
On Mon, Jun 09, 2025 at 05:44:22PM +0200, Paolo Bonzini wrote:
> Date: Mon, 9 Jun 2025 17:44:22 +0200
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
> X-Mailer: git-send-email 2.49.0
>
> This is the trick that allows the parent-field initializer to be used
> only for the object that it's meant to be initialized. This way,
> the owner of a MemoryRegion must be the object that embeds it.
>
> More information is in the comments; it's best explained with a simplified
> example.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> rust/qemu-api/src/qom.rs | 88 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 84 insertions(+), 4 deletions(-)
>
> diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs
> index 21c271cd2f9..1481ef20f0c 100644
> --- a/rust/qemu-api/src/qom.rs
> +++ b/rust/qemu-api/src/qom.rs
> @@ -95,6 +95,7 @@
> use std::{
> ffi::{c_void, CStr},
> fmt,
> + marker::PhantomData,
> mem::{ManuallyDrop, MaybeUninit},
> ops::{Deref, DerefMut},
> ptr::NonNull,
> @@ -208,12 +209,91 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
>
> /// This struct knows that the superclasses of the object have already been
> /// initialized.
> -pub struct ParentInit<'a, T>(&'a mut MaybeUninit<T>);
> +///
> +/// The declaration of `ParentInit` is.. *"a kind of magic"*. It uses a
> +/// technique that is found in several crates, the main ones probably being
> +/// `ghost-cell` (in fact it was introduced by the [`GhostCell` paper](https://plv.mpi-sws.org/rustbelt/ghostcell/))
> +/// and `generativity`.
From the paper, I understand this technique should be "branded type". :-)
> +/// The `PhantomData` makes the `ParentInit` type *invariant* with respect to
> +/// the lifetime argument `'init`. This, together with the `for<'...>` in
> +/// `[ParentInit::with]`, block any attempt of the compiler to be creative when
> +/// operating on types of type `ParentInit` and to extend their lifetimes. In
> +/// particular, it ensures that the `ParentInit` cannot be made to outlive the
> +/// `rust_instance_init()` function that creates it, and therefore that the
> +/// `&'init T` reference is valid.
> +///
> +/// This implementation of the same concept, without the QOM baggage, can help
> +/// understanding the effect:
> +///
> +/// ```
> +/// use std::marker::PhantomData;
> +///
> +/// #[derive(PartialEq, Eq)]
> +/// pub struct Jail<'closure, T: Copy>(&'closure T, PhantomData<fn(&'closure ()) -> &'closure ()>);
> +///
> +/// impl<'closure, T: Copy> Jail<'closure, T> {
> +/// fn get(&self) -> T {
> +/// *self.0
> +/// }
> +///
> +/// #[inline]
> +/// fn with<U>(v: T, f: impl for<'id> FnOnce(Jail<'id, T>) -> U) -> U {
> +/// let parent_init = Jail(&v, PhantomData);
> +/// f(parent_init)
> +/// }
> +/// }
> +/// ```
> +///
> +/// It's impossible to escape the `Jail`; `token1` cannot be moved out of the
> +/// closure:
> +///
> +/// ```ignore
> +/// let x = 42;
> +/// let escape = Jail::with(&x, |token1| {
> +/// println!("{}", token1.get());
> +/// token1
This line will fail to compile (the below comment "// fails to compile" seems
to indicate that println! will fail):
error: lifetime may not live long enough
--> src/main.rs:22:9
|
20 | let escape = Jail::with(x, |token1| {
| ------- return type of closure is Jail<'2, i32>
| |
| has type `Jail<'1, i32>`
21 | println!("{}", token1.get());
22 | token1
| ^^^^^^ returning this value requires that `'1` must outlive `'2`
Referring to GhostToken::new() [*], it said:
// Return the result of running `f`. Note that the `GhostToken` itself
// cannot be returned, because `R` cannot mention the lifetime `'id`, so
// the `GhostToken` only exists within its scope.
So this example is good, I think just need to optimize the location of the error hint.
[*]: https://gitlab.mpi-sws.org/FP/ghostcell/-/blob/master/ghostcell/src/lib.rs#L128
> +/// });
> +/// // fails to compile:
> +/// println!("{}", escape.get());
> +/// ```
> +///
> +/// Likewise, in the QOM case the `ParentInit` cannot be moved out of
> +/// `instance_init()`. Without this trick it would be possible to stash a
> +/// `ParentInit` and use it later to access uninitialized memory.
> +///
> +/// Here is another example, showing how separately-created "identities" stay
> +/// isolated:
> +///
> +/// ```ignore
> +/// impl<'closure, T: Copy> Clone for Jail<'closure, T> {
> +/// fn clone(&self) -> Jail<'closure, T> {
> +/// Jail(self.0, PhantomData)
> +/// }
> +/// }
> +///
> +/// fn main() {
> +/// Jail::with(42, |token1| {
> +/// // this works and returns true: the clone has the same "identity"
> +/// println!("{}", token1 == token1.clone());
> +/// Jail::with(42, |token2| {
> +/// // here the outer token remains accessible...
> +/// println!("{}", token1.get());
> +/// // ... but the two are separate: this fails to compile:
> +/// println!("{}", token1 == token2);
> +/// });
> +/// });
> +/// }
> +/// ```
> +pub struct ParentInit<'init, T>(
> + &'init mut MaybeUninit<T>,
> + PhantomData<fn(&'init ()) -> &'init ()>,
> +);
>
> -impl<'a, T> ParentInit<'a, T> {
> +impl<'init, T> ParentInit<'init, T> {
> #[inline]
> - pub fn with(obj: &'a mut MaybeUninit<T>, f: impl FnOnce(ParentInit<'a, T>)) {
> - let parent_init = ParentInit(obj);
> + pub fn with(obj: &'init mut MaybeUninit<T>, f: impl for<'id> FnOnce(ParentInit<'id, T>)) {
> + let parent_init = ParentInit(obj, PhantomData);
I think it's also valuable to add the similar comment as GhostToken did,
mentioning this `f` can't reture ParentInit itself.
> f(parent_init)
> }
> }
> --
> 2.49.0
>
Nice comment and nice reference (learned a lot).
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
2025-06-12 9:41 ` Zhao Liu
@ 2025-06-12 9:24 ` Paolo Bonzini
0 siblings, 0 replies; 15+ messages in thread
From: Paolo Bonzini @ 2025-06-12 9:24 UTC (permalink / raw)
To: Zhao Liu; +Cc: qemu-devel, qemu-rust
On Thu, Jun 12, 2025 at 11:20 AM Zhao Liu <zhao1.liu@intel.com> wrote:
> > Right, I put it there because '2 lives until the second println!. The
> > problem is not so much that it's returning token1, it's that the
> > println uses it.
>
> Even after I comment out the last intln line, the compiler still
> complains about returning token1. It seems the compiler's checking is
> stricter.
Oh, you're right. Anything that the closure returns must have a longer
lifetime than the closure itself!
Paolo
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
2025-06-12 9:07 ` Paolo Bonzini
@ 2025-06-12 9:41 ` Zhao Liu
2025-06-12 9:24 ` Paolo Bonzini
0 siblings, 1 reply; 15+ messages in thread
From: Zhao Liu @ 2025-06-12 9:41 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
On Thu, Jun 12, 2025 at 11:07:51AM +0200, Paolo Bonzini wrote:
> Date: Thu, 12 Jun 2025 11:07:51 +0200
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: Re: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
>
> On Thu, Jun 12, 2025 at 11:00 AM Zhao Liu <zhao1.liu@intel.com> wrote:
> > > +/// It's impossible to escape the `Jail`; `token1` cannot be moved out of the
> > > +/// closure:
> > > +///
> > > +/// ```ignore
> > > +/// let x = 42;
> > > +/// let escape = Jail::with(&x, |token1| {
> > > +/// println!("{}", token1.get());
> > > +/// token1
> >
> > This line will fail to compile (the below comment "// fails to compile" seems
> > to indicate that println! will fail):
> >
> > error: lifetime may not live long enough
> > --> src/main.rs:22:9
> > |
> > 20 | let escape = Jail::with(x, |token1| {
> > | ------- return type of closure is Jail<'2, i32>
> > | |
> > | has type `Jail<'1, i32>`
> > 21 | println!("{}", token1.get());
> > 22 | token1
> > | ^^^^^^ returning this value requires that `'1` must outlive `'2`
>
> Right, I put it there because '2 lives until the second println!. The
> problem is not so much that it's returning token1, it's that the
> println uses it.
Even after I comment out the last intln line, the compiler still
complains about returning token1. It seems the compiler's checking is
stricter.
I tried at there:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1a9232532250b4a275638b926d7e65e5
Thanks,
Zhao
> I can see that it's confusing, maybe:
>
> // Because "escape" is used after the closure has returned, the
> // compiler cannot find a type for the "let escape" assignment.
>
> Paolo
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant
2025-06-12 9:21 ` Zhao Liu
2025-06-12 9:07 ` Paolo Bonzini
@ 2025-06-12 10:31 ` Zhao Liu
1 sibling, 0 replies; 15+ messages in thread
From: Zhao Liu @ 2025-06-12 10:31 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
> > -impl<'a, T> ParentInit<'a, T> {
> > +impl<'init, T> ParentInit<'init, T> {
> > #[inline]
> > - pub fn with(obj: &'a mut MaybeUninit<T>, f: impl FnOnce(ParentInit<'a, T>)) {
> > - let parent_init = ParentInit(obj);
> > + pub fn with(obj: &'init mut MaybeUninit<T>, f: impl for<'id> FnOnce(ParentInit<'id, T>)) {
> > + let parent_init = ParentInit(obj, PhantomData);
>
> I think it's also valuable to add the similar comment as GhostToken did,
> mentioning this `f` can't reture ParentInit itself.
My bad, I forgot there's no returned value...
> > f(parent_init)
> > }
> > }
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<>
2025-06-09 15:44 ` [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<> Paolo Bonzini
@ 2025-06-12 15:25 ` Zhao Liu
0 siblings, 0 replies; 15+ messages in thread
From: Zhao Liu @ 2025-06-12 15:25 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, qemu-rust
On Mon, Jun 09, 2025 at 05:44:23PM +0200, Paolo Bonzini wrote:
> Date: Mon, 9 Jun 2025 17:44:23 +0200
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<>
> X-Mailer: git-send-email 2.49.0
>
> This removes undefined behavior associated to writing to uninitialized
> fields, and makes it possible to remove "unsafe" from the instance_init
> implementation.
>
> However, the init function itself is still unsafe, because it must promise
> (as a sort as MaybeUninit::assume_init) that all fields have been
> initialized.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> rust/hw/char/pl011/src/device.rs | 34 ++++++++++-----------
> rust/hw/timer/hpet/src/device.rs | 16 ++++------
> rust/qemu-api/src/memory.rs | 12 ++++----
> rust/qemu-api/src/qdev.rs | 51 ++++++++++++++++++++------------
> rust/qemu-api/src/qom.rs | 9 ++++--
> 5 files changed, 65 insertions(+), 57 deletions(-)
Reviewed-by: Zhao Liu <zhao1.liu@intel.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2025-06-12 15:05 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-09 15:44 [PATCH 0/5] rust: make instance_init implementations use safe Rust Paolo Bonzini
2025-06-09 15:44 ` [PATCH 1/5] rust: qemu_api: introduce MaybeUninit field projection Paolo Bonzini
2025-06-11 11:09 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 2/5] rust: hpet: fully initialize object after instance_init Paolo Bonzini
2025-06-11 9:43 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 3/5] rust: qom: introduce ParentInit Paolo Bonzini
2025-06-11 15:25 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 4/5] rust: qom: make ParentInit lifetime-invariant Paolo Bonzini
2025-06-12 9:21 ` Zhao Liu
2025-06-12 9:07 ` Paolo Bonzini
2025-06-12 9:41 ` Zhao Liu
2025-06-12 9:24 ` Paolo Bonzini
2025-06-12 10:31 ` Zhao Liu
2025-06-09 15:44 ` [PATCH 5/5] rust: qom: change instance_init to take a ParentInit<> Paolo Bonzini
2025-06-12 15:25 ` Zhao Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).