On Fri, May 2, 2025 at 3:13 PM Paolo Bonzini <bonz...@gnu.org> wrote:
>
> It's been roughly three months since my previous update on the Rust in
> QEMU project.  Support for Rust remains experimental, with most of the
> past three months spent cleaning up the bindings and making more
> functionality available from safe Rust.
>
> As before, this mostly covers what I have looked at, which is making
> it possible to write devices in safe Rust.  Topics such as QAPI and
> async (block devices) are missing for this reason.
>
> Overall, I'd say the progress is good: most of the missing features
> mentioned in the previous update have been fixed or at least have a
> plan for the next few months.
>
> Table of contents
> '''''''''''''''''
>
> * Status in QEMU 10.0
> * Build system
> * Feature parity for devices
> * Remaining unsafe code
> * Rust version requirements
> * A coding style for devices
> * Next steps
>
>
> Status in QEMU 10.0
> '''''''''''''''''''
>
> QEMU when built with ``--enable-rust`` compiles on all supported
> build platforms.  It passes CI and ``make check-unit`` runs tests for
> rust/qemu-api.  ``make check-qtests`` covers the Rust pl011 and HPET
> device models, including migration of the former.  pl011 is entirely
> implemented using safe code (minus migration and qdev properties).
> HPET uses unsafe in some small and fairly well confined cases (see
> below).
>
> Since the previous update, some mistakes in the early bindings code
> have become apparent; in particular, orphan rules made it too hard
> to implement classes outside the qemu_api crate, and in general to
> split the qemu_api crate in multiple parts---for example, parts that
> are of interest to tools and parts that are only used by system
> emulators.  Another important change is the separation between
> bindgen-generated types and the structs that are actually used by
> Rust code.  This allows traits such as Send, Sync or Zeroable to be
> specified independently for C and Rust structs.
>
> Thanks to Kevin Wolf's work on the block layer a new module appeared
> to convert between C success/-errno conventions and ``io::Result``.
> This module is also used in character device bindings.
>
>
> Build system
> ''''''''''''
>
> Developers can use ninja to easily access clippy, rustfmt and rustdoc.
> Meson 1.8 supports clippy and rustdoc natively (including doctests),
> but due to some regressions in 1.8.0 this will have to wait for the
> next stable release.  This update to Meson will also make it possible
> to use --enable-modules and --enable-rust together.
>
> Rust is still not enabled and its presence is not checked for by
> default.  The main reason is that Rust staticlibs also link statically
> to the Rust standard library, thus bloating the resulting executable
> (and making distros hate us as well).  A pending Meson pull request[1]
> will fix this, as long as system/main.c is rewritten or wrapped in Rust.
>
> .. [1] https://github.com/mesonbuild/meson/pull/14224
>
>
> Feature parity for devices
> ''''''''''''''''''''''''''
>
> Support for HPET live migration is ready to be merged.
>
> As before, some recent pl011 commits are missing in the Rust version.
>
> Logging and tracing were proposed as a project for Google Summer of
> Code.
>
>
> Remaining unsafe code
> '''''''''''''''''''''
>
> qdev bindings cover basic classes and interfaces, including
> GPIO pins, timers, clocks and MemoryRegionOps.  VMState
> still needs unsafe callbacks for pre_save/post_load, with
> the final version waiting for a bump of the minimum supported
> Rust version to 1.83.0.
>
> Apart from VMState, the remaining instances of `unsafe` blocks in the
> pl011 and HPET code can all be removed without bumping the language
> version.
>
> HPET does some very simple memory accesses; a good safe solution
> for this may be the ``vm-memory`` crate.  While I have not looked into
> using it, ``vm-memory`` and ``vm-virtio`` were written with QEMU's
> use cases in mind.
>
> The ``instance_init`` method is using unsafe code.  There are multiple
> solutions to this: the one I planned for was to use a crate such as
> `pin_init <https://docs.rs/pin_init/>`__ or
> `pinned_init <https://docs.rs/pinned_init/>`__, but
> I have also worked for self-education on a simpler version based on
> ``std::mem::MaybeUninit`` field projections.  This one removes ``unsafe``
> only from the implementation and not from the ``instance_init`` method
> itself, but it is less invasive and could be a possibility in the
> short term.
>
> The amount of functionality available from safe Rust is enough that
> including new devices should be possible, even if they need some unsafe
> code for parts of QEMU that do not have bindings yet.  Most devices
> added to QEMU are simple and do not do any complex DMA; while such
> simple devices have very little benefit from *rewriting* them in Rust,
> there will be a substantial benefit to writing *new* devices in Rust as
> soon as tracing and logging are supported.  Even though unsafe code in
> migration and ``instance_init`` would count as technical debt for every
> Rust device that is added to QEMU, I don't expect a flood of Rust devices
> in the next few months such that this would be a problem.
>
> There is still no interoperability between QEMU's C data structure and
> Rust counterparts has no news either.  As before, we'll figure it out
> as soon as we need a realize() implementation that can fail, or when
> tackling QAPI.
>
>
> Rust version requirements
> '''''''''''''''''''''''''
>
> Patches are on the list (and have mostly been reviewed) to bump the
> minimum supported Rust version to 1.77.0.  However, there will probably
> be at least one more bump to support references to statics in constants,
> which are stable in 1.83.0 and are important for migration support in
> safe Rust.
>
> This will require dropping support for ``--enable-rust`` on Debian
> bookworm with a distro-provided compiler.  If any devices are contributed
> that are written in Rust and do not have a C counterpart, it may be
> worth splitting "enable Rust" from "enable all devices written in Rust".
> This way, the C versions of the pl011 and HPET devices remain available
> on bookworm.
>
>
> A coding style for devices
> ''''''''''''''''''''''''''
>
> pl011 and HPET were developed independently and sometimes have different
> idioms that could be unified.  Peter Maydell made several observations:
>
>    Something I do notice is that there's some inconsistency in
>    how we've structured things between the two devices, e.g.:
>
>    * the pl011 main source file is device.rs, but the hpet one
>      is hpet.rs
>
>    * some places we use the actual names of bits in registers
>      (eg Interrupt's OE, BE, etc consts), and some places we
>      seem to have renamed them (e.g. pl011 Flags has clear_to_send
>      not CTS, etc)
>
>    * pl011 has defined named fields for its registers, but hpet does
>      things like::
>
>         self.config.get() & (1 << HPET_CFG_LEG_RT_SHIFT) != 0
>
>    * pl011 has a split between PL011State and PL011Registers,
>      but HPET does not. As I mentioned in an email thread a
>      little while back, I feel like the State/Registers split
>      is something we should either make a more clear deliberate
>      formalised separation that's part of how we recommend
>      device models should be designed
>
>    [...]
>
>    I think it would be good to figure out what we think is the
>    right, best style, for writing this kind of thing, and be
>    consistent. We have long standing problems in the C device
>    models where there are multiple different styles for how
>    we write them, and it would be good to try to aim for
>    more uniformity on the Rust side.

The pl011 stuff was deliberate decisions:

- device.rs vs pl011.rs: the device was written as a crate, so it's
essentially its own library, plus pl011/src/pl011.rs would be
redundant :)
  That said, it's not important, we can choose either convention. I
like the less redundancy and separation of concerns: if pl011 gets
converted into a module in a future refactor, it could keep its
functionality split into different submodules and `pl011.rs` or
`pl011/mod.rs` would be the device module.
  Rust's concept of files being either a leaf module or parent module
does not translate to C's "every file is a compilation unit" cleanly.
- Using typed registers instead of constants: yes coming from C I can
understand it can feel unfamiliar. I specifically wanted to make the
register fields typed to avoid making the implementation a "C port",
and I think it's worthwhile to use the type system as much as
possible.
  A straight C port would result into integer constants with integer
typed fields everywhere for registers/flags.
  Yes, From/Into aren't const, at least yet, but it's not really a
hotpath performance wise. I think non-dynamically dispatched trait
methods can be inlined by annotating the `fn from(..)` impl with a
`#[inline(always)]` hint but I haven't confirmed this, just
speculation.
  Again, no strong opinions here. I like the "everything is typed"
approach and I think it's worth it to explore it because it allows us
to "make invalid/illegal states unrepresentable" as one sage article
goes.

>
> One thing that I noticed is that in Rust QEMU code I tend to rely on
> ``const`` and ``static`` a lot, and several crates are not friendly
> to this style, including the ``bilge`` crate that we use for named
> fields and others such as ``bitflags``.  In both cases, this is
> related to Rust not having const traits, e.g. for ``from()``/into()``
> or operator overloading).  I already have a prototype of a bitflags-like
> macro that is more const friendly, and we also need to make a decision
> on whether to keep using ``bilge``, fork it, rewrite it or whatever.
>
>
> Next steps
> ''''''''''
>
> With respect to missing functionality, tracepoints and logging remain
> the highest-priority missing feature, perhaps together with DMA, and the
> main blocker before implementing new devices in Rust can be encouraged.
> Hopefully this hole will be closed over the summer.
>
> On the experimental side, if anybody wants to play with the ``vm-memory``
> crate for DMA that would be very interesting.  However, the next steps
> I am suggesting are mostly along the lines of cleaning up what is there,
> ensuring that we're ready for more widespread usage of Rust in QEMU.
>
> If someone like menial work, splitting the ``qemu_api`` crate is now
> possible and a good thing to do.
>
> If someone has good taste, they might go over the code with Peter's
> above remarks in mind, cleaning up things so that pl011 and HPET both
> provide good examples of Rust code in QEMU.
>
> I also believe it's time to look again at using procedural macros to
> simplify declaring QOM/qdev classes.  For example::
>
>      #[derive(qemu_api_macros::Object(class_name="pl011", class=PL011Class))]
>      #[derive(qemu_api_macros::Device(vmsd=VMSTATE_HPET))
>      pub struct PL011State {
>          pub parent_obj: ParentField<SysBusDevice>,
>          pub iomem: MemoryRegion,
>          pub regs: BqlRefCell<PL011Registers>,
>          pub interrupts: [InterruptSource; IRQMASK.len()],
>          pub clock: Owned<Clock>,
>
>          #[qemu_api_macros::property(name="chr")]
>          pub char_backend: CharBackend,
>
>          #[qemu_api_macros::property(name="migrate-clk", default=true)]
>          pub migrate_clock: bool,
>      }
>
> Related to this I have found recently the `attrs crate
> <https://docs.rs/attrs/>`__, which provides an easy way to parse the
> contents of attributes in a procedural macro.

I actually have some WIP patches for this I put a pause on and can
continue e.g. 
https://gitlab.com/epilys/rust-for-qemu/-/commit/c2c97caaaf03273fabc14aee5a4d1499668ddbe3

Reply via email to