Provide a macro to register a type and automatically define instance_init (actually instance_mem_init) and instance_finalize functions. Subclasses of Object must define a trait ObjectImpl, to point the type definition machinery to the implementation of virtual functions in Object.
Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> --- qemu/src/lib.rs | 4 + qemu/src/qom/mod.rs | 1 + qemu/src/qom/object_impl.rs | 146 ++++++++++++++++++++++++++++++++++++ qemu/src/util/mod.rs | 1 + qemu/src/util/zeroed.rs | 21 ++++++ qemu/tests/main.rs | 32 ++++++++ 6 files changed, 205 insertions(+) create mode 100644 qemu/src/qom/object_impl.rs create mode 100644 qemu/src/util/zeroed.rs create mode 100644 qemu/tests/main.rs diff --git a/qemu/src/lib.rs b/qemu/src/lib.rs index b0dcce1..81abf9c 100644 --- a/qemu/src/lib.rs +++ b/qemu/src/lib.rs @@ -4,6 +4,7 @@ pub mod bindings; pub use bindings::DeviceState; pub use bindings::Object; +pub use bindings::TypeInfo; pub mod hw; pub use hw::core::device::DeviceMethods; @@ -12,6 +13,8 @@ pub mod qom; pub use qom::object::ObjectClassMethods; pub use qom::object::ObjectMethods; pub use qom::object::ObjectType; +pub use qom::object_impl::ObjectImpl; +pub use qom::object_impl::TypeImpl; pub use qom::refs::ObjectCast; pub use qom::refs::Owned; @@ -21,4 +24,5 @@ pub use util::foreign::CloneToForeign; pub use util::foreign::FromForeign; pub use util::foreign::IntoNative; pub use util::foreign::OwnedPointer; +pub use util::zeroed::Zeroed; pub type Result<T> = std::result::Result<T, Error>; diff --git a/qemu/src/qom/mod.rs b/qemu/src/qom/mod.rs index 95489c5..3f8ee6e 100644 --- a/qemu/src/qom/mod.rs +++ b/qemu/src/qom/mod.rs @@ -1,2 +1,3 @@ pub mod object; +pub mod object_impl; pub mod refs; diff --git a/qemu/src/qom/object_impl.rs b/qemu/src/qom/object_impl.rs new file mode 100644 index 0000000..61546b6 --- /dev/null +++ b/qemu/src/qom/object_impl.rs @@ -0,0 +1,146 @@ +//! Macros and traits to implement subclasses of Object in Rust +//! +//! @author Paolo Bonzini + +#![allow(clippy::missing_safety_doc)] + +use const_default::ConstDefault; + +use std::ffi::c_void; +use std::mem; +use std::mem::MaybeUninit; +use std::ptr::drop_in_place; + +use crate::qom::object::ObjectType; + +use crate::qom::refs::ObjectCast; + +use crate::bindings::type_register; +use crate::bindings::Object; +use crate::bindings::ObjectClass; +use crate::bindings::TypeInfo; + +use crate::util::zeroed::Zeroed; + +/// Information on which superclass methods are overridden +/// by a Rust-implemented subclass of Object. +pub trait ObjectImpl: ObjectType { + /// If not `None`, a function that implements the `unparent` member + /// of the QOM `ObjectClass`. + const UNPARENT: Option<fn(obj: &Self)> = None; +} + +impl ObjectClass { + /// Initialize an `ObjectClass` from an `ObjectImpl`. + pub fn class_init<T: ObjectImpl>(&mut self) { + unsafe extern "C" fn rust_unparent<T: ObjectImpl>(obj: *mut Object) { + let f = T::UNPARENT.unwrap(); + f((&*obj).unsafe_cast::<T>()) + } + self.unparent = T::UNPARENT.map(|_| rust_unparent::<T> as _); + } +} + +impl Object { + pub unsafe extern "C" fn rust_class_init<T: ObjectImpl>( + klass: *mut c_void, + _data: *mut c_void, + ) { + let oc: &mut ObjectClass = &mut *(klass.cast()); + oc.class_init::<T>(); + } +} + +/// Internal information on a Rust-implemented subclass of Object. +/// Only public because it is used by macros. +pub unsafe trait TypeImpl: ObjectType + ObjectImpl { + type Super: ObjectType; + type Conf: ConstDefault; + type State: Default; + + const CLASS_INIT: unsafe extern "C" fn(klass: *mut c_void, data: *mut c_void); + + fn uninit_conf(obj: &mut MaybeUninit<Self>) -> &mut MaybeUninit<Self::Conf>; + fn uninit_state(obj: &mut MaybeUninit<Self>) -> &mut MaybeUninit<Self::State>; +} + +unsafe fn rust_type_register<T: TypeImpl + ObjectImpl>() { + unsafe extern "C" fn rust_instance_mem_init<T: TypeImpl>(obj: *mut c_void) { + let obj: &mut std::mem::MaybeUninit<T> = &mut *(obj.cast()); + + T::uninit_conf(obj).write(ConstDefault::DEFAULT); + T::uninit_state(obj).write(Default::default()); + } + + unsafe extern "C" fn rust_instance_finalize<T: TypeImpl>(obj: *mut c_void) { + let obj: *mut T = obj.cast(); + drop_in_place(obj); + } + + let ti = TypeInfo { + name: T::TYPE.as_ptr(), + parent: T::Super::TYPE.as_ptr(), + instance_size: mem::size_of::<T>(), + instance_mem_init: Some(rust_instance_mem_init::<T>), + instance_finalize: Some(rust_instance_finalize::<T>), + class_init: Some(T::CLASS_INIT), + + // SAFETY: TypeInfo is defined in C and all fields are okay to be zeroed + ..Zeroed::zeroed() + }; + + type_register(&ti) +} + +#[macro_export] +macro_rules! qom_define_type { + ($name:expr, $struct:ident, $conf_ty:ty, $state_ty:ty; @extends $super:ty $(,$supers:ty)*) => { + struct $struct { + // self.base dropped by call to superclass instance_finalize + base: std::mem::ManuallyDrop<$super>, + conf: $conf_ty, + state: $state_ty, + } + + // Define IsA markers for the struct itself and all the superclasses + $crate::qom_isa!($struct, $super $(,$supers)*); + + unsafe impl $crate::qom::object::ObjectType for $struct { + const TYPE: &'static std::ffi::CStr = $name; + } + + unsafe impl $crate::qom::object_impl::TypeImpl for $struct { + type Super = $super; + type Conf = $conf_ty; + type State = $state_ty; + + const CLASS_INIT: unsafe extern "C" fn(klass: *mut std::ffi::c_void, data: *mut std::ffi::c_void) + = <$super>::rust_class_init::<Self>; + + fn uninit_conf(obj: &mut std::mem::MaybeUninit::<Self>) -> &mut std::mem::MaybeUninit<$conf_ty> { + use std::ptr::addr_of_mut; + + // Projecting the incoming reference to a single field is safe, + // because the return value is also MaybeUnit + unsafe { &mut *(addr_of_mut!((*obj.as_mut_ptr()).conf).cast()) } + } + + fn uninit_state(obj: &mut std::mem::MaybeUninit::<Self>) -> &mut std::mem::MaybeUninit<$state_ty> { + use std::ptr::addr_of_mut; + + // Projecting the incoming reference to a single field is safe, + // because the return value is also MaybeUnit + unsafe { &mut *(addr_of_mut!((*obj.as_mut_ptr()).state).cast()) } + } + } + + // TODO: call rust_type_register + }; +} + +#[macro_export] +macro_rules! conf_type { + ($type:ty) => { + <$type as $crate::qom::object_impl::TypeImpl>::Conf + }; +} diff --git a/qemu/src/util/mod.rs b/qemu/src/util/mod.rs index e6078ac..9c081b6 100644 --- a/qemu/src/util/mod.rs +++ b/qemu/src/util/mod.rs @@ -1,2 +1,3 @@ pub mod error; pub mod foreign; +pub mod zeroed; diff --git a/qemu/src/util/zeroed.rs b/qemu/src/util/zeroed.rs new file mode 100644 index 0000000..e656834 --- /dev/null +++ b/qemu/src/util/zeroed.rs @@ -0,0 +1,21 @@ +#![allow(clippy::undocumented_unsafe_blocks)] + +use std::mem::MaybeUninit; + +/// Trait providing an easy way to obtain an all-zero +/// value for a struct +/// +/// # Safety +/// +/// Only add this to a type if `MaybeUninit::zeroed().assume_init()` +/// is valid for that type. +pub unsafe trait Zeroed: Sized { + fn zeroed() -> Self { + // SAFETY: If this weren't safe, just do not add the + // trait to a type. + unsafe { MaybeUninit::zeroed().assume_init() } + } +} + +// Put here all the impls that you need for the bindgen-provided types. +unsafe impl Zeroed for crate::bindings::TypeInfo {} diff --git a/qemu/tests/main.rs b/qemu/tests/main.rs new file mode 100644 index 0000000..a7cbeed --- /dev/null +++ b/qemu/tests/main.rs @@ -0,0 +1,32 @@ +use const_default::ConstDefault; + +use qemu::qom_define_type; +use qemu::Object; +use qemu::ObjectClassMethods; +use qemu::ObjectImpl; + +#[derive(Default, ConstDefault)] +struct TestConf { + #[allow(dead_code)] + foo: bool, +} + +#[derive(Default)] +struct TestState { + #[allow(dead_code)] + bar: i32, +} + +qom_define_type!( + c"test-object", + TestObject, + TestConf, + (); + @extends Object +); + +impl ObjectImpl for TestObject {} + +fn main() { + drop(TestObject::new()); +} -- 2.45.2