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.

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.

Reply via email to