@@ -93,6 +93,7 @@ name = "qemu_api"
version = "0.1.0"
dependencies = [
"qemu_api_macros",
+ "version_check",
]
[[package]]
@@ -55,7 +55,7 @@ impl DeviceId {
}
#[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,
@@ -19,4 +19,4 @@ proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
-syn = "2"
+syn = { version = "2", features = ["extra-traits"] }
@@ -3,8 +3,34 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use proc_macro::TokenStream;
-use quote::quote;
-use syn::{parse_macro_input, DeriveInput};
+use proc_macro2::Span;
+use quote::{quote, quote_spanned};
+use syn::{
+ parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field,
+ Fields, Ident, Type, Visibility,
+};
+
+struct CompileError(String, Span);
+
+impl From<CompileError> for proc_macro2::TokenStream {
+ fn from(err: CompileError) -> Self {
+ let CompileError(msg, span) = err;
+ quote_spanned! { span => compile_error!(#msg); }
+ }
+}
+
+fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> {
+ let expected = parse_quote! { #[repr(C)] };
+
+ if input.attrs.iter().any(|attr| attr == &expected) {
+ Ok(())
+ } else {
+ Err(CompileError(
+ format!("#[repr(C)] required for {}", msg),
+ input.ident.span(),
+ ))
+ }
+}
#[proc_macro_derive(Object)]
pub fn derive_object(input: TokenStream) -> TokenStream {
@@ -21,3 +47,48 @@ pub fn derive_object(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}
+
+fn get_fields(input: &DeriveInput) -> Result<&Punctuated<Field, Comma>, CompileError> {
+ if let Data::Struct(s) = &input.data {
+ if let Fields::Named(fs) = &s.fields {
+ Ok(&fs.named)
+ } else {
+ Err(CompileError(
+ "Cannot generate offsets for unnamed fields.".to_string(),
+ input.ident.span(),
+ ))
+ }
+ } else {
+ Err(CompileError(
+ "Cannot generate offsets for union or enum.".to_string(),
+ input.ident.span(),
+ ))
+ }
+}
+
+#[rustfmt::skip::macros(quote)]
+fn derive_offsets_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, CompileError> {
+ is_c_repr(&input, "#[derive(offsets)]")?;
+
+ let name = &input.ident;
+ let fields = get_fields(&input)?;
+ let field_names: Vec<&Ident> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
+ let field_types: Vec<&Type> = fields.iter().map(|f| &f.ty).collect();
+ let field_vis: Vec<&Visibility> = fields.iter().map(|f| &f.vis).collect();
+
+ Ok(quote! {
+ ::qemu_api::with_offsets! {
+ struct #name {
+ #(#field_vis #field_names: #field_types,)*
+ }
+ }
+ })
+}
+
+#[proc_macro_derive(offsets)]
+pub fn derive_offsets(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let expanded = derive_offsets_or_error(input).unwrap_or_else(Into::into);
+
+ TokenStream::from(expanded)
+}
@@ -16,9 +16,13 @@ categories = []
[dependencies]
qemu_api_macros = { path = "../qemu-api-macros" }
+[build-dependencies]
+version_check = "~0.9"
+
[features]
default = []
allocator = []
[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)'] }
@@ -4,6 +4,8 @@
use std::path::Path;
+use version_check as rustc;
+
fn main() {
if !Path::new("src/bindings.rs").exists() {
panic!(
@@ -11,4 +13,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");
}
@@ -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,6 +12,7 @@ _qemu_api_rs = static_library(
'src/c_str.rs',
'src/definitions.rs',
'src/device_class.rs',
+ 'src/offset_of.rs',
'src/vmstate.rs',
'src/zeroable.rs',
],
@@ -13,10 +20,7 @@ _qemu_api_rs = static_library(
),
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,
@@ -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 as u64 },
..$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
}
@@ -32,6 +32,7 @@ unsafe impl Sync for bindings::VMStateInfo {}
pub mod c_str;
pub mod definitions;
pub mod device_class;
+pub mod offset_of;
pub mod vmstate;
pub mod zeroable;
@@ -169,3 +170,6 @@ unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
}
}
}
+
+#[cfg(has_offset_of)]
+pub use core::mem::offset_of;
new file mode 100644
@@ -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);
+ }
+}
@@ -58,7 +58,7 @@ macro_rules! vmstate_single_test {
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
err_hint: ::core::ptr::null(),
- offset: ::core::mem::offset_of!($struct_name, $field_name),
+ offset: $crate::offset_of!($struct_name, $field_name),
size: $size,
start: 0,
num: 0,
@@ -135,7 +135,7 @@ macro_rules! vmstate_array {
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
err_hint: ::core::ptr::null(),
- offset: ::core::mem::offset_of!($struct_name, $field_name),
+ offset: $crate::offset_of!($struct_name, $field_name),
size: $size,
start: 0,
num: $length as _,
@@ -183,7 +183,7 @@ macro_rules! vmstate_struct_pointer_v {
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
err_hint: ::core::ptr::null(),
- offset: ::core::mem::offset_of!($struct_name, $field_name),
+ offset: $crate::offset_of!($struct_name, $field_name),
size: ::core::mem::size_of::<*const $type>(),
start: 0,
num: 0,
@@ -212,7 +212,7 @@ macro_rules! vmstate_array_of_pointer {
info: unsafe { $info },
size: ::core::mem::size_of::<*const $type>(),
flags: VMStateFlags(VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0),
- offset: ::core::mem::offset_of!($struct_name, $field_name),
+ offset: $crate::offset_of!($struct_name, $field_name),
err_hint: ::core::ptr::null(),
start: 0,
num_offset: 0,
@@ -241,7 +241,7 @@ macro_rules! vmstate_array_of_pointer_to_struct {
| VMStateFlags::VMS_STRUCT.0
| VMStateFlags::VMS_ARRAY_OF_POINTER.0,
),
- offset: ::core::mem::offset_of!($struct_name, $field_name),
+ offset: $crate::offset_of!($struct_name, $field_name),
err_hint: ::core::ptr::null(),
start: 0,
num_offset: 0,
@@ -21,6 +21,7 @@ fn test_device_decl_macros() {
..Zeroable::ZERO
};
+ #[derive(qemu_api_macros::offsets)]
#[repr(C)]
#[derive(qemu_api_macros::Object)]
pub struct DummyState {
@@ -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,