Features/Rust/QOM: Difference between revisions

From QEMU
No edit summary
No edit summary
Line 71: Line 71:
struct must be #[repr(C)]
struct must be #[repr(C)]


One traits per class + one more if it has virtual functions
One trait per class if it has virtual functions


;trait #1: ObjectTypeImpl, DeviceTypeImpl, ...
<pre>
:class-side: implements initialization of TypeInfo and XyzClass
pub trait DeviceImpl: ObjectImpl + DeviceTypeImpl {
;trait #2: ObjectImpl, DeviceImpl, ...
    const REALIZE: Option<fn(obj: &Self) -> crate::Result<()>> = None;
:implements instance-side functions (e.g. <tt>fn unrealize(&self)</tt>)
    const UNREALIZE: Option<fn(obj: &Self)> = None;
:unsafe functions call members of trait #2. They are assigned to XyzClass fields by trait #1.
    const COLD_RESET: Option<fn(obj: &Self)> = None;
}
</pre>
 
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:
 
<pre>
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
}
</pre>


Rust implementation are split in configuration (implements Default + ConstDefault) and state (implements Default):
Rust implementation are split in configuration (implements Default + ConstDefault) and state (implements Default):
Line 85: Line 101:
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.
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.


Right now, defining classes not part of qdev (i.e. not under DeviceState) is not considered because it would require full-blown Visitor support.
== 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]]

Revision as of 10:29, 24 June 2024

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

Passing objects around

ObjectRef
  • trait for performing casts on objects
  • upcasts safe at compile time, downcasts safe at runtime
  • implemented by &T and qom::Arc<T>
  • casting &T produces &U, casting qom::Arc<T> produces qom::Arc<U>
qom::Arc<T>
  • T is a struct for a QOM object
  • cloning qom::Arc calls object_ref, dropping qom::Arc calls object_unref
pub trait ObjectRef: Sized {
    /// Type of the underlying QOM object.
    type Wrapped: ObjectType;

    fn as_ref(&self) -> &Self::Wrapped;
    fn upcast<'a, U: ObjectType>(self) -> ... where Self::Wrapped: IsA<U>;
    fn downcast<'a, U: IsA<Self::Wrapped>>(self) -> Option<...>;
    fn dynamic_cast<'a, U: ObjectType>(self) -> Option<...>;
    unsafe fn unsafe_cast<'a, U: ObjectType>(self) -> ...;
}

Requires generic associated types (Rust 1.65).


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
pub unsafe trait ObjectMethods: Sized + ObjectRef
where
    <Self as ObjectRef>::Wrapped: IsA<Object>,

unsafe impl<T: IsA<Object>, R: ObjectRef<Wrapped = T>> ObjectMethods for R {}

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) -> crate::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.

Class-init 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 are split in configuration (implements Default + ConstDefault) and state (implements Default):

  • 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

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

  • 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