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.