mirror of
https://github.com/qemu/qemu.git
synced 2025-08-04 00:12:05 +00:00

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)]. The procedural macro is perhaps a bit overengineered, but it helps introducing some idioms that will be useful in the future as well. 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>
95 lines
2.8 KiB
Rust
95 lines
2.8 KiB
Rust
// Copyright 2024, Linaro Limited
|
|
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
use proc_macro::TokenStream;
|
|
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 {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
let name = input.ident;
|
|
|
|
let expanded = quote! {
|
|
::qemu_api::module_init! {
|
|
MODULE_INIT_QOM => unsafe {
|
|
::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
|
|
}
|
|
}
|
|
};
|
|
|
|
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)
|
|
}
|