Hello,
here is my second attempt to review this, this time placing the remarks
as close as possible to the code that is affected. However, the meat is
the same as in my previous replies to the 03/11 thread.
I hope this shows that I have practical concerns about the patch and
it's not just FUD that it's not acceptable.
On 10/24/24 16:03, Manos Pitsidianakis wrote:
Add a new derive procedural macro to declare device models. Add
corresponding DeviceImpl trait after already existing ObjectImpl trait.
At the same time, add instance_init, instance_post_init,
instance_finalize methods to the ObjectImpl trait and call them from the
ObjectImplUnsafe trait, which is generated by the procedural macro.
This allows all the boilerplate device model registration to be handled
by macros, and all pertinent details to be declared through proc macro
attributes or trait associated constants and methods.
The device class can now be generated automatically and the name can be
optionally overridden:
------------------------ >8 ------------------------
#[repr(C)]
#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
#[device(class_name_override = PL011Class)]
/// PL011 Device Model in QEMU
pub struct PL011State {
The first design issue is already visible here in this example. I could
place the same comment when the code appears in rust/hw/char/pl011, but
it's easier to do it here.
You have two derive macros, Object and Device. Object is derived by all
objects (even if right now we have only devices), Device is derived by
devices only.
The class name is a property of any object, not just devices. It should
not be part of the #[device()] attribute. #[derive(Device)] and
#[device()] instead should take care of properties and categories (and
possibly vmstate, but I'm not sure about that and there's already enough
to say about this patch).
You also have no documentation, which means that users will have no idea
of what are the other sub-attributes of #[device()], including the
difference between class_name and class_name_override, or how categories
are defined.
Even if we don't have support for rustdoc yet in tree, we should have
all the needed documentation as soon as the API moves from "ad hoc usage
of C symbols" to idiomatic.
Object methods (instance_init, etc) methods are now trait methods:
------------------------ >8 ------------------------
/// Trait a type must implement to be registered with QEMU.
pub trait ObjectImpl {
type Class: ClassImpl;
const TYPE_NAME: &'static CStr;
const PARENT_TYPE_NAME: Option<&'static CStr>;
const ABSTRACT: bool;
Class, TYPE_NAME, PARENT_TYPE_NAME, ABSTRACT should be defined via
#[object()].
But actually, there is already room for defining a separate trait:
/// # Safety
///
/// - the first field of the struct must be of `Object` type,
/// or derived from it
///
/// - `TYPE` must match the type name used in the `TypeInfo` (no matter
/// if it is defined in C or Rust).
///
/// - the struct must be `#[repr(C)]`
pub unsafe trait ObjectType {
type Class: ClassImpl;
const TYPE_NAME: &'static CStr;
}
... because you can implement it even for classes that are defined in C
code. Then #[derive(Object)] can find the TYPE_NAME directly from the
first field of the struct, i.e.
parent_obj: SysBusDevice;
becomes
const PARENT_TYPE_NAME: Option<&'static CStr> =
Some(<SysBusDevice as TypeImpl>::TYPE_NAME);
while #[object()] would be just
#[object(class_type = PL011Class, type_name = "pl011")]
Accessing the type of the first field is easy using the get_fields()
function that Junjie added at
https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonz...@redhat.com/
This shows another reason why I prefer to get CI to work first. Having
to do simple, but still non-trivial work, often provides code that can
be reused in more complex setups.
unsafe fn instance_init(&mut self) {}
fn instance_post_init(&mut self) {}
fn instance_finalize(&mut self) {}
}
In the trait, having a default implementation that is empty works
(unlike for realize/reset, as we'll see later). So this is a bit
simpler. However, instance_finalize should have a non-empty default
implementation:
std::ptr::drop_in_place(self);
which should be okay for most devices.
Alternatively, leave out instance_post_init() and instance_finalize()
until we need them, and put the drop_in_place() call directly in the
unsafe function that goes in the TypeInfo.
------------------------ >8 ------------------------
Device methods (realize/reset etc) are now safe idiomatic trait methods:
------------------------ >8 ------------------------
/// Implementation methods for device types.
pub trait DeviceImpl: ObjectImpl {
fn realize(&mut self) {}
fn reset(&mut self) {}
}
------------------------ >8 ------------------------
This is an incorrect definition of the trait. The default definition of
device methods is not "empty", it's "just reuse the superclass
implementation". In particular, this means that PL011LuminaryState
right now cannot use #[derive(Device)].
The derive Device macro is responsible for creating all the extern "C" FFI
functions that QEMU needs to call these methods.
This is unnecessary. It is perfectly possible to write the extern "C"
functions (class_init, realize, reset) just once as either type-generic
functions, or functions in a trait. More on this later.
diff --git a/rust/qemu-api/src/objects.rs b/rust/qemu-api/src/objects.rs
new file mode 100644
index
0000000000000000000000000000000000000000..5c6762023ed6914f9c6b7dd16a5e07f778c2d4fa
--- /dev/null
+++ b/rust/qemu-api/src/objects.rs
@@ -0,0 +1,90 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Implementation traits for QEMU objects, devices.
+
+use ::core::ffi::{c_int, c_void, CStr};
+
+use crate::bindings::{DeviceState, Error, MigrationPriority, Object,
ObjectClass, TypeInfo};
+
+/// Trait a type must implement to be registered with QEMU.
+pub trait ObjectImpl {
+ type Class: ClassImpl;
+ const TYPE_NAME: &'static CStr;
+ const PARENT_TYPE_NAME: Option<&'static CStr>;
+ const ABSTRACT: bool;
These consts should entirely be derived from the #[object()] attribute.
You can facilitate the split by having two traits, one for things
derived from the attribute (the above four), and one for the vtable.
+ unsafe fn instance_init(&mut self) {}
+ fn instance_post_init(&mut self) {}
+ fn instance_finalize(&mut self) {}
+}
See above remark on the default implementation of instance_finalize.
+/// The `extern`/`unsafe` analogue of [`ObjectImpl`]; it is used internally by
`#[derive(Object)]`
+/// and should not be implemented manually.
+pub unsafe trait ObjectImplUnsafe {
+ const TYPE_INFO: TypeInfo;
+
+ const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
+ const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
+ const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
+}
+
This trait is not needed at all, because it really has juts one
implementation. The fact that there is just one implementation is
hidden by the fact that you are generating the code instead of relying
on the type system.
All you need is a single function, which will be called via the
module_init mechanism:
fn rust_type_register<T: ObjectImpl>() {
let TypeInfo ti = ...;
unsafe { type_register(&ti); }
}
+/// Methods for QOM class types.
+pub trait ClassImpl {
+ type Object: ObjectImpl;
+
+ unsafe fn class_init(&mut self, _data: *mut core::ffi::c_void) {}
+ unsafe fn class_base_init(&mut self, _data: *mut core::ffi::c_void) {}
+}
+
This trait (or more precisely class_init and class_base_init) is not
needed. class_base_init is only needed in very special cases, we can
just decide they won't be available in Rust for now and possible for ever.
As to class_init device XYZ would only need a non-empty class_init
method if we added support for the _data argument. But then we would
need a way to provide the type of _data, and to cast _data to the
appropriate type; we would also need a way to provide a mapping from
multiple data objects to multiple type names, which is hard to do
because right now each Rust struct has a single type name associated.
So, let's just keep only the auto-generated class_init for simplicity.
If we can just decide that, if device XYZ has superclass FooDevice, it
implements FooDeviceImpl and class_init is provided by the FooDevice
bindings.
I can't really say if the "type Object" part is needed. I couldn't
offhand find anything that uses it, but I may have missed it. If so, it
can be in ClassImplUnsafe.
+/// The `extern`/`unsafe` analogue of [`ClassImpl`]; it is used internally by
`#[derive(Object)]`
+/// and should not be implemented manually.
+pub unsafe trait ClassImplUnsafe {
+ const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data:
*mut c_void)>;
+ const CLASS_BASE_INIT: Option<
+ unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
+ >;
+}
Again, no need to have CLASS_BASE_INIT here.
+/// Implementation methods for device types.
+pub trait DeviceImpl: ObjectImpl {
+ fn realize(&mut self) {}
+ fn reset(&mut self) {}
+}
These unfortunately cannot be functions. Doing so forces the class_init
method to assign both dc->reset and dc->realize for _all_ classes,
whereas for example PL011LuminaryClass would not override either.
Therefore, the definition must be
pub trait DeviceImpl: ObjectImpl {
const REALIZE: Option<fn realize(&mut self)> = None;
const RESET: Option<fn realize(&mut self)> = None;
}
Yes, it's uglier, but we cannot escape the fact that we're implementing
something that Rust doesn't have natively (inheritance). :( So we
cannot use language features meant for a completely different kind of
polymorphism.
+/// The `extern`/`unsafe` analogue of [`DeviceImpl`]; it is used internally by
`#[derive(Device)]`
+/// and should not be implemented manually.
+pub unsafe trait DeviceImplUnsafe {
+ const REALIZE: Option<unsafe extern "C" fn(dev: *mut DeviceState, _errp: *mut
*mut Error)>;
+ const RESET: Option<unsafe extern "C" fn(dev: *mut DeviceState)>;
+}
This trait is also unnecessary, because all that you need is a single
function:
fn rust_device_class_init<T: DeviceImpl>(
klass: *mut ObjectClass, _data: *mut c_void)
defined outside the procedural macro. #[derive(Device)] can define
ClassImplUnsafe to point CLASS_INIT to rust_device_class_init.
(Later, rust_device_class_init() can be moved into a trait so that it's
possible to define other classes of devices, for example PCI devices.
Note that such an extension would be much easier, than if it was
_required_ to touch the procedural macro).
+/// Constant metadata and implementation methods for types with device
migration state.
+pub trait Migrateable: DeviceImplUnsafe {
+ const NAME: Option<&'static CStr> = None;
+ const UNMIGRATABLE: bool = true;
+ const EARLY_SETUP: bool = false;
+ const VERSION_ID: c_int = 1;
+ const MINIMUM_VERSION_ID: c_int = 1;
+ const PRIORITY: MigrationPriority = MigrationPriority::MIG_PRI_DEFAULT;
+
+ unsafe fn pre_load(&mut self) -> c_int {
+ 0
+ }
+ unsafe fn post_load(&mut self, _version_id: c_int) -> c_int {
+ 0
+ }
+ unsafe fn pre_save(&mut self) -> c_int {
+ 0
+ }
+ unsafe fn post_save(&mut self) -> c_int {
+ 0
+ }
+ unsafe fn needed(&mut self) -> bool {
+ false
+ }
+ unsafe fn dev_unplug_pending(&mut self) -> bool {
+ false
+ }
+}
Premature. No need to add this trait until you add support for migration.
diff --git a/rust/qemu-api/src/tests.rs b/rust/qemu-api/src/tests.rs
deleted file mode 100644
index
df54edbd4e27e7d2aafc243355d1826d52497c21..0000000000000000000000000000000000000000
--- a/rust/qemu-api/src/tests.rs
+++ /dev/null
@@ -1,49 +0,0 @@
Nope. Fix the test, don't remove it.
-#[derive(Debug, qemu_api_macros::Object)]
+#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::Device)]
+#[device(class_name_override = PL011Class)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
@@ -51,6 +52,7 @@ pub struct PL011State {
pub read_count: usize,
pub read_trigger: usize,
#[doc(alias = "chr")]
+ #[property(name = c"chardev", qdev_prop = qdev_prop_chr)]
(See earlier comments on accepting only a LitStr and deriving qdev_prop
from the type).
+impl DeviceImpl for PL011State {
+ fn realize(&mut self) {
+ ...
+ }
+
+ fn reset(&mut self) {
+ ...
+ }
This extractions of code into DeviceImpl is good. However, as I said
above, I'm not sure about the trait itself. I'll remark later when I
encounter the definition.
+impl qemu_api::objects::Migrateable for PL011State {}
Premature.
Before moving on to the procedural macro code, my proposal to split the
patches is:
1) introduce the trait ObjectType, define it for Object, DeviceState and
SysBusDevice.
2) introduce the traits ObjectImpl, DeviceImpl and ClassImplUnsafe.
Define the first two for PL011State.
3) add to common code the wrappers that call into DeviceImpl, removing
them from PL011State
4) introduce the functions rust_type_register and rust_device_class_init
that use the traits.
5) remove most arguments of device_class_init!(), using the
infrastructure introduced in the previous two steps
6) split ObjectImpl into a part that will be covered by #[object()],
let's call it ObjectInfo
7) add implementation of #[object()], replace PL011State's
implementation of ObjectInfo with #[object()]
8) split DeviceImpl into a part that will be covered by #[device()]
(properties and categories), let's call it DeviceInfo
9) add #[derive(Device) and implementation of #[device()], replace
PL011State's implementation of DeviceInfo with #[device()]
Where 1-5 should be submitted as a separate series, one that does not
touch procedural macros *at all* and just generalizes the pl011 code
that defines QOM types.
Anyhow, I'll continue reviewing the procedural macro code.
+#[derive(Debug, Default)]
+struct DeriveContainer {
+ category: Option<syn::Path>,
+ class_name: Option<syn::Ident>,
+ class_name_override: Option<syn::Ident>,
+}
Rename to DeviceAttribute.
+impl Parse for DeriveContainer {
+ fn parse(input: ParseStream) -> Result<Self> {
syn::Result represents a parse error, not an error in the allowed syntax
of the attribute. Below, you're using panic! and unwrap(), but probably
instead of syn::Result we need to have something like
pub enum Error {
CompileError(syn::Span, String),
ParseError(syn::Error)
}
which extends the CompileError enum of
https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonz...@redhat.com/
and is amenable to use with "?". In particular, note the idiom used by
the root derive_offsets() functions:
let input = parse_macro_input!(input as DeriveInput);
let expanded = derive_offsets_or_error(input).
unwrap_or_else(Into::into);
TokenStream::from(expanded)
which works via an "impl From<CompileError> for proc_macro2::TokenStream".
I believe that most of the benefit of this series (basically, all except
the #[property] attribute) can be obtained without the procedural macro.
Therefore, once we do add the procedural macro, we should not have it
panic on errors.
+ let _: syn::Token![#] = input.parse()?;
+ let bracketed;
+ _ = syn::bracketed!(bracketed in input);
+ assert_eq!(DEVICE, bracketed.parse::<syn::Ident>()?);
+ let mut retval = Self {
+ category: None,
+ class_name: None,
+ class_name_override: None,
+ };
+ let content;
+ _ = syn::parenthesized!(content in bracketed);
+ while !content.is_empty() {
+ let value: syn::Ident = content.parse()?;
+ if value == CLASS_NAME {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.class_name.is_some() {
+ panic!("{} can only be used at most once", CLASS_NAME);
+ }
No panic!, instead we need to return a compile_error!() TokenStream, or
as above a Result<> that can be converted to compile_error!() up in the
chain.
+ retval.class_name = Some(content.parse()?);
Please make this function a separate trait in utilities:
trait AttributeParsing {
const NAME: Symbol;
fn set(&mut self, key: &syn::Ident, input: &ParseStream) -> Result<()>;
fn parse(input: ParseStream) -> Result<Self> { ... }
}
Then the "if" can move to the struct-specific implementation of
AttributeParsing::set, while the rest can move to the default
implementation of AttributeParsing::parse.
#[property()] and #[device()] (and also the proposed #[object()]) can
then share the implementation of AttributeParsing::parse.
+ } else if value == CLASS_NAME_OVERRIDE {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.class_name_override.is_some() {
+ panic!("{} can only be used at most once",
CLASS_NAME_OVERRIDE);
+ }> + retval.class_name_override =
Some(content.parse()?);
+ } else if value == CATEGORY {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.category.is_some() {
+ panic!("{} can only be used at most once", CATEGORY);
+ }
+ let lit: syn::LitStr = content.parse()?;
+ let path: syn::Path = lit.parse()?;
Do I understand that this would be
category = "foo::bar::Baz"
? If so, why the extra quotes? There can actually be more than one
category, so at least add a TODO here.
+#[derive(Debug)]
+struct QdevProperty {
+ name: Option<syn::LitCStr>,
Just LitStr. Convert it to CString in the macro. You can reuse the
c_str!() macro that I'm adding in the series to fix CI and support old
rustc, i.e. quote! { ::qemu_api::c_str!(#name) } or something like that.
+ qdev_prop: Option<syn::Path>,
+}
+
+impl Parse for QdevProperty {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let _: syn::Token![#] = input.parse()?;
+ let bracketed;
+ _ = syn::bracketed!(bracketed in input);
+ assert_eq!(PROPERTY, bracketed.parse::<syn::Ident>()?);
+ let mut retval = Self {
+ name: None,
+ qdev_prop: None,
+ };
+ let content;
+ _ = syn::parenthesized!(content in bracketed);
+ while !content.is_empty() {
+ let value: syn::Ident = content.parse()?;
+ if value == NAME {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.name.is_some() {
+ panic!("{} can only be used at most once", NAME);
+ }
+ retval.name = Some(content.parse()?);
+ } else if value == QDEV_PROP {
+ let _: syn::Token![=] = content.parse()?;
+ if retval.qdev_prop.is_some() {
+ panic!("{} can only be used at most once", QDEV_PROP);
+ }
+ retval.qdev_prop = Some(content.parse()?);
+ } else {
+ panic!("unrecognized token `{}`", value);
+ }
+
+ if !content.is_empty() {
+ let _: syn::Token![,] = content.parse()?;
+ }
+ }
+ Ok(retval)
See above with respect to the duplicated code with #[device()].
+ let derive_container: DeriveContainer = input
+ .attrs
+ .iter()
+ .find(|a| a.path() == DEVICE)
+ .map(|a| syn::parse(a.to_token_stream().into()).expect("could not parse
device attr"))
+ .unwrap_or_default();
+ let (qdev_properties_static, qdev_properties_expanded) =
make_qdev_properties(&input);
Please put functions before their callers.
+ let realize_fn = format_ident!("__{}_realize_generated", name);
+ let reset_fn = format_ident!("__{}_reset_generated", name);
+
+ let expanded = quote! {
+ unsafe impl ::qemu_api::objects::DeviceImplUnsafe for #name {
+ const REALIZE: ::core::option::Option<
+ unsafe extern "C" fn(
+ dev: *mut ::qemu_api::bindings::DeviceState,
+ errp: *mut *mut ::qemu_api::bindings::Error,
+ ),
+ > = Some(#realize_fn);
+ const RESET: ::core::option::Option<
+ unsafe extern "C" fn(dev: *mut
::qemu_api::bindings::DeviceState),
+ > = Some(#reset_fn);
+ }
+
+ #[no_mangle]
Not needed.
+ pub unsafe extern "C" fn #realize_fn(
+ dev: *mut ::qemu_api::bindings::DeviceState,
+ errp: *mut *mut ::qemu_api::bindings::Error,
+ ) {
+ let mut instance =
NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null
pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::DeviceImpl::realize(instance.as_mut());
+ }
+ }
+
+ #[no_mangle]
Not needed.
+ pub unsafe extern "C" fn #reset_fn(
+ dev: *mut ::qemu_api::bindings::DeviceState,
+ ) {
+ let mut instance =
NonNull::new(dev.cast::<#name>()).expect(concat!("Expected dev to be a non-null
pointer of type ", stringify!(#name)));
+ unsafe {
+ ::qemu_api::objects::DeviceImpl::reset(instance.as_mut());
+ }
+ }
All this code depends on nothing but #name. This is not the C
preprocessor; the way to do it in Rust is monomorphization as described
above.
+fn gen_device_class(
+ derive_container: DeriveContainer,
+ qdev_properties_static: syn::Ident,
+ name: &syn::Ident,
+) -> proc_macro2::TokenStream {
+ let (class_name, class_def) = match (
+ derive_container.class_name_override,
+ derive_container.class_name,
+ ) {
+ (Some(class_name), _) => {
+ let class_expanded = quote! {
+ #[repr(C)]
+ pub struct #class_name {
+ _inner: [u8; 0],
+ }
+ };
+ (class_name, class_expanded)
+ }
+ (None, Some(class_name)) => (class_name, quote! {}),
+ (None, None) => {
+ let class_name = format_ident!("{}Class", name);
+ let class_expanded = quote! {
+ #[repr(C)]
+ pub struct #class_name {
+ _inner: [u8; 0],
+ }
This should have a DeviceClass member, it should not be a dummy 0-byte type.
Also, this should be generated by #[derive(Object)].
+ };
+ (class_name, class_expanded)
+ }
+ };
+ let class_init_fn = format_ident!("__{}_class_init_generated", class_name);
+ let class_base_init_fn = format_ident!("__{}_class_base_init_generated",
class_name);
+
+ let (vmsd, vmsd_impl) = {
+ let (i, vmsd) = make_vmstate(name);
+ (quote! { &#i }, vmsd)
+ };
+ let category = if let Some(category) = derive_container.category {
+ quote! {
+ const BITS_PER_LONG: u32 = ::core::ffi::c_ulong::BITS;
+ let _: ::qemu_api::bindings::DeviceCategory = #category;
+ let nr: ::core::ffi::c_ulong = #category as _;
+ let mask = 1 << (nr as u32 % BITS_PER_LONG);
+ let p =
::core::ptr::addr_of_mut!(dc.as_mut().categories).offset((nr as u32 /
BITS_PER_LONG) as isize);
+ let p: *mut ::core::ffi::c_ulong = p.cast();
+ let categories = p.read_unaligned();
+ p.write_unaligned(categories | mask);
What's wrong with
const BITS_PER_ELEMENT: u32 =
::core::mem::sizeof(dc.categories) /
dc.categories.len() * 8;
dc.categories[((nr as u32) / BITS_PER_ELEMENT) as usize]
|= 1 << ((nr as u32) % BITS_PER_ELEMENT);
?
+ #[no_mangle]
+ pub unsafe extern "C" fn #class_init_fn(klass: *mut ObjectClass, data:
*mut core::ffi::c_void) {
+ {
+ {
+ let mut dc =
+
::core::ptr::NonNull::new(klass.cast::<::qemu_api::bindings::DeviceClass>()).unwrap();
And then "let mut dc = dc.as_mut()". Just write the conversion once.
+ unsafe {
+ dc.as_mut().realize =
+ <#name as
::qemu_api::objects::DeviceImplUnsafe>::REALIZE;
+ ::qemu_api::bindings::device_class_set_legacy_reset(
+ dc.as_mut(),
+ <#name as
::qemu_api::objects::DeviceImplUnsafe>::RESET
+ );
As written elsewhere, these should be conditional.
+ dc.as_mut().vmsd = #vmsd;
+ #props
+ #category
> + }> + }
All this code should be outside the macro, and should use trait consts
instead of quoting.
+ let mut klass =
NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a
non-null pointer of type ", stringify!(#class_name)));
+ unsafe {
+ ::qemu_api::objects::ClassImpl::class_init(klass.as_mut(),
data);
+ }
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #class_base_init_fn(klass: *mut ObjectClass,
data: *mut core::ffi::c_void) {
+ {
+ let mut klass =
NonNull::new(klass.cast::<#class_name>()).expect(concat!("Expected klass to be a
non-null pointer of type ", stringify!(#class_name)));
+ unsafe {
+
::qemu_api::objects::ClassImpl::class_base_init(klass.as_mut(), data);
+ }
+ }
+ }
+
+ #vmsd_impl
+ }
+}
+
+fn make_vmstate(name: &syn::Ident) -> (syn::Ident, proc_macro2::TokenStream) {
Not needed. Just let the user provide a VMStateDescription in
DeviceImpl. I'm not sure if it's possible to make it a const; if not,
it can be a function returning a &'static VMStateDescription.
+ let vmstate_description_ident = format_ident!("__VMSTATE_{}", name);
+
+ let pre_load = format_ident!("__{}_pre_load_generated", name);
+ let post_load = format_ident!("__{}_post_load_generated", name);
+ let pre_save = format_ident!("__{}_pre_save_generated", name);
+ let post_save = format_ident!("__{}_post_save_generated", name);
+ let needed = format_ident!("__{}_needed_generated", name);
+ let dev_unplug_pending =
format_ident!("__{}_dev_unplug_pending_generated", name);
+
+ let migrateable_fish = quote! {<#name as
::qemu_api::objects::Migrateable>};
+ let vmstate_description = quote! {
+ #[used]
Attribute not needed.
+ #[allow(non_upper_case_globals)]
+ pub static #vmstate_description_ident:
::qemu_api::bindings::VMStateDescription =
::qemu_api::bindings::VMStateDescription {
+ name: if let Some(name) = #migrateable_fish::NAME {
+ name.as_ptr()
+ } else {
+ <#name as
::qemu_api::objects::ObjectImplUnsafe>::TYPE_INFO.name
+ },
+ unmigratable: #migrateable_fish::UNMIGRATABLE,
+ early_setup: #migrateable_fish::EARLY_SETUP,
+ version_id: #migrateable_fish::VERSION_ID,
+ minimum_version_id: #migrateable_fish::MINIMUM_VERSION_ID,
+ priority: #migrateable_fish::PRIORITY,
+ pre_load: Some(#pre_load),
+ post_load: Some(#post_load),
+ pre_save: Some(#pre_save),
+ post_save: Some(#post_save),
+ needed: Some(#needed),
+ dev_unplug_pending: Some(#dev_unplug_pending),
+ fields: ::core::ptr::null(),
+ subsections: ::core::ptr::null(),
+ };
+
+ #[no_mangle]
Not needed (other occurrences below).
+ pub unsafe extern "C" fn #pre_load(opaque: *mut ::core::ffi::c_void)
-> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected
opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::pre_load(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #post_load(opaque: *mut ::core::ffi::c_void,
version_id: core::ffi::c_int) -> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected
opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::post_load(instance.as_mut(),
version_id)
Again, introducing the Migrateable code and all these thunks is
premature; but in any case, this can only return 0 or -1 so make
Migrateable::post_load() return Result<(), ()>.
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #pre_save(opaque: *mut ::core::ffi::c_void)
-> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected
opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::pre_save(instance.as_mut())
+ }
+ }
Likewise.
+ #[no_mangle]
+ pub unsafe extern "C" fn #post_save(opaque: *mut ::core::ffi::c_void)
-> ::core::ffi::c_int {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected
opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::post_save(instance.as_mut())
+ }
+ }
Likewise.
+ #[no_mangle]
+ pub unsafe extern "C" fn #needed(opaque: *mut ::core::ffi::c_void) ->
bool {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected
opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+ ::qemu_api::objects::Migrateable::needed(instance.as_mut())
+ }
+ }
+ #[no_mangle]
+ pub unsafe extern "C" fn #dev_unplug_pending(opaque: *mut
::core::ffi::c_void) -> bool {
+ let mut instance = NonNull::new(opaque.cast::<#name>()).expect(concat!("Expected
opaque to be a non-null pointer of type ", stringify!(#name), "::Object"));
+ unsafe {
+
::qemu_api::objects::Migrateable::dev_unplug_pending(instance.as_mut())
+ }
+ }
+ };
+
+ let expanded = quote! {
+ #vmstate_description
+ };
+ (vmstate_description_ident, expanded)
+}
diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
index
59aba592d9ae4c5a4cdfdc6f9b9b08363b8a57e5..7753a853fae72fc87e6dc642cf076c6d0c736345
100644
--- a/rust/qemu-api-macros/src/lib.rs
+++ b/rust/qemu-api-macros/src/lib.rs
@@ -2,42 +2,21 @@
// Author(s): Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
+#![allow(dead_code)]
Why?
use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, DeriveInput};
+
+mod device;
+mod object;
+mod symbols;
+mod utilities;
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as DeriveInput);
-
- let name = input.ident;
- let module_static = format_ident!("__{}_LOAD_MODULE", name);
-
- let expanded = quote! {
- #[allow(non_upper_case_globals)]
- #[used]
- #[cfg_attr(target_os = "linux", link_section = ".ctors")]
- #[cfg_attr(target_os = "macos", link_section =
"__DATA,__mod_init_func")]
- #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
- pub static #module_static: extern "C" fn() = {
- extern "C" fn __register() {
- unsafe {
- ::qemu_api::bindings::type_register_static(&<#name as
::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
- }
- }
-
- extern "C" fn __load() {
- unsafe {
- ::qemu_api::bindings::register_module_init(
- Some(__register),
- ::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
- );
- }
- }
-
- __load
- };
- };
+ object::derive_object(input)
Moving code to a separate file should be a separate patch from modifying
the expansion of the macro.
diff --git a/rust/qemu-api-macros/src/symbols.rs
b/rust/qemu-api-macros/src/symbols.rs
new file mode 100644
index
0000000000000000000000000000000000000000..f73768d228ed2b4d478c18336db56cb11e70f012
--- /dev/null
+++ b/rust/qemu-api-macros/src/symbols.rs
@@ -0,0 +1,55 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use core::fmt;
+use syn::{Ident, Path};
+
+#[derive(Copy, Clone, Debug)]
+pub struct Symbol(&'static str);
+
+pub const DEVICE: Symbol = Symbol("device");
+pub const NAME: Symbol = Symbol("name");
+pub const CATEGORY: Symbol = Symbol("category");
+pub const CLASS_NAME: Symbol = Symbol("class_name");
+pub const CLASS_NAME_OVERRIDE: Symbol = Symbol("class_name_override");
+pub const QDEV_PROP: Symbol = Symbol("qdev_prop");
+pub const MIGRATEABLE: Symbol = Symbol("migrateable");
+pub const PROPERTIES: Symbol = Symbol("properties");
+pub const PROPERTY: Symbol = Symbol("property");
Declare these in device.rs as needed, not here. This avoids "use
symbols::*". It also allows making them not "pub", so that dead ones
are detected by the compiler (e.g. MIGRATEABLE, PROPERTIES).
+pub fn assert_is_repr_c_struct(input: &DeriveInput, derive_macro: &'static
str) {
Nice but a bit overengineered. Unless you think/know that you'll have a
use for Repr elsewhere, try sharing code with Junjie's macro
https://lore.kernel.org/qemu-devel/20241025160209.194307-16-pbonz...@redhat.com/.
Paolo