Features/Rust/QOM: Difference between revisions

From QEMU
No edit summary
No edit summary
 
(7 intermediate revisions by the same user not shown)
Line 3: Line 3:
== Passing objects around ==
== Passing objects around ==


;ObjectRef:
;ObjectCast:
:*trait for performing casts on objects
:*trait for performing casts on objects
:*upcasts safe at compile time, downcasts safe at runtime
:*upcasts safe at compile time, downcasts safe at runtime
:*implemented by &T and qom::Arc<T>
:*implemented by &T
:*casting &T produces &U, casting qom::Arc<T> produces qom::Arc<U>
:*casting &T produces &U


<pre>
<pre>
pub trait ObjectRef: Sized {
pub trait ObjectCast: Copy + Deref
    /// Type of the underlying QOM object.
where
     type Wrapped: ObjectType;
     <Self as Deref>::Target: ObjectType,
 
{
     fn as_ref(&self) -> &Self::Wrapped;
     fn as_ref(&self) -> &Self::Target;
     fn upcast<U: ObjectType>(self) -> &U where Self::Wrapped: IsA<U>;
     fn upcast<U: ObjectType>(self) -> &U where Self::Target: IsA<U>;
     fn downcast<U: IsA<Self::Wrapped>>(self) -> Option<&U>;
     fn downcast<U: IsA<Self::Target>>(self) -> Option<&U>;
     fn dynamic_cast<U: ObjectType>(self) -> Option<&U>;
     fn dynamic_cast<U: ObjectType>(self) -> Option<&U>;
     unsafe fn unsafe_cast<U: ObjectType>(self) -> &U;
     unsafe fn unsafe_cast<U: ObjectType>(self) -> &U;
}
}
impl<T: ObjectType> ObjectCast for &T {}
</pre>
</pre>


Line 25: Line 27:
:*T is a struct for a QOM object
:*T is a struct for a QOM object
:*cloning qom::Arc calls object_ref, dropping qom::Arc calls object_unref
:*cloning qom::Arc calls object_ref, dropping qom::Arc calls object_unref
:*associated functions for casting similar to the above (except downcast/dynamic_cast returns <tt>Result&lt;Arc&lt;U&gt;, Arc&lt;T&gt;&gt;</tt>)
:*possible to go (unsafely) from &T to Arc<T>
:*associated functions for casting owned references similar to the above (except downcast/dynamic_cast returns <tt>Result&lt;Arc&lt;U&gt;, Arc&lt;T&gt;&gt;</tt>)
:*consider renaming this to Owned<> or (similar to Linux) ARef<>?


== Calling methods (bindings to classes implemented in C) ==
== Calling methods (bindings to classes implemented in C) ==
Line 54: Line 58:
;trait: ObjectMethods, DeviceMethods, UserCreatableMethods, ...
;trait: ObjectMethods, DeviceMethods, UserCreatableMethods, ...
:* defines methods of non-final classes and interfaces, for example ObjectMethods::typename(&self)
:* defines methods of non-final classes and interfaces, for example ObjectMethods::typename(&self)
:* automatically implemented by &T or qom::Arc&lt;T&gt; where T is a subclass
:* automatically implemented by &T or qom::Arc&lt;T&gt; where T is a subclass. The idea is that you can operate as if [https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/deref-coercions.html deref coercion] could also convert to superclasses.
 
It is implemented as follows


<pre>
<pre>
pub unsafe trait ObjectMethods: Sized + ObjectRef
pub trait ObjectMethods: Deref
where
where
     <Self as ObjectRef>::Wrapped: IsA<Object>,
     Self::Target: IsA<Object>,
{
    /// Remove the object from the QOM tree
    fn unparent(&self) {
        let obj = self.upcast::<Object>();
        // SAFETY: safety of this is the requirement for implementing IsA
        unsafe {
            object_unparent(obj.as_ptr_mut());
        }
    }
}


unsafe impl<T: IsA<Object>, R: ObjectRef<Wrapped = T>> ObjectMethods for R {}
impl<R: Deref> ObjectMethods for R where R::Target: IsA<Object> {}
</pre>
</pre>


Line 72: Line 88:
<pre>
<pre>
pub trait DeviceImpl: ObjectImpl + DeviceTypeImpl {
pub trait DeviceImpl: ObjectImpl + DeviceTypeImpl {
     const REALIZE: Option<fn(obj: &Self) -> crate::Result<()>> = None;
     const REALIZE: Option<fn(obj: &Self) -> qom::Result<()>> = None;
     const UNREALIZE: Option<fn(obj: &Self)> = None;
     const UNREALIZE: Option<fn(obj: &Self)> = None;
     const COLD_RESET: Option<fn(obj: &Self)> = None;
     const COLD_RESET: Option<fn(obj: &Self)> = None;
Line 80: Line 96:
Instance-side functions (e.g. <tt>fn unrealize(&self)</tt>) implemented as private methods and exposed via the above trait; None means use superclass.
Instance-side functions (e.g. <tt>fn unrealize(&self)</tt>) implemented as private methods and exposed via the above trait; None means use superclass.


Class-init sets up QOM to call these functions via unsafe wrappers:
Each superclass must implement a function like
 
<pre>
    pub unsafe extern "C" fn rust_class_init<T: DeviceImpl>(klass: *mut c_void) ...
</pre>
 
The function becomes the subclass's class_init and that daisy-chains to the superclass's rust_class_init. It sets up QOM to call these functions via unsafe wrappers:


<pre>
<pre>
Line 92: Line 114:
</pre>
</pre>


Rust implementation are split in configuration (implements Default + ConstDefault) and state (implements Default):
Rust implementation types contain a configuration part (implements Default + ConstDefault) and a state part (implements Default):
* instance_init is implemented automatically via Default/ConstDefault trait (using MaybeUninit to move the default values into the struct).
* instance_init is implemented automatically via Default/ConstDefault trait (using MaybeUninit to move the default values into the struct).
* instance_finalize implemented automatically via Drop trait
**Change in common code: pre_init hook that replaces memset(obj, 0, type->instance_size)? This would allow filling in the default values of the configuration fields ''before'' property initialization. This is safer, because otherwise we have to rely entirely on qdev properties to initialize the configuration fields.
 
* instance_finalize implemented automatically via drop_in_place
Possible change in common code: pre_init hook that replaces memset(obj, 0, type->instance_size)? This would allow filling in the default values of the configuration fields ''before'' property initialization. This is safer, because otherwise we have to rely entirely on qdev properties to initialize the configuration fields.


== TODO ==
== TODO ==

Latest revision as of 08:43, 26 June 2024

Rust QOM interoperability design, very loosely inspired by glib-rs.

Passing objects around

ObjectCast
  • trait for performing casts on objects
  • upcasts safe at compile time, downcasts safe at runtime
  • implemented by &T
  • casting &T produces &U
pub trait ObjectCast: Copy + Deref
where
    <Self as Deref>::Target: ObjectType,
{
    fn as_ref(&self) -> &Self::Target;
    fn upcast<U: ObjectType>(self) -> &U where Self::Target: IsA<U>;
    fn downcast<U: IsA<Self::Target>>(self) -> Option<&U>;
    fn dynamic_cast<U: ObjectType>(self) -> Option<&U>;
    unsafe fn unsafe_cast<U: ObjectType>(self) -> &U;
}

impl<T: ObjectType> ObjectCast for &T {}
qom::Arc<T>
  • T is a struct for a QOM object
  • cloning qom::Arc calls object_ref, dropping qom::Arc calls object_unref
  • possible to go (unsafely) from &T to Arc<T>
  • associated functions for casting owned references similar to the above (except downcast/dynamic_cast returns Result<Arc<U>, Arc<T>>)
  • consider renaming this to Owned<> or (similar to Linux) ARef<>?

Calling methods (bindings to classes implemented in C)

All methods receive &self (interior mutability). Rust implementation needs to wrap state with Cell<>, RefCell<> or Mutex<>

Two traits describe the hierarchy and provide the mapping from Rust types to C string names:

pub unsafe trait IsA<P: ObjectType>: ObjectType {}

pub unsafe trait ObjectType: Sized {
    const TYPE: &'static CStr;
    fn new() -> qom::Arc<Self> { ... }
}

Each class is composed of:

  • one struct that provides the layout. It implements ObjectType and IsA<> for each direct or indirect parent
  • methods are placed on a trait for non-final classes. The trait is automatically visible to all structs that implement IsA for that class.

Each interface only has the latter.

Struct
Object, Device, ...
  • defines constructors
  • defines methods of final classes
trait
ObjectMethods, DeviceMethods, UserCreatableMethods, ...
  • defines methods of non-final classes and interfaces, for example ObjectMethods::typename(&self)
  • automatically implemented by &T or qom::Arc<T> where T is a subclass. The idea is that you can operate as if deref coercion could also convert to superclasses.

It is implemented as follows

pub trait ObjectMethods: Deref
where
    Self::Target: IsA<Object>,
{
    /// Remove the object from the QOM tree
    fn unparent(&self) {
        let obj = self.upcast::<Object>();
        // SAFETY: safety of this is the requirement for implementing IsA
        unsafe {
            object_unparent(obj.as_ptr_mut());
        }
    }
}

impl<R: Deref> ObjectMethods for R where R::Target: IsA<Object> {}

Defining QOM classes in Rust

struct must be #[repr(C)]

One trait per class if it has virtual functions

pub trait DeviceImpl: ObjectImpl + DeviceTypeImpl {
    const REALIZE: Option<fn(obj: &Self) -> qom::Result<()>> = None;
    const UNREALIZE: Option<fn(obj: &Self)> = None;
    const COLD_RESET: Option<fn(obj: &Self)> = None;
}

Instance-side functions (e.g. fn unrealize(&self)) implemented as private methods and exposed via the above trait; None means use superclass.

Each superclass must implement a function like

    pub unsafe extern "C" fn rust_class_init<T: DeviceImpl>(klass: *mut c_void) ...

The function becomes the subclass's class_init and that daisy-chains to the superclass's rust_class_init. It sets up QOM to call these functions via unsafe wrappers:

unsafe extern "C" fn rust_cold_reset<T: DeviceImpl>(obj: *mut DeviceState) {
    let f = T::COLD_RESET.unwrap();
    f((&*obj).unsafe_cast::<T>())
}
if T::COLD_RESET.is_some() {
    self.cold_reset = rust_cold_reset::<T>; // self is &mut DeviceClass
}

Rust implementation types contain a configuration part (implements Default + ConstDefault) and a state part (implements Default):

  • instance_init is implemented automatically via Default/ConstDefault trait (using MaybeUninit to move the default values into the struct).
    • Change in common code: pre_init hook that replaces memset(obj, 0, type->instance_size)? This would allow filling in the default values of the configuration fields before property initialization. This is safer, because otherwise we have to rely entirely on qdev properties to initialize the configuration fields.
  • instance_finalize implemented automatically via drop_in_place

TODO

  • Creating new objects requires object_property_set* wrappers
  • Defining classes not part of qdev (i.e. not under DeviceState) is not considered because properties would require full-blown Visitor support. Alternative: Features/QOM-QAPI_integration