qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Paolo Bonzini <pbonzini@redhat.com>
To: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>,
	qemu-devel@nongnu.org
Cc: "Peter Maydell" <peter.maydell@linaro.org>,
	"Marc-André Lureau" <marcandre.lureau@redhat.com>,
	"Daniel P. Berrangé" <berrange@redhat.com>,
	"Philippe Mathieu-Daudé" <philmd@linaro.org>,
	"Alex Bennée" <alex.bennee@linaro.org>,
	"Thomas Huth" <thuth@redhat.com>,
	"Junjie Mao" <junjie.mao@hotmail.com>,
	"Junjie Mao" <junjie.mao@intel.com>,
	"Zhao Liu" <zhao1.liu@intel.com>, "Kevin Wolf" <kwolf@redhat.com>,
	"Richard Henderson" <richard.henderson@linaro.org>,
	"Gustavo Romero" <gustavo.romero@linaro.org>,
	"Pierrick Bouvier" <pierrick.bouvier@linaro.org>
Subject: Re: [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro
Date: Sun, 27 Oct 2024 21:58:00 +0100	[thread overview]
Message-ID: <f7d087fe-e9cb-4021-b816-df43656e22f5@redhat.com> (raw)
In-Reply-To: <20241024-rust-round-2-v1-3-051e7a25b978@linaro.org>

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-pbonzini@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.pitsidianakis@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-pbonzini@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.pitsidianakis@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.pitsidianakis@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-pbonzini@redhat.com/.

Paolo



  parent reply	other threads:[~2024-10-27 20:59 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-10-24 14:02 [PATCH 00/11] Rust device model patches and misc cleanups Manos Pitsidianakis
2024-10-24 14:02 ` [PATCH 01/11] Revert "rust: add PL011 device model" Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 02/11] rust: add PL011 device model Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 03/11] rust/qemu-api-macros: introduce Device proc macro Manos Pitsidianakis
2024-10-24 15:13   ` Alex Bennée
2024-10-24 17:06     ` Manos Pitsidianakis
2024-10-25 12:01   ` Paolo Bonzini
2024-10-25 14:04     ` Manos Pitsidianakis
2024-10-25 15:22       ` Paolo Bonzini
2024-10-25 16:22         ` Manos Pitsidianakis
2024-10-27 20:58   ` Paolo Bonzini [this message]
2024-10-27 22:39     ` Manos Pitsidianakis
2024-10-28  7:07       ` Paolo Bonzini
2024-10-24 14:03 ` [PATCH 04/11] rust: add support for migration in device models Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 05/11] rust/pl011: move CLK_NAME static to function scope Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 06/11] rust/pl011: add TYPE_PL011_LUMINARY device Manos Pitsidianakis
2024-10-24 17:27   ` Zhao Liu
2024-10-24 14:03 ` [PATCH 07/11] rust/pl011: move pub callback decl to local scope Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 08/11] rust/pl011: remove commented out C code Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 09/11] rust/pl011: Use correct masks for IBRD and FBRD Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 10/11] rust/qemu-api: add log module Manos Pitsidianakis
2024-10-24 14:03 ` [PATCH 11/11] rust/pl011: log guest/unimp errors Manos Pitsidianakis
2024-10-25  9:33 ` [PATCH 00/11] Rust device model patches and misc cleanups Paolo Bonzini
2024-10-26 10:06   ` Manos Pitsidianakis
2024-10-27  8:13     ` Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f7d087fe-e9cb-4021-b816-df43656e22f5@redhat.com \
    --to=pbonzini@redhat.com \
    --cc=alex.bennee@linaro.org \
    --cc=berrange@redhat.com \
    --cc=gustavo.romero@linaro.org \
    --cc=junjie.mao@hotmail.com \
    --cc=junjie.mao@intel.com \
    --cc=kwolf@redhat.com \
    --cc=manos.pitsidianakis@linaro.org \
    --cc=marcandre.lureau@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=philmd@linaro.org \
    --cc=pierrick.bouvier@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    --cc=thuth@redhat.com \
    --cc=zhao1.liu@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).