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(),
>>> + };
>>> + }
>>> +}
next prev parent 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).