qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Junjie Mao <junjie.mao@intel.com>
To: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>,
	<qemu-devel@nongnu.org>
Cc: "Paolo Bonzini" <pbonzini@redhat.com>,
	"Marc-Andr é Lureau" <marcandre.lureau@redhat.com>,
	"Daniel P. Berrangé" <berrange@redhat.com>,
	"Thomas Huth" <thuth@redhat.com>,
	"Philippe Mathieu-Daud é" <philmd@linaro.org>,
	"Pierrick Bouvier" <pierrick.bouvier@linaro.org>,
	"Richard Henderson" <richard.henderson@linaro.org>,
	"Gustavo Romero" <gustavo.romero@linaro.org>,
	"Alex Benn é e" <alex.bennee@linaro.org>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Zhao Liu" <zhao1.liu@intel.com>
Subject: Re: [PATCH v8 6/8] rust: add crate to expose bindings and interfaces
Date: Mon, 26 Aug 2024 16:41:06 +0800	[thread overview]
Message-ID: <38860145-c338-40ee-b1e1-1983dab77695@intel.com> (raw)
In-Reply-To: <itblf.by425lac4ow@linaro.org>

On 8/26/2024 2:12 PM, Manos Pitsidianakis wrote:
> On Mon, 26 Aug 2024 08:03, Junjie Mao <junjie.mao@intel.com> wrote:
>> Hi Manos,
>>
>> On 8/23/2024 4:11 PM, Manos Pitsidianakis wrote:
>>> Add rust/qemu-api, which exposes rust-bindgen generated FFI bindings and
>>> provides some declaration macros for symbols visible to the rest of
>>> QEMU.
>>>
>>> Co-authored-by: Junjie Mao <junjie.mao@intel.com>
>>> Co-authored-by: Paolo Bonzini <pbonzini@redhat.com>
>>> Signed-off-by: Junjie Mao <junjie.mao@intel.com>
>>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>>> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
>>> ---
>>>   MAINTAINERS                       |   6 ++
>>>   rust/meson.build                  |   1 +
>>>   rust/qemu-api/.gitignore          |   2 +
>>>   rust/qemu-api/Cargo.lock          |   7 +++
>>>   rust/qemu-api/Cargo.toml          |  26 ++++++++
>>>   rust/qemu-api/README.md           |  17 +++++
>>>   rust/qemu-api/build.rs            |  14 +++++
>>>   rust/qemu-api/meson.build         |  20 ++++++
>>>   rust/qemu-api/rustfmt.toml        |   1 +
>>>   rust/qemu-api/src/definitions.rs  | 109 ++++++++++++++++++++++++++++++++
>>>   rust/qemu-api/src/device_class.rs | 128 ++++++++++++++++++++++++++++++++++++++
>>>   rust/qemu-api/src/lib.rs          | 102 ++++++++++++++++++++++++++++++
>>>   rust/qemu-api/src/tests.rs        |  49 +++++++++++++++
>>>   rust/rustfmt.toml                 |   7 +++
>>>   14 files changed, 489 insertions(+)
>>>
[snip]
>>> diff --git a/rust/qemu-api/src/definitions.rs b/rust/qemu-api/src/definitions.rs
>>> new file mode 100644
>>> index 0000000000..4abd0253bd
>>> --- /dev/null
>>> +++ b/rust/qemu-api/src/definitions.rs
>>> @@ -0,0 +1,109 @@
>>> +// Copyright 2024, Linaro Limited
>>> +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
>>> +// SPDX-License-Identifier: GPL-2.0-or-later
>>> +
>>> +//! Definitions required by QEMU when registering a device.
>>> +
>>> +/// Trait a type must implement to be registered with QEMU.
>>> +pub trait ObjectImpl {
>>> +    type Class;
>>> +    const TYPE_INFO: crate::bindings::TypeInfo;
>>> +    const TYPE_NAME: &'static ::core::ffi::CStr;
>>> +    const PARENT_TYPE_NAME: Option<&'static ::core::ffi::CStr>;
>>> +    const INSTANCE_INIT: ::core::option::Option<
>>> +        unsafe extern "C" fn(obj: *mut crate::bindings::Object),
>>> +    >;
>>> +    const INSTANCE_POST_INIT: ::core::option::Option<
>>> +        unsafe extern "C" fn(obj: *mut crate::bindings::Object),
>>> +    >;
>>> +    const INSTANCE_FINALIZE: ::core::option::Option<
>>> +        unsafe extern "C" fn(obj: *mut crate::bindings::Object),
>>> +    >;
>>> +    const ABSTRACT: bool;
>>> +}
>>> +
>>> +pub trait Class {
>>> +    const CLASS_INIT: ::core::option::Option<
>>> +        unsafe extern "C" fn(
>>> +            klass: *mut crate::bindings::ObjectClass,
>>> +            data: *mut core::ffi::c_void,
>>> +        ),
>>> +    >;
>>> +    const CLASS_BASE_INIT: ::core::option::Option<
>>> +        unsafe extern "C" fn(
>>> +            klass: *mut crate::bindings::ObjectClass,
>>> +            data: *mut core::ffi::c_void,
>>> +        ),
>>> +    >;
>>> +}
>>> +
>>> +#[macro_export]
>>> +macro_rules! module_init {
>>> +    ($func:expr, $type:expr) => {
>>> +        #[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 LOAD_MODULE: extern "C" fn() = {
>>> +            assert!($type < 
>>> $crate::bindings::module_init_type_MODULE_INIT_MAX);
>>> +
>>> +            extern "C" fn __load() {
>>> +                unsafe {
>>> +                    $crate::bindings::register_module_init(Some($func), $type);
>>> +                }
>>> +            }
>>> +
>>> +            __load
>>> +        };
>>> +    };
>>> +    (qom: $func:ident => $body:block) => {
>>
>> This arm looks duplicating what #[derive(Object)] is for, while both have 
>> their strengths and limitations: module_init!() provides more flexibility on 
>> the registration function body, and #[derive(Object)] is much more convenient 
>> to use.
>>
>> Complex registration functions are not rare, and thus the Rust APIs should 
>> ideally have both strengths: clean type declaration in most cases, and full 
>> flexibility when needed. In the current codebase, we have ~1080 uses of 
>> type_init(), with 750 of them having a registration function as simple as a 
>> single call to type_register_static() (disclaimer: those numbers are collected 
>> via brute-force searches and may not be accurate). More complex cases include:
>>
>> 1. Registering multiple types (e.g., multiple models of same device) that 
>> share the same data structure, e.g., hw/misc/aspeed_xdma.c and 
>> hw/xtensa/xtfpga.c. There are ~200 uses of this kind in the codebase.
>>
>> 2. Use domain-specific registration function, e.g., ui/egl-headless.c, 
>> audio/ossaudio.c and hw/virtio/virtio-net-pci.c.
>>
>> 3. Other device-specific operations, e.g., hw/net/spapr_llan.c.
>>
> 
> This is why I left the decl macro, I was prototyping this series with a second 
> Rust device (that is not included in the patches) and I needed more logic in the 
> module init.
> 
>> My rough idea is to define a proc macro around an impl block to collect 
>> constants (type names, parent names, etc.) as tokens and callbacks (class 
>> init, instance init, etc.) as functions, from which we generate TypeInfo and 
>> (optionally) type registration code. As an example:
> 
> Do you think we should not use a trait to define type info at all by the way?
> 

I'm not sure, to be honest. Traits are a great way to specify a series of 
functions and mostly fit our needs here. I'm still thinking about how a 
trait-based approach works for multi-model devices where multiple TypeInfo can 
be defined for one structure. A naive way is to define one struct for each 
TypeInfo, which should work but does not look perfect to me.

>>
>>   pub struct PL011State {
>>     ...
>>   }
>>
>>   #[qemu_type(name = "pl011", parent = TYPE_SYS_BUS_DEVICE, (abstract)*)]
>>   impl PL011State {
>>     #[class_init]
>>     pub fn class_init(klass: *mut ObjectClass, data: *mut core::ffi::c_void) {
>>       ...
>>     }
>>
>>     #[instance_init]
>>     pub fn init(obj: *mut Object) { ... }
>>
>>     ...
>>   }
>>
>> The proc macro then generates a TypeInfo instance named TYPE_INFO_pl011, with 
>> optional callbacks being None when not given. A registration function will 
>> also be generated unless qemu_type! has a no_register token.
> 
> Maybe this too can be a trait method people can override with a blank 
> implementation to avoid registration...
> 

Agree.

>> Devices can still use module_init! to define their own registration function.
>>
>> The class_init callback is specified together with instance_init because it is 
>> common for multi-model devices to provide a different class_init even they 
>> share the same class structure. Refer to hw/misc/aspeed_xdma.c for an example.
> 
> Thanks I will take a look. QEMU Classes are a bit complex indeed.
> 
>>
>> What do you think? It is still preliminary and the example can have 
>> grammatical issues, but I can try drafting if you think that is a good direction.
> 
> In my plan I wanted to eventually have all these callbacks available to Rust 
> code via trait methods which only take rust references instead of pointers. Then 
> the proc macros would generate extern "C" wrappers for each of them, make a 
> typeinfo declaration, set everything up. I like your approach too. Should we 
> wait until we have an actual device that requires redesigning this? We're free 
> to change things anyway.
> 

Your idea looks promising, too, esp. the "take rust references" part. It may be 
difficult to figure out if a callback should be None since the trait will always 
define a default, empty implementation, but the performance overhead of calling 
empty constructors / destructors should be negligible.

I agree that we'd better try a variety of device types to better understand 
different use cases before we conclude on the API design. I'll also try 
prototyping some device for a deeper understanding of the current APIs.

---
Best Regards
Junjie Mao

>>
>> ---
>> Best Regards
>> Junjie Mao
>>
>>> +        // NOTE: To have custom identifiers for the ctor func we need to 
>>> either supply
>>> +        // them directly as a macro argument or create them with a proc macro.
>>> +        #[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 LOAD_MODULE: extern "C" fn() = {
>>> +            extern "C" fn __load() {
>>> +                #[no_mangle]
>>> +                unsafe extern "C" fn $func() {
>>> +                    $body
>>> +                }
>>> +
>>> +                unsafe {
>>> +                    $crate::bindings::register_module_init(
>>> +                        Some($func),
>>> +                        $crate::bindings::module_init_type_MODULE_INIT_QOM,
>>> +                    );
>>> +                }
>>> +            }
>>> +
>>> +            __load
>>> +        };
>>> +    };
>>> +}
>>> +
>>> +#[macro_export]
>>> +macro_rules! type_info {
>>> +    ($t:ty) => {
>>> +        $crate::bindings::TypeInfo {
>>> +            name: <$t as $crate::definitions::ObjectImpl>::TYPE_NAME.as_ptr(),
>>> +            parent: if let Some(pname) = <$t as 
>>> $crate::definitions::ObjectImpl>::PARENT_TYPE_NAME {
>>> +                pname.as_ptr()
>>> +            } else {
>>> +                ::core::ptr::null_mut()
>>> +            },
>>> +            instance_size: ::core::mem::size_of::<$t>(),
>>> +            instance_align: ::core::mem::align_of::<$t>(),
>>> +            instance_init: <$t as 
>>> $crate::definitions::ObjectImpl>::INSTANCE_INIT,
>>> +            instance_post_init: <$t as 
>>> $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
>>> +            instance_finalize: <$t as 
>>> $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
>>> +            abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
>>> +            class_size:  ::core::mem::size_of::<<$t as 
>>> $crate::definitions::ObjectImpl>::Class>(),
>>> +            class_init: <<$t as $crate::definitions::ObjectImpl>::Class as 
>>> $crate::definitions::Class>::CLASS_INIT,
>>> +            class_base_init: <<$t as $crate::definitions::ObjectImpl>::Class 
>>> as $crate::definitions::Class>::CLASS_BASE_INIT,
>>> +            class_data: ::core::ptr::null_mut(),
>>> +            interfaces: ::core::ptr::null_mut(),
>>> +        };
>>> +    }
>>> +}


  reply	other threads:[~2024-08-26  8:42 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-08-23  8:11 [PATCH v8 0/8] Add Rust build support, ARM PL011 device impl Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 1/8] Require meson version 1.5.0 Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 2/8] build-sys: Add rust feature option Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 3/8] configure, meson: detect Rust toolchain Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 4/8] rust: add bindgen step as a meson dependency Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 5/8] .gitattributes: add Rust diff and merge attributes Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 6/8] rust: add crate to expose bindings and interfaces Manos Pitsidianakis
2024-08-26  5:03   ` Junjie Mao
2024-08-26  6:12     ` Manos Pitsidianakis
2024-08-26  8:41       ` Junjie Mao [this message]
2024-08-26  5:31   ` Junjie Mao
2024-08-26  6:41     ` Manos Pitsidianakis
2024-08-26  7:45       ` Junjie Mao
2024-08-26  8:24       ` Thomas Huth
2024-08-26 11:29         ` Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 7/8] rust: add utility procedural macro crate Manos Pitsidianakis
2024-08-26  5:15   ` Junjie Mao
2024-08-26  6:02     ` Manos Pitsidianakis
2024-08-23  8:11 ` [PATCH v8 8/8] rust: add PL011 device model Manos Pitsidianakis

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=38860145-c338-40ee-b1e1-1983dab77695@intel.com \
    --to=junjie.mao@intel.com \
    --cc=alex.bennee@linaro.org \
    --cc=berrange@redhat.com \
    --cc=gustavo.romero@linaro.org \
    --cc=manos.pitsidianakis@linaro.org \
    --cc=marcandre.lureau@redhat.com \
    --cc=pbonzini@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).