[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