[RFC v2 22/26] rust/system/memory: Wrap vm-memory's endian types for explicit access
Zhao Liu <[email protected]> Wed, 8 Jul 2026 16:10:48 +0800
| Newsgroups | org.nongnu.qemu-rust,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
QEMU is getting rid of native endianness, so the load/store interfaces should explicitly specify the target device's endianness. vm-memory provides BeNN and LeNN types, but its Bytes trait uses the AtomicAccess trait bound for its save/load methods. The explicit endian wrappers do not implement this bound. As the top-level interface, AddressSpace does not need to implement the Bytes trait. It can simply take explicit endian types in its save/load methods, convert them to a fixed-width AtomicAccess primitive, and forward them to the underlying FlatView's Bytes::save and load. Introduce the EndianValued trait to wrap vm-memory's endian types, allowing interfaces that require explicit endianness to use it as a trait bound. Also, add a conversion method in EndianValued to get the fixed-width AtomicAccess carrier, allowing higher-level interfaces to maintain compatibility with Bytes::save/load internally. Finally, implement the Sealed pattern for EndianValued to strictly limit the supported types within the crate. Signed-off-by: Zhao Liu <[email protected]> --- Changes since v1: * New commit to wrap BeNN and LeNN as an independent trait. --- rust/system/src/memory.rs | 94 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/rust/system/src/memory.rs b/rust/system/src/memory.rs index 12f48f71900b..b16e71ddbf4e 100644 --- a/rust/system/src/memory.rs +++ b/rust/system/src/memory.rs @@ -17,12 +17,12 @@ use common::{callbacks::FnCall, uninit::MaybeUninitField, zeroable::Zeroable, Opaque}; use qom::prelude::*; -pub use vm_memory::GuestAddress; use vm_memory::{ - bitmap::BS, Address, AtomicAccess, Bytes, GuestMemoryBackend, GuestMemoryError, + bitmap::BS, Address, AtomicAccess, ByteValued, Bytes, GuestMemoryBackend, GuestMemoryError, GuestMemoryRegion, GuestMemoryRegionBytes, GuestMemoryResult, GuestUsize, MemoryRegionAddress, Permissions, ReadVolatile, WriteVolatile, }; +pub use vm_memory::{Be16, Be32, Be64, GuestAddress, Le16, Le32, Le64}; use crate::bindings::{ self, address_space_lookup_section, device_endian, flatview_ref, flatview_translate_section, @@ -1200,3 +1200,93 @@ fn clone(&self) -> Self { ) } } + +/// Private module hosting the [`Sealed`](private::Sealed) supertrait of +/// [`EndianValued`], keeping the set of [`EndianValued`] types closed to the +/// ones listed below. +/// +/// Reference: <https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed> +mod private { + pub trait Sealed {} +} + +/// A sealed trait for types with an explicitly fixed byte order (e.g., `Le32`, +/// `Be32`). +/// +/// This ensures guest memory accesses via [`AddressSpace::store`] and +/// [`AddressSpace::load`] are strictly independent of the host endianness. +/// Types with implicit endianness (like bare u32 or [u8; 4]) are rejected. +/// +/// Architecturally, this trait is designed for two purposes: +/// +/// 1) Bypass the orphan rule: It acts as a local trait allowed to select +/// specific `LeNN` and `BeNN` types from `vm-memory`'s [`ByteValued`], +/// allowing a trait bound for the `AddressSpace` API. +/// +/// 2) Enforce a closed set: Combined with the sealed mechanism, it prevents +/// downstream crates (device or other crates) from implementing this trait. +pub trait EndianValued: ByteValued + private::Sealed { + /// The same-width primitive integer used as the atomic carrier when an + /// explicit-endian access is passed to `Bytes::store`/`Bytes::load`. + type Carrier: AtomicAccess; + + /// Convert `self` to [`Carrier`](Self::Carrier) integer, keeping the exact + /// bytes. + fn to_carrier(self) -> Self::Carrier; + + /// Convert the [`Carrier`](Self::Carrier) integer back to this + /// fixed-endian type, keeping the exact bytes. + fn from_carrier(carrier: Self::Carrier) -> Self; +} + +macro_rules! impl_endian_valued { + ($($t:ty => $carrier:ty),* $(,)?) => { + $( + impl private::Sealed for $t {} + + impl EndianValued for $t { + type Carrier = $carrier; + + #[inline(always)] + #[allow(clippy::useless_transmute)] // For i8 and u8 + fn to_carrier(self) -> Self::Carrier { + // SAFETY: The macro strictly pairs types of the exact same size. + unsafe { std::mem::transmute(self) } + } + + #[inline(always)] + #[allow(clippy::useless_transmute)] // For i8 and u8 + fn from_carrier(carrier: Self::Carrier) -> Self { + // SAFETY: The macro strictly pairs types of the exact same size. + unsafe { std::mem::transmute(carrier) } + } + } + )* + }; +} + +// Explicit-endian wrappers (byte order fixed by the type) plus the single-byte +// integers (which have no byte order at all). +impl_endian_valued!( + Le16 => u16, + Le32 => u32, + Be16 => u16, + Be32 => u32, + u8 => u8, + i8 => i8, +); + +// vm-memory only implements `AtomicAccess` for the 64-bit primitives on 64-bit +// targets. Copy vm-memory's cfg here to avoid a mismatch between the two +// traits. +#[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "riscv64" +))] +impl_endian_valued!( + Le64 => u64, + Be64 => u64, +); -- 2.34.1