From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Cc: berrange@redhat.com, kwolf@redhat.com, junjie.mao@hotmail.com,
manos.pitsidianakis@linaro.org
Subject: [PATCH v2 07/14] rust: introduce alternative implementation of offset_of!
Date: Tue, 22 Oct 2024 12:09:48 +0200 [thread overview]
Message-ID: <20241022100956.196657-8-pbonzini@redhat.com> (raw)
In-Reply-To: <20241022100956.196657-1-pbonzini@redhat.com>
From: Junjie Mao <junjie.mao@hotmail.com>
offset_of! was stabilized in Rust 1.77.0. Use an alternative implemenation
that was found on the Rust forums, and whose author agreed to license as
MIT for use in QEMU.
The alternative allows only one level of field access, but apart
from this can be used just by replacing core::mem::offset_of! with
qemu_api::offset_of!.
The actual implementation of offset_of! is done in a declarative macro,
but for simplicity and to avoid introducing an extra level of indentation,
the trigger is a procedural macro #[derive(offsets)].
Signed-off-by: Junjie Mao <junjie.mao@hotmail.com>
Co-developed-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
rust/hw/char/pl011/Cargo.lock | 1 +
rust/hw/char/pl011/src/device.rs | 2 +-
rust/qemu-api-macros/Cargo.toml | 5 +-
rust/qemu-api-macros/src/lib.rs | 29 +++-
rust/qemu-api/Cargo.lock | 7 +
rust/qemu-api/Cargo.toml | 6 +-
rust/qemu-api/build.rs | 8 +
rust/qemu-api/meson.build | 12 +-
rust/qemu-api/src/device_class.rs | 8 +-
rust/qemu-api/src/lib.rs | 4 +
rust/qemu-api/src/offset_of.rs | 161 ++++++++++++++++++
rust/qemu-api/tests/tests.rs | 1 +
subprojects/packagefiles/syn-2-rs/meson.build | 1 +
13 files changed, 232 insertions(+), 13 deletions(-)
create mode 100644 rust/qemu-api/src/offset_of.rs
diff --git a/rust/hw/char/pl011/Cargo.lock b/rust/hw/char/pl011/Cargo.lock
index 82028ddf793..087658ca72b 100644
--- a/rust/hw/char/pl011/Cargo.lock
+++ b/rust/hw/char/pl011/Cargo.lock
@@ -93,6 +93,7 @@ name = "qemu_api"
version = "0.1.0"
dependencies = [
"qemu_api_macros",
+ "version_check",
]
[[package]]
diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 5ffe1911376..c26d21e079d 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -29,7 +29,7 @@
pub const PL011_FIFO_DEPTH: usize = 16_usize;
#[repr(C)]
-#[derive(Debug, qemu_api_macros::Object)]
+#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
diff --git a/rust/qemu-api-macros/Cargo.toml b/rust/qemu-api-macros/Cargo.toml
index 144cc3650fa..47fec2e4637 100644
--- a/rust/qemu-api-macros/Cargo.toml
+++ b/rust/qemu-api-macros/Cargo.toml
@@ -19,7 +19,10 @@ proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
-syn = "2"
+
+[dependencies.syn]
+version = "2"
+features= ["extra-traits"]
# Do not include in any global workspace
[workspace]
diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs
index a4bc5d01ee8..472264159d6 100644
--- a/rust/qemu-api-macros/src/lib.rs
+++ b/rust/qemu-api-macros/src/lib.rs
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use proc_macro::TokenStream;
-use quote::quote;
-use syn::{parse_macro_input, DeriveInput};
+use quote::{quote, quote_spanned};
+use syn::{parse_macro_input, parse_quote, spanned::Spanned, Attribute, DeriveInput};
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
@@ -21,3 +21,28 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}
+
+#[proc_macro_derive(offsets)]
+pub fn derive_offsets(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let repr_c: Attribute = parse_quote! { #[repr(C)] };
+
+ // All the work for expanding this macro is done in "with_offsets!",
+ // a macro in the qemu_api crate which re-parses the struct declaration;
+ // this derive macro is just a convenience.
+ //
+ // The with_offsets! macro expands to nothing if the compiler is new enough
+ // to have a stable offset_of.
+ let expanded = if !input.attrs.iter().any(|x| *x == repr_c) {
+ quote_spanned! {
+ input.span() =>
+ compile_error!("expected #[repr(C)] together with #[derive(offsets)]");
+ }
+ } else {
+ quote! {
+ ::qemu_api::with_offsets! { #input }
+ }
+ };
+
+ TokenStream::from(expanded)
+}
diff --git a/rust/qemu-api/Cargo.lock b/rust/qemu-api/Cargo.lock
index 468293feedd..371c6167918 100644
--- a/rust/qemu-api/Cargo.lock
+++ b/rust/qemu-api/Cargo.lock
@@ -16,6 +16,7 @@ name = "qemu_api"
version = "0.1.0"
dependencies = [
"qemu_api_macros",
+ "version_check",
]
[[package]]
@@ -52,3 +53,9 @@ name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml
index db594c64083..9c3a99522a9 100644
--- a/rust/qemu-api/Cargo.toml
+++ b/rust/qemu-api/Cargo.toml
@@ -16,6 +16,9 @@ categories = []
[dependencies]
qemu_api_macros = { path = "../qemu-api-macros" }
+[build-dependencies]
+version_check = "~0.9"
+
[features]
default = []
allocator = []
@@ -24,4 +27,5 @@ allocator = []
[workspace]
[lints.rust]
-unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)'] }
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)',
+ 'cfg(has_offset_of)'] }
diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs
index 419b154c2d2..0a68c3712f1 100644
--- a/rust/qemu-api/build.rs
+++ b/rust/qemu-api/build.rs
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use std::path::Path;
+use version_check as rustc;
fn main() {
if !Path::new("src/bindings.rs").exists() {
@@ -11,4 +12,11 @@ fn main() {
(`ninja bindings.rs`) and copy them to src/bindings.rs, or build through meson."
);
}
+
+ // Check for available rustc features
+ if rustc::is_min_version("1.77.0").unwrap_or(false) {
+ println!("cargo:rustc-cfg=has_offset_of");
+ }
+
+ println!("cargo:rerun-if-changed=build.rs");
}
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 2b3dea59fb6..4ac33acced3 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -1,3 +1,9 @@
+_qemu_api_cfg = ['--cfg', 'MESON']
+# _qemu_api_cfg += ['--cfg', 'feature="allocator"']
+if rustc.version().version_compare('>=1.77.0')
+ _qemu_api_cfg += ['--cfg', 'has_offset_of']
+endif
+
_qemu_api_rs = static_library(
'qemu_api',
structured_sources(
@@ -6,16 +12,14 @@ _qemu_api_rs = static_library(
'src/c_str.rs',
'src/definitions.rs',
'src/device_class.rs',
+ 'src/offset_of.rs',
'src/zeroable.rs',
],
{'.' : bindings_rs},
),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
- rust_args: [
- '--cfg', 'MESON',
- # '--cfg', 'feature="allocator"',
- ],
+ rust_args: _qemu_api_cfg,
)
rust.test('rust-qemu-api-tests', _qemu_api_rs,
diff --git a/rust/qemu-api/src/device_class.rs b/rust/qemu-api/src/device_class.rs
index 5c305d945d3..ce3d5f50507 100644
--- a/rust/qemu-api/src/device_class.rs
+++ b/rust/qemu-api/src/device_class.rs
@@ -23,23 +23,23 @@ macro_rules! device_class_init {
#[macro_export]
macro_rules! define_property {
- ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
+ ($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
- offset: ::core::mem::offset_of!($state, $field) as isize,
+ offset: $crate::offset_of!($state, $field) as isize,
set_default: true,
defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval.into() },
..$crate::zeroable::Zeroable::ZERO
}
};
- ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr$(,)*) => {
+ ($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
- offset: ::core::mem::offset_of!($state, $field) as isize,
+ offset: $crate::offset_of!($state, $field) as isize,
set_default: false,
..$crate::zeroable::Zeroable::ZERO
}
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index 9c3be4f6489..92f1785d891 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -30,6 +30,7 @@ unsafe impl Sync for bindings::VMStateDescription {}
pub mod c_str;
pub mod definitions;
pub mod device_class;
+pub mod offset_of;
pub mod zeroable;
use std::{
@@ -166,3 +167,6 @@ unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
}
}
}
+
+#[cfg(has_offset_of)]
+pub use std::mem::offset_of;
diff --git a/rust/qemu-api/src/offset_of.rs b/rust/qemu-api/src/offset_of.rs
new file mode 100644
index 00000000000..87db8607592
--- /dev/null
+++ b/rust/qemu-api/src/offset_of.rs
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: MIT
+
+/// This macro provides the same functionality as `core::mem::offset_of`,
+/// except that only one level of field access is supported. The declaration
+/// of the struct must be wrapped with `with_offsets! { }`.
+///
+/// It is needed because `offset_of!` was only stabilized in Rust 1.77.
+#[cfg(not(has_offset_of))]
+#[macro_export]
+macro_rules! offset_of {
+ ($Container:ty, $field:ident) => {
+ <$Container>::OFFSET_TO__.$field
+ };
+}
+
+/// A wrapper for struct declarations, that allows using `offset_of!` in
+/// versions of Rust prior to 1.77
+#[macro_export]
+macro_rules! with_offsets {
+ // This method to generate field offset constants comes from:
+ //
+ // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=10a22a9b8393abd7b541d8fc844bc0df
+ //
+ // used under MIT license with permission of Yandros aka Daniel Henry-Mantilla
+ (
+ $(#[$struct_meta:meta])*
+ $struct_vis:vis
+ struct $StructName:ident {
+ $(
+ $(#[$field_meta:meta])*
+ $field_vis:vis
+ $field_name:ident : $field_ty:ty
+ ),*
+ $(,)?
+ }
+ ) => (
+ #[cfg(not(has_offset_of))]
+ const _: () = {
+ struct StructOffsetsHelper<T>(std::marker::PhantomData<T>);
+ const END_OF_PREV_FIELD: usize = 0;
+
+ // populate StructOffsetsHelper<T> with associated consts,
+ // one for each field
+ $crate::with_offsets! {
+ @struct $StructName
+ @names [ $($field_name)* ]
+ @tys [ $($field_ty ,)*]
+ }
+
+ // now turn StructOffsetsHelper<T>'s consts into a single struct,
+ // applying field visibility. This provides better error messages
+ // than if offset_of! used StructOffsetsHelper::<T> directly.
+ pub
+ struct StructOffsets {
+ $(
+ $field_vis
+ $field_name: usize,
+ )*
+ }
+ impl $StructName {
+ pub
+ const OFFSET_TO__: StructOffsets = StructOffsets {
+ $(
+ $field_name: StructOffsetsHelper::<$StructName>::$field_name,
+ )*
+ };
+ }
+ };
+ );
+
+ (
+ @struct $StructName:ident
+ @names []
+ @tys []
+ ) => ();
+
+ (
+ @struct $StructName:ident
+ @names [$field_name:ident $($other_names:tt)*]
+ @tys [$field_ty:ty , $($other_tys:tt)*]
+ ) => (
+ #[allow(non_local_definitions)]
+ #[allow(clippy::modulo_one)]
+ impl StructOffsetsHelper<$StructName> {
+ #[allow(nonstandard_style)]
+ const $field_name: usize = {
+ const ALIGN: usize = std::mem::align_of::<$field_ty>();
+ const TRAIL: usize = END_OF_PREV_FIELD % ALIGN;
+ END_OF_PREV_FIELD + (if TRAIL == 0 { 0usize } else { ALIGN - TRAIL })
+ };
+ }
+ const _: () = {
+ const END_OF_PREV_FIELD: usize =
+ StructOffsetsHelper::<$StructName>::$field_name +
+ std::mem::size_of::<$field_ty>()
+ ;
+ $crate::with_offsets! {
+ @struct $StructName
+ @names [$($other_names)*]
+ @tys [$($other_tys)*]
+ }
+ };
+ );
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::offset_of;
+
+ #[repr(C)]
+ struct Foo {
+ a: u16,
+ b: u32,
+ c: u64,
+ d: u16,
+ }
+
+ #[repr(C)]
+ struct Bar {
+ pub a: u16,
+ pub b: u64,
+ c: Foo,
+ d: u64,
+ }
+
+ crate::with_offsets! {
+ #[repr(C)]
+ struct Bar {
+ pub a: u16,
+ pub b: u64,
+ c: Foo,
+ d: u64,
+ }
+ }
+
+ #[repr(C)]
+ pub struct Baz {
+ b: u32,
+ a: u8,
+ }
+ crate::with_offsets! {
+ #[repr(C)]
+ pub struct Baz {
+ b: u32,
+ a: u8,
+ }
+ }
+
+ #[test]
+ fn test_offset_of() {
+ const OFFSET_TO_C: usize = offset_of!(Bar, c);
+
+ assert_eq!(offset_of!(Bar, a), 0);
+ assert_eq!(offset_of!(Bar, b), 8);
+ assert_eq!(OFFSET_TO_C, 16);
+ assert_eq!(offset_of!(Bar, d), 40);
+
+ assert_eq!(offset_of!(Baz, b), 0);
+ assert_eq!(offset_of!(Baz, a), 4);
+ }
+}
diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs
index 605bc99ed4c..96e413b75ee 100644
--- a/rust/qemu-api/tests/tests.rs
+++ b/rust/qemu-api/tests/tests.rs
@@ -20,6 +20,7 @@ fn test_device_decl_macros() {
unmigratable: true,
}
+ #[derive(qemu_api_macros::offsets)]
#[repr(C)]
#[derive(qemu_api_macros::Object)]
pub struct DummyState {
diff --git a/subprojects/packagefiles/syn-2-rs/meson.build b/subprojects/packagefiles/syn-2-rs/meson.build
index a53335f3092..9f56ce1c24d 100644
--- a/subprojects/packagefiles/syn-2-rs/meson.build
+++ b/subprojects/packagefiles/syn-2-rs/meson.build
@@ -24,6 +24,7 @@ _syn_rs = static_library(
'--cfg', 'feature="printing"',
'--cfg', 'feature="clone-impls"',
'--cfg', 'feature="proc-macro"',
+ '--cfg', 'feature="extra-traits"',
],
dependencies: [
quote_dep,
--
2.46.2
next prev parent reply other threads:[~2024-10-22 10:12 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-10-22 10:09 [PATCH v2 00/14] rust: allow older versions of rustc and bindgen Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 01/14] rust: patch bilge-impl to allow compilation with 1.63.0 Paolo Bonzini
2024-10-24 2:12 ` Junjie Mao
2024-10-24 10:43 ` Alex Bennée
2024-10-22 10:09 ` [PATCH v2 02/14] rust: fix cfgs of proc-macro2 for 1.63.0 Paolo Bonzini
2024-10-24 2:33 ` Junjie Mao
2024-10-24 9:02 ` Paolo Bonzini
2024-10-24 9:09 ` Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 03/14] rust: use std::os::raw instead of core::ffi Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 04/14] rust: introduce a c_str macro Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 05/14] rust: silence unknown warnings for the sake of old compilers Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 06/14] rust: synchronize dependencies between subprojects and Cargo.lock Paolo Bonzini
2024-10-24 2:53 ` Junjie Mao
2024-10-24 9:04 ` Paolo Bonzini
2024-10-22 10:09 ` Paolo Bonzini [this message]
2024-10-22 10:09 ` [PATCH v2 08/14] rust: do not use MaybeUninit::zeroed() Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 09/14] rust: clean up detection of the language Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 10/14] rust: allow version 1.63.0 of rustc Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 11/14] rust: do not use --generate-cstr Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 12/14] rust: allow older version of bindgen Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 13/14] rust: make rustfmt optional Paolo Bonzini
2024-10-22 10:09 ` [PATCH v2 14/14] dockerfiles: install bindgen from cargo on Ubuntu 22.04 Paolo Bonzini
2024-10-24 11:28 ` [PATCH v2 00/14] rust: allow older versions of rustc and bindgen Paolo Bonzini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20241022100956.196657-8-pbonzini@redhat.com \
--to=pbonzini@redhat.com \
--cc=berrange@redhat.com \
--cc=junjie.mao@hotmail.com \
--cc=kwolf@redhat.com \
--cc=manos.pitsidianakis@linaro.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).