| .. |msrv| replace:: 1.83.0 | |
| Rust in QEMU | |
| ============ | |
| Rust in QEMU is a project to enable using the Rust programming language | |
| to add new functionality to QEMU. | |
| Right now, the focus is on making it possible to write devices that inherit | |
| from ``SysBusDevice`` in `*safe*`__ Rust. Later, it may become possible | |
| to write other kinds of devices (e.g. PCI devices that can do DMA), | |
| complete boards, or backends (e.g. block device formats). | |
| __ https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html | |
| Building the Rust in QEMU code | |
| ------------------------------ | |
| The Rust in QEMU code is included in the emulators via Meson. Meson | |
| invokes rustc directly, building static libraries that are then linked | |
| together with the C code. This is completely automatic when you run | |
| ``make`` or ``ninja``. | |
| However, QEMU's build system also tries to be easy to use for people who | |
| are accustomed to the more "normal" Cargo-based development workflow. | |
| In particular: | |
| * the set of warnings and lints that are used to build QEMU always | |
| comes from the ``rust/Cargo.toml`` workspace file | |
| * it is also possible to use ``cargo`` for common Rust-specific coding | |
| tasks, in particular to invoke ``clippy``, ``rustfmt`` and ``rustdoc``. | |
| To this end, QEMU includes a ``build.rs`` build script that picks up | |
| generated sources from QEMU's build directory and puts it in Cargo's | |
| output directory (typically ``rust/target/``). A vanilla invocation | |
| of Cargo will complain that it cannot find the generated sources, | |
| which can be fixed in different ways: | |
| * by using Makefile targets, provided by Meson, that run ``clippy`` or | |
| ``rustdoc``: | |
| make clippy | |
| make rustdoc | |
| A target for ``rustfmt`` is also declared in ``rust/meson.build``: | |
| make rustfmt | |
| * by invoking ``cargo`` through the Meson `development environment`__ | |
| feature:: | |
| pyvenv/bin/meson devenv -w ../rust cargo clippy --tests | |
| pyvenv/bin/meson devenv -w ../rust cargo fmt | |
| If you are going to use ``cargo`` repeatedly, ``pyvenv/bin/meson devenv`` | |
| will enter a shell where commands like ``cargo fmt`` just work. | |
| __ https://mesonbuild.com/Commands.html#devenv | |
| * by pointing the ``MESON_BUILD_ROOT`` to the top of your QEMU build | |
| tree. This third method is useful if you are using ``rust-analyzer``; | |
| you can set the environment variable through the | |
| ``rust-analyzer.cargo.extraEnv`` setting. | |
| As shown above, you can use the ``--tests`` option as usual to operate on test | |
| code. Note however that you cannot *build* or run tests via ``cargo``, because | |
| they need support C code from QEMU that Cargo does not know about. Tests can | |
| be run via Meson (``pyvenv/bin/meson test``) or ``make``:: | |
| make check-rust | |
| Note that doctests require all ``.o`` files from the build to be available. | |
| Supported tools | |
| ''''''''''''''' | |
| QEMU supports rustc version 1.83.0 and newer. The following features | |
| from relatively new versions of Rust are not used for historical reasons; | |
| patches are welcome: | |
| * associated constants are still explicitly marked ``'static`` (`changed in | |
| 1.81.0`__) | |
| * ``&raw`` (stable in 1.82.0). | |
| * NUL-terminated file names with ``#[track_caller]`` are scheduled for | |
| inclusion as ``#![feature(location_file_nul)]``, but it will be a while | |
| before QEMU can use them. For now, there is special code in | |
| ``util/error.c`` to support non-NUL-terminated file names. | |
| Associated const equality would be nice to have for some users of | |
| ``callbacks::FnCall``, but is still experimental. Const assertions | |
| are used instead. | |
| __ https://github.com/rust-lang/rust/pull/125258 | |
| QEMU also supports version 0.60.x of bindgen, which is missing option | |
| ``--generate-cstr``. This option requires version 0.66.x and will | |
| be adopted as soon as supporting these older versions is not necessary | |
| anymore. | |
| Writing Rust code in QEMU | |
| ------------------------- | |
| QEMU includes several crates: | |
| * ``common`` provides Rust-only utilities | |
| * ``bql``, ``chardev``, ``hw/core``, ``migration``, ``qom``, ``system``, | |
| ``util`` for bindings to respective QEMU C library APIs | |
| * ``qemu_macros`` defines several procedural macros that are useful when | |
| writing C code | |
| * ``pl011`` (under ``rust/hw/char/pl011``) and ``hpet`` (under ``rust/hw/timer/hpet``) | |
| are sample devices that demonstrate Rust binding usage and ``qemu_macros``, and are | |
| used to further develop them. These two crates are functional\ [#issues]_ replacements | |
| for the ``hw/char/pl011.c`` and ``hw/timer/hpet.c`` files. | |
| .. [#issues] The ``pl011`` crate is synchronized with ``hw/char/pl011.c`` | |
| as of commit 3e0f118f82. The ``hpet`` crate is synchronized as of | |
| commit 1433e38cc8. Both are lacking tracing functionality. | |
| This section explains how to work with them. | |
| Status | |
| '''''' | |
| The stability of the modules can be defined as: | |
| - *complete*: ready for use in new devices; if applicable, the API supports the | |
| full functionality available in C | |
| - *stable*: ready for production use, the API is safe and should not undergo | |
| major changes | |
| - *proof of concept*: the API is subject to change but allows working with safe | |
| Rust | |
| - *initial*: the API is in its initial stages; it requires large amount of | |
| unsafe code; it might have soundness or type-safety issues | |
| The status of the modules is as follows: | |
| ========================== ====================== | |
| module status | |
| ========================== ====================== | |
| ``bql::cell`` stable | |
| ``common::assertions`` stable | |
| ``common::bitops`` complete | |
| ``common::callbacks`` complete | |
| ``common::errno`` complete | |
| ``common::zeroable`` stable | |
| ``hwcore::irq`` complete | |
| ``hwcore::qdev`` stable | |
| ``hwcore::sysbus`` stable | |
| ``migration::migratable`` proof of concept | |
| ``migration::vmstate`` stable | |
| ``qom`` stable | |
| ``system::memory`` stable | |
| ``util::error`` stable | |
| ``util::log`` proof of concept | |
| ``util::module`` complete | |
| ``util::timer`` stable | |
| ========================== ====================== | |
| .. note:: | |
| API stability is not a promise, if anything because the C APIs are not a stable | |
| interface either. Also, ``unsafe`` interfaces may be replaced by safe interfaces | |
| later. | |
| Naming convention | |
| ''''''''''''''''' | |
| C function names usually are prefixed according to the data type that they | |
| apply to, for example ``timer_mod`` or ``sysbus_connect_irq``. Furthermore, | |
| both function and structs sometimes have a ``qemu_`` or ``QEMU`` prefix. | |
| Generally speaking, these are all removed in the corresponding Rust functions: | |
| ``QEMUTimer`` becomes ``timer::Timer``, ``timer_mod`` becomes ``Timer::modify``, | |
| ``sysbus_connect_irq`` becomes ``SysBusDeviceMethods::connect_irq``. | |
| Sometimes however a name appears multiple times in the QOM class hierarchy, | |
| and the only difference is in the prefix. An example is ``qdev_realize`` and | |
| ``sysbus_realize``. In such cases, whenever a name is not unique in | |
| the hierarchy, always add the prefix to the classes that are lower in | |
| the hierarchy; for the top class, decide on a case by case basis. | |
| For example: | |
| ========================== ========================================= | |
| ``device_cold_reset()`` ``DeviceMethods::cold_reset()`` | |
| ``pci_device_reset()`` ``PciDeviceMethods::pci_device_reset()`` | |
| ``pci_bridge_reset()`` ``PciBridgeMethods::pci_bridge_reset()`` | |
| ========================== ========================================= | |
| Here, the name is not exactly the same, but nevertheless ``PciDeviceMethods`` | |
| adds the prefix to avoid confusion, because the functionality of | |
| ``device_cold_reset()`` and ``pci_device_reset()`` is subtly different. | |
| In this case, however, no prefix is needed: | |
| ========================== ========================================= | |
| ``device_realize()`` ``DeviceMethods::realize()`` | |
| ``sysbus_realize()`` ``SysbusDeviceMethods::sysbus_realize()`` | |
| ``pci_realize()`` ``PciDeviceMethods::pci_realize()`` | |
| ========================== ========================================= | |
| Here, the lower classes do not add any functionality, and mostly | |
| provide extra compile-time checking; the basic *realize* functionality | |
| is the same for all devices. Therefore, ``DeviceMethods`` does not | |
| add the prefix. | |
| Whenever a name is unique in the hierarchy, instead, you should | |
| always remove the class name prefix. | |
| Common pitfalls | |
| ''''''''''''''' | |
| Rust has very strict rules with respect to how you get an exclusive (``&mut``) | |
| reference; failure to respect those rules is a source of undefined behavior. | |
| In particular, even if a value is loaded from a raw mutable pointer (``*mut``), | |
| it *cannot* be casted to ``&mut`` unless the value was stored to the ``*mut`` | |
| from a mutable reference. Furthermore, it is undefined behavior if any | |
| shared reference was created between the store to the ``*mut`` and the load:: | |
| let mut p: u32 = 42; | |
| let p_mut = &mut p; // 1 | |
| let p_raw = p_mut as *mut u32; // 2 | |
| // p_raw keeps the mutable reference "alive" | |
| let p_shared = &p; // 3 | |
| println!("access from &u32: {}", *p_shared); | |
| // Bring back the mutable reference, its lifetime overlaps | |
| // with that of a shared reference. | |
| let p_mut = unsafe { &mut *p_raw }; // 4 | |
| println!("access from &mut 32: {}", *p_mut); | |
| println!("access from &u32: {}", *p_shared); // 5 | |
| These rules can be tested with `MIRI`__, for example. | |
| __ https://github.com/rust-lang/miri | |
| Almost all Rust code in QEMU will involve QOM objects, and pointers to these | |
| objects are *shared*, for example because they are part of the QOM composition | |
| tree. This creates exactly the above scenario: | |
| 1. a QOM object is created | |
| 2. a ``*mut`` is created, for example as the opaque value for a ``MemoryRegion`` | |
| 3. the QOM object is placed in the composition tree | |
| 4. a memory access dereferences the opaque value to a ``&mut`` | |
| 5. but the shared reference is still present in the composition tree | |
| Because of this, QOM objects should almost always use ``&self`` instead | |
| of ``&mut self``; access to internal fields must use *interior mutability* | |
| to go from a shared reference to a ``&mut``. | |
| Whenever C code provides you with an opaque ``void *``, avoid converting it | |
| to a Rust mutable reference, and use a shared reference instead. The | |
| ``bql::cell`` module provides wrappers that can be used to tell the | |
| Rust compiler about interior mutability, and optionally to enforce locking | |
| rules for the "Big QEMU Lock". In the future, similar cell types might | |
| also be provided for ``AioContext``-based locking as well. | |
| In particular, device code will usually rely on the ``BqlRefCell`` and | |
| ``BqlCell`` type to ensure that data is accessed correctly under the | |
| "Big QEMU Lock". These cell types are also known to the ``vmstate`` | |
| crate, which is able to "look inside" them when building an in-memory | |
| representation of a ``struct``'s layout. Note that the same is not true | |
| of a ``RefCell`` or ``Mutex``. | |
| Bindings code instead will usually use the ``Opaque`` type, which hides | |
| the contents of the underlying struct and can be easily converted to | |
| a raw pointer, for use in calls to C functions. It can be used for | |
| example as follows:: | |
| #[repr(transparent)] | |
| #[derive(Debug, common::Wrapper)] | |
| pub struct Object(Opaque<bindings::Object>); | |
| where the special ``derive`` macro provides useful methods such as | |
| ``from_raw``, ``as_ptr`, ``as_mut_ptr`` and ``raw_get``. The bindings will | |
| then manually check for the big QEMU lock with assertions, which allows | |
| the wrapper to be declared thread-safe:: | |
| unsafe impl Send for Object {} | |
| unsafe impl Sync for Object {} | |
| Writing bindings to C code | |
| '''''''''''''''''''''''''' | |
| Here are some things to keep in mind when working on the QEMU Rust crate. | |
| **Look at existing code** | |
| Very often, similar idioms in C code correspond to similar tricks in | |
| Rust bindings. If the C code uses ``offsetof``, look at qdev properties | |
| or ``vmstate``. If the C code has a complex const struct, look at | |
| ``MemoryRegion``. Reuse existing patterns for handling lifetimes; | |
| for example use ``&T`` for QOM objects that do not need a reference | |
| count (including those that can be embedded in other objects) and | |
| ``Owned<T>`` for those that need it. | |
| **Use the type system** | |
| Bindings often will need access information that is specific to a type | |
| (either a builtin one or a user-defined one) in order to pass it to C | |
| functions. Put them in a trait and access it through generic parameters. | |
| The ``vmstate`` module has examples of how to retrieve type information | |
| for the fields of a Rust ``struct``. | |
| **Prefer unsafe traits to unsafe functions** | |
| Unsafe traits are much easier to prove correct than unsafe functions. | |
| They are an excellent place to store metadata that can later be accessed | |
| by generic functions. C code usually places metadata in global variables; | |
| in Rust, they can be stored in traits and then turned into ``static`` | |
| variables. Often, unsafe traits can be generated by procedural macros. | |
| **Document limitations due to old Rust versions** | |
| If you need to settle for an inferior solution because of the currently | |
| supported set of Rust versions, document it in the source and in this | |
| file. This ensures that it can be fixed when the minimum supported | |
| version is bumped. | |
| **Keep locking in mind**. | |
| When marking a type ``Sync``, be careful of whether it needs the big | |
| QEMU lock. Use ``BqlCell`` and ``BqlRefCell`` for interior data, | |
| or assert ``bql_locked()``. | |
| **Don't be afraid of complexity, but document and isolate it** | |
| It's okay to be tricky; device code is written more often than bindings | |
| code and it's important that it is idiomatic. However, you should strive | |
| to isolate any tricks in a place (for example a ``struct``, a trait | |
| or a macro) where it can be documented and tested. If needed, include | |
| toy versions of the code in the documentation. | |
| FFI Binding Generation | |
| '''''''''''''''''''''' | |
| QEMU's Rust integration uses multiple ``*-sys`` crates that contain raw FFI | |
| bindings to different QEMU subsystems. These crates mirror the dependency | |
| structure that meson.build uses for C code, and which is reflected in | |
| ``static_library()`` declarations. For example: | |
| * util-sys: Basic utilities (no dependencies) | |
| * qom-sys: QEMU Object Model (depends on util-sys) | |
| * chardev-sys: Character devices (depends on qom-sys, util-sys) | |
| * hwcore-sys: Hardware core (depends on qom-sys, util-sys) | |
| * migration-sys: Migration (depends on util-sys) | |
| * system-sys: System-level APIs (depends on all others) | |
| Having multiple crates avoids massive rebuilds of all Rust code when C headers | |
| are changed. On the other hand, bindgen is not aware of how headers are split | |
| across crates, and therefore it would generate declarations for dependencies | |
| again. These duplicate declarations are not only large, they create distinct | |
| types and therefore they are incompatible with each other. | |
| Bindgen Configuration | |
| ~~~~~~~~~~~~~~~~~~~~~ | |
| Bindgen options such as symbol blocklists or how to configure enums can be | |
| defined in each crate's ``Cargo.toml`` via a ``[package.metadata.bindgen]`` section. | |
| For example:: | |
| [package.metadata.bindgen] | |
| header = "wrapper.h" # Main header file for this crate | |
| rustified-enum = ["QEMUClockType"] # Enums to generate as Rust enums | |
| bitfield-enum = ["VMStateFlags"] # Enums to treat as bitfields | |
| blocklist-function = [ # Functions to exclude | |
| "vmstate_register_ram", | |
| "vmstate_unregister_ram" | |
| ] | |
| additional-files = [ # Extra files to allowlist | |
| "include/system/memory_ldst.*" | |
| ] | |
| All bindgen options are supported in the metadata section. The complete list | |
| can be found in ``rust/bindings/generate_bindgen_args.py``. | |
| Dependency Management | |
| ~~~~~~~~~~~~~~~~~~~~~ | |
| By examining the dependency chain before bindgen creates the code for | |
| the ``*-sys`` crates, the build system ensures that header files included in | |
| one crate are blocked from appearing in dependent crates, thus avoiding | |
| duplicate definitions. Dependent crates can import the definition via | |
| "use" statements. | |
| This dependency-aware binding generation is handled automatically by | |
| ``rust/bindings/generate_bindgen_args.py``, which processes the Cargo.toml | |
| files in dependency order and generates appropriate ``--allowlist-file`` and | |
| ``--blocklist-file`` arguments for bindgen. | |
| Writing procedural macros | |
| ''''''''''''''''''''''''' | |
| By conventions, procedural macros are split in two functions, one | |
| returning ``Result<proc_macro2::TokenStream, syn::Error>`` with the body of | |
| the procedural macro, and the second returning ``proc_macro::TokenStream`` | |
| which is the actual procedural macro. The former's name is the same as | |
| the latter with the ``_or_error`` suffix. The code for the latter is more | |
| or less fixed; it follows the following template, which is fixed apart | |
| from the type after ``as`` in the invocation of ``parse_macro_input!``:: | |
| #[proc_macro_derive(Object)] | |
| pub fn derive_object(input: TokenStream) -> TokenStream { | |
| let input = parse_macro_input!(input as DeriveInput); | |
| derive_object_or_error(input) | |
| .unwrap_or_else(syn::Error::into_compile_error) | |
| .into() | |
| } | |
| The ``qemu_macros`` crate has utility functions to examine a | |
| ``DeriveInput`` and perform common checks (e.g. looking for a struct | |
| with named fields). These functions return ``Result<..., syn::Error>`` | |
| and can be used easily in the procedural macro function:: | |
| fn derive_object_or_error(input: DeriveInput) -> | |
| Result<proc_macro2::TokenStream, Error> | |
| { | |
| is_c_repr(&input, "#[derive(Object)]")?; | |
| let name = &input.ident; | |
| let parent = &get_fields(&input, "#[derive(Object)]")?[0].ident; | |
| ... | |
| } | |
| Use procedural macros with care. They are mostly useful for two purposes: | |
| * Performing consistency checks; for example ``#[derive(Object)]`` checks | |
| that the structure has ``#[repr[C])`` and that the type of the first field | |
| is consistent with the ``ObjectType`` declaration. | |
| * Extracting information from Rust source code into traits, typically based | |
| on types and attributes. For example, ``#[derive(TryInto)]`` builds an | |
| implementation of ``TryFrom``, and it uses the ``#[repr(...)]`` attribute | |
| as the ``TryFrom`` source and error types. | |
| Procedural macros can be hard to debug and test; if the code generation | |
| exceeds a few lines of code, it may be worthwhile to delegate work to | |
| "regular" declarative (``macro_rules!``) macros and write unit tests for | |
| those instead. | |
| Coding style | |
| '''''''''''' | |
| Code should pass clippy and be formatted with rustfmt. | |
| Right now, only the nightly version of ``rustfmt`` is supported. This | |
| might change in the future. While CI checks for correct formatting via | |
| ``cargo fmt --check``, maintainers can fix this for you when applying patches. | |
| It is expected that QEMU Rust crates provides full ``rustdoc`` documentation for | |
| bindings that are in their final shape or close. | |
| Adding dependencies | |
| ------------------- | |
| Generally, the set of dependent crates is kept small. Think twice before | |
| adding a new external crate, especially if it comes with a large set of | |
| dependencies itself. Sometimes QEMU only needs a small subset of the | |
| functionality; see for example QEMU's ``assertions`` module. | |
| On top of this recommendation, adding external crates to QEMU is a | |
| slightly complicated process, mostly due to the need to teach Meson how | |
| to build them. While Meson has initial support for parsing ``Cargo.lock`` | |
| files, it is still highly experimental and is therefore not used. | |
| Therefore, external crates must be added as subprojects for Meson to | |
| learn how to build them, as well as to the relevant ``Cargo.toml`` files. | |
| The versions specified in ``rust/Cargo.lock`` must be the same as the | |
| subprojects; note that the ``rust/`` directory forms a Cargo `workspace`__, | |
| and therefore there is a single lock file for the whole build. | |
| __ https://doc.rust-lang.org/cargo/reference/workspaces.html#virtual-workspace | |
| Choose a version of the crate that works with QEMU's minimum supported | |
| Rust version (|msrv|). | |
| Second, a new ``wrap`` file must be added to teach Meson how to download the | |
| crate. The wrap file must be named ``NAME-SEMVER-rs.wrap``, where ``NAME`` | |
| is the name of the crate and ``SEMVER`` is the version up to and including the | |
| first non-zero number. For example, a crate with version ``0.2.3`` will use | |
| ``0.2`` for its ``SEMVER``, while a crate with version ``1.0.84`` will use ``1``. | |
| Third, the Meson rules to build the crate must be added at | |
| ``subprojects/NAME-SEMVER-rs/meson.build``. Generally this includes: | |
| * ``subproject`` and ``dependency`` lines for all dependent crates | |
| * a ``static_library`` or ``rust.proc_macro`` line to perform the actual build | |
| * ``declare_dependency`` and a ``meson.override_dependency`` lines to expose | |
| the result to QEMU and to other subprojects | |
| Remember to add ``native: true`` to ``dependency``, ``static_library`` and | |
| ``meson.override_dependency`` for dependencies of procedural macros. | |
| If a crate is needed in both procedural macros and QEMU binaries, everything | |
| apart from ``subproject`` must be duplicated to build both native and | |
| non-native versions of the crate. | |
| It's important to specify the right compiler options. These include: | |
| * the language edition (which can be found in the ``Cargo.toml`` file) | |
| * the ``--cfg`` (which have to be "reverse engineered" from the ``build.rs`` | |
| file of the crate). | |
| * usually, a ``--cap-lints allow`` argument to hide warnings from rustc | |
| or clippy. | |
| After every change to the ``meson.build`` file you have to update the patched | |
| version with ``meson subprojects update --reset ``NAME-SEMVER-rs``. This might | |
| be automated in the future. | |
| Also, after every change to the ``meson.build`` file it is strongly suggested to | |
| do a dummy change to the ``.wrap`` file (for example adding a comment like | |
| ``# version 2``), which will help Meson notice that the subproject is out of date. | |
| As a last step, add the new subproject to ``scripts/archive-source.sh``, | |
| ``scripts/make-release`` and ``subprojects/.gitignore``. |