From 71e84e5ae87b8e6adce3af7dd2a490c7b2f39772 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 8 Sep 2025 12:49:36 +0200 Subject: rust: add missing const markers for MSRV==1.83.0 Rust 1.83 allows more functions to be marked const. Fix clippy with bumped minimum supported Rust version. Reviewed-by: Zhao Liu Link: https://lore.kernel.org/r/20250908105005.2119297-5-pbonzini@redhat.com Signed-off-by: Paolo Bonzini --- rust/bits/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rust/bits/src') diff --git a/rust/bits/src/lib.rs b/rust/bits/src/lib.rs index d485d6bd11..e9d15ad0cb 100644 --- a/rust/bits/src/lib.rs +++ b/rust/bits/src/lib.rs @@ -165,19 +165,19 @@ macro_rules! bits { #[allow(dead_code)] #[inline(always)] - pub fn set(&mut self, rhs: Self) { + pub const fn set(&mut self, rhs: Self) { self.0 |= rhs.0; } #[allow(dead_code)] #[inline(always)] - pub fn clear(&mut self, rhs: Self) { + pub const fn clear(&mut self, rhs: Self) { self.0 &= !rhs.0; } #[allow(dead_code)] #[inline(always)] - pub fn toggle(&mut self, rhs: Self) { + pub const fn toggle(&mut self, rhs: Self) { self.0 ^= rhs.0; } -- cgit 1.4.1 From 0d93f8177310515ae2d8aea8e1320e53818d70bd Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 8 Sep 2025 12:49:57 +0200 Subject: rust: rename qemu_api_macros -> qemu_macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since "qemu_api" is no longer the unique crate to provide APIs. Signed-off-by: Marc-André Lureau Link: https://lore.kernel.org/r/20250827104147.717203-17-marcandre.lureau@redhat.com Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- MAINTAINERS | 2 +- rust/Cargo.lock | 22 +- rust/Cargo.toml | 2 +- rust/bits/Cargo.toml | 2 +- rust/bits/meson.build | 2 +- rust/bits/src/lib.rs | 4 +- rust/chardev/Cargo.toml | 2 +- rust/chardev/meson.build | 4 +- rust/chardev/src/chardev.rs | 2 +- rust/common/src/opaque.rs | 4 +- rust/hw/char/pl011/Cargo.toml | 2 +- rust/hw/char/pl011/meson.build | 4 +- rust/hw/char/pl011/src/device.rs | 4 +- rust/hw/char/pl011/src/registers.rs | 2 +- rust/hw/core/Cargo.toml | 2 +- rust/hw/core/meson.build | 4 +- rust/hw/core/src/irq.rs | 2 +- rust/hw/core/src/qdev.rs | 6 +- rust/hw/core/src/sysbus.rs | 2 +- rust/hw/core/tests/tests.rs | 4 +- rust/hw/timer/hpet/Cargo.toml | 2 +- rust/hw/timer/hpet/meson.build | 4 +- rust/hw/timer/hpet/src/device.rs | 6 +- rust/meson.build | 2 +- rust/migration/Cargo.toml | 2 +- rust/qemu-api-macros/Cargo.toml | 24 --- rust/qemu-api-macros/meson.build | 22 -- rust/qemu-api-macros/src/bits.rs | 213 ------------------ rust/qemu-api-macros/src/lib.rs | 415 ------------------------------------ rust/qemu-api-macros/src/tests.rs | 244 --------------------- rust/qemu-api/Cargo.toml | 2 +- rust/qemu-api/meson.build | 4 +- rust/qemu-macros/Cargo.toml | 24 +++ rust/qemu-macros/meson.build | 22 ++ rust/qemu-macros/src/bits.rs | 213 ++++++++++++++++++ rust/qemu-macros/src/lib.rs | 415 ++++++++++++++++++++++++++++++++++++ rust/qemu-macros/src/tests.rs | 244 +++++++++++++++++++++ rust/qom/Cargo.toml | 2 +- rust/qom/meson.build | 4 +- rust/qom/src/qom.rs | 4 +- rust/system/Cargo.toml | 2 +- rust/system/meson.build | 4 +- rust/system/src/memory.rs | 2 +- rust/util/Cargo.toml | 2 +- rust/util/meson.build | 2 +- rust/util/src/timer.rs | 4 +- 46 files changed, 981 insertions(+), 981 deletions(-) delete mode 100644 rust/qemu-api-macros/Cargo.toml delete mode 100644 rust/qemu-api-macros/meson.build delete mode 100644 rust/qemu-api-macros/src/bits.rs delete mode 100644 rust/qemu-api-macros/src/lib.rs delete mode 100644 rust/qemu-api-macros/src/tests.rs create mode 100644 rust/qemu-macros/Cargo.toml create mode 100644 rust/qemu-macros/meson.build create mode 100644 rust/qemu-macros/src/bits.rs create mode 100644 rust/qemu-macros/src/lib.rs create mode 100644 rust/qemu-macros/src/tests.rs (limited to 'rust/bits/src') diff --git a/MAINTAINERS b/MAINTAINERS index 92d575b535..23bda7d332 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3521,7 +3521,7 @@ F: rust/common/ F: rust/hw/core/ F: rust/migration/ F: rust/qemu-api -F: rust/qemu-api-macros +F: rust/qemu-macros/ F: rust/qom/ F: rust/rustfmt.toml F: rust/system/ diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 77118e882b..021ce6dd48 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ name = "bits" version = "0.1.0" dependencies = [ - "qemu_api_macros", + "qemu_macros", ] [[package]] @@ -58,7 +58,7 @@ dependencies = [ "bql", "common", "migration", - "qemu_api_macros", + "qemu_macros", "qom", "util", ] @@ -94,7 +94,7 @@ dependencies = [ "hwcore", "migration", "qemu_api", - "qemu_api_macros", + "qemu_macros", "qom", "system", "util", @@ -108,7 +108,7 @@ dependencies = [ "chardev", "common", "migration", - "qemu_api_macros", + "qemu_macros", "qom", "system", "util", @@ -134,7 +134,7 @@ name = "migration" version = "0.1.0" dependencies = [ "common", - "qemu_api_macros", + "qemu_macros", "util", ] @@ -151,7 +151,7 @@ dependencies = [ "hwcore", "migration", "qemu_api", - "qemu_api_macros", + "qemu_macros", "qom", "system", "util", @@ -198,14 +198,14 @@ dependencies = [ "common", "hwcore", "migration", - "qemu_api_macros", + "qemu_macros", "qom", "system", "util", ] [[package]] -name = "qemu_api_macros" +name = "qemu_macros" version = "0.1.0" dependencies = [ "proc-macro2", @@ -220,7 +220,7 @@ dependencies = [ "bql", "common", "migration", - "qemu_api_macros", + "qemu_macros", "util", ] @@ -249,7 +249,7 @@ name = "system" version = "0.1.0" dependencies = [ "common", - "qemu_api_macros", + "qemu_macros", "qom", "util", ] @@ -268,7 +268,7 @@ dependencies = [ "common", "foreign", "libc", - "qemu_api_macros", + "qemu_macros", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8ec07d2065..b2a5c230fa 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,7 +5,7 @@ members = [ "bql", "common", "migration", - "qemu-api-macros", + "qemu-macros", "qemu-api", "qom", "system", diff --git a/rust/bits/Cargo.toml b/rust/bits/Cargo.toml index 1ff38a4117..7fce972b27 100644 --- a/rust/bits/Cargo.toml +++ b/rust/bits/Cargo.toml @@ -13,7 +13,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } [lints] workspace = true diff --git a/rust/bits/meson.build b/rust/bits/meson.build index 2a41e138c5..359ca86f15 100644 --- a/rust/bits/meson.build +++ b/rust/bits/meson.build @@ -3,7 +3,7 @@ _bits_rs = static_library( 'src/lib.rs', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', - dependencies: [qemu_api_macros], + dependencies: [qemu_macros], ) bits_rs = declare_dependency(link_with: _bits_rs) diff --git a/rust/bits/src/lib.rs b/rust/bits/src/lib.rs index e9d15ad0cb..1bc882fde1 100644 --- a/rust/bits/src/lib.rs +++ b/rust/bits/src/lib.rs @@ -380,11 +380,11 @@ macro_rules! bits { }; { $type:ty: $expr:expr } => { - ::qemu_api_macros::bits_const_internal! { $type @ ($expr) } + ::qemu_macros::bits_const_internal! { $type @ ($expr) } }; { $type:ty as $int_type:ty: $expr:expr } => { - (::qemu_api_macros::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type + (::qemu_macros::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type }; } diff --git a/rust/chardev/Cargo.toml b/rust/chardev/Cargo.toml index 7df9c677fc..c139177307 100644 --- a/rust/chardev/Cargo.toml +++ b/rust/chardev/Cargo.toml @@ -18,7 +18,7 @@ bql = { path = "../bql" } migration = { path = "../migration" } qom = { path = "../qom" } util = { path = "../util" } -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } [lints] workspace = true diff --git a/rust/chardev/meson.build b/rust/chardev/meson.build index 5d333e232b..a2fa3268d2 100644 --- a/rust/chardev/meson.build +++ b/rust/chardev/meson.build @@ -35,7 +35,7 @@ _chardev_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', link_with: [_bql_rs, _migration_rs, _qom_rs, _util_rs], - dependencies: [common_rs, qemu_api_macros], + dependencies: [common_rs, qemu_macros], ) -chardev_rs = declare_dependency(link_with: [_chardev_rs], dependencies: [qemu_api_macros, chardev, qemuutil]) +chardev_rs = declare_dependency(link_with: [_chardev_rs], dependencies: [qemu_macros, chardev, qemuutil]) diff --git a/rust/chardev/src/chardev.rs b/rust/chardev/src/chardev.rs index 072d806e4a..cb6f99398e 100644 --- a/rust/chardev/src/chardev.rs +++ b/rust/chardev/src/chardev.rs @@ -26,7 +26,7 @@ use crate::bindings; /// A safe wrapper around [`bindings::Chardev`]. #[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] +#[derive(qemu_macros::Wrapper)] pub struct Chardev(Opaque); pub type ChardevClass = bindings::ChardevClass; diff --git a/rust/common/src/opaque.rs b/rust/common/src/opaque.rs index 97ed3e8452..3b3263acaa 100644 --- a/rust/common/src/opaque.rs +++ b/rust/common/src/opaque.rs @@ -192,7 +192,7 @@ impl Opaque { /// Annotates [`Self`] as a transparent wrapper for another type. /// -/// Usually defined via the [`qemu_api_macros::Wrapper`] derive macro. +/// Usually defined via the [`qemu_macros::Wrapper`] derive macro. /// /// # Examples /// @@ -228,7 +228,7 @@ impl Opaque { /// /// They are not defined here to allow them to be `const`. /// -/// [`qemu_api_macros::Wrapper`]: ../../qemu_api_macros/derive.Wrapper.html +/// [`qemu_macros::Wrapper`]: ../../qemu_macros/derive.Wrapper.html pub unsafe trait Wrapper { type Wrapped; } diff --git a/rust/hw/char/pl011/Cargo.toml b/rust/hw/char/pl011/Cargo.toml index 830d88586b..9e451fc0aa 100644 --- a/rust/hw/char/pl011/Cargo.toml +++ b/rust/hw/char/pl011/Cargo.toml @@ -25,7 +25,7 @@ chardev = { path = "../../../chardev" } system = { path = "../../../system" } hwcore = { path = "../../../hw/core" } qemu_api = { path = "../../../qemu-api" } -qemu_api_macros = { path = "../../../qemu-api-macros" } +qemu_macros = { path = "../../../qemu-macros" } [lints] workspace = true diff --git a/rust/hw/char/pl011/meson.build b/rust/hw/char/pl011/meson.build index fac0432113..bad6a839c3 100644 --- a/rust/hw/char/pl011/meson.build +++ b/rust/hw/char/pl011/meson.build @@ -12,7 +12,7 @@ _libpl011_rs = static_library( util_rs, migration_rs, bql_rs, - qemu_api_macros, + qemu_macros, qom_rs, chardev_rs, system_rs, @@ -24,6 +24,6 @@ rust_devices_ss.add(when: 'CONFIG_X_PL011_RUST', if_true: [declare_dependency( link_whole: [_libpl011_rs], # Putting proc macro crates in `dependencies` is necessary for Meson to find # them when compiling the root per-target static rust lib. - dependencies: [bilge_impl_rs, qemu_api_macros], + dependencies: [bilge_impl_rs, qemu_macros], variables: {'crate': 'pl011'}, )]) diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index a6a17d9f2d..3010b6d983 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -97,7 +97,7 @@ pub struct PL011Registers { } #[repr(C)] -#[derive(qemu_api_macros::Object, qemu_api_macros::Device)] +#[derive(qemu_macros::Object, qemu_macros::Device)] /// PL011 Device Model in QEMU pub struct PL011State { pub parent_obj: ParentField, @@ -683,7 +683,7 @@ pub unsafe extern "C" fn pl011_create( } #[repr(C)] -#[derive(qemu_api_macros::Object, qemu_api_macros::Device)] +#[derive(qemu_macros::Object, qemu_macros::Device)] /// PL011 Luminary device model. pub struct PL011Luminary { parent_obj: ParentField, diff --git a/rust/hw/char/pl011/src/registers.rs b/rust/hw/char/pl011/src/registers.rs index 2bfbd81095..a1c41347ed 100644 --- a/rust/hw/char/pl011/src/registers.rs +++ b/rust/hw/char/pl011/src/registers.rs @@ -16,7 +16,7 @@ use migration::{impl_vmstate_bitsized, impl_vmstate_forward}; #[doc(alias = "offset")] #[allow(non_camel_case_types)] #[repr(u64)] -#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] +#[derive(Debug, Eq, PartialEq, qemu_macros::TryInto)] pub enum RegisterOffset { /// Data Register /// diff --git a/rust/hw/core/Cargo.toml b/rust/hw/core/Cargo.toml index 0b35380264..0eb9ffee26 100644 --- a/rust/hw/core/Cargo.toml +++ b/rust/hw/core/Cargo.toml @@ -20,7 +20,7 @@ chardev = { path = "../../chardev" } migration = { path = "../../migration" } system = { path = "../../system" } util = { path = "../../util" } -qemu_api_macros = { path = "../../qemu-api-macros" } +qemu_macros = { path = "../../qemu-macros" } [lints] workspace = true diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build index 7dd1ade6f0..67eacf854f 100644 --- a/rust/hw/core/meson.build +++ b/rust/hw/core/meson.build @@ -58,7 +58,7 @@ _hwcore_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', link_with: [_bql_rs, _chardev_rs, _migration_rs, _qom_rs, _system_rs, _util_rs], - dependencies: [qemu_api_macros, common_rs], + dependencies: [qemu_macros, common_rs], ) hwcore_rs = declare_dependency(link_with: [_hwcore_rs], @@ -71,7 +71,7 @@ test('rust-hwcore-rs-integration', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_args: ['--test'], install: false, - dependencies: [common_rs, hwcore_rs, bql_rs, migration_rs, qemu_api_macros, util_rs]), + dependencies: [common_rs, hwcore_rs, bql_rs, migration_rs, qemu_macros, util_rs]), args: [ '--test', '--test-threads', '1', '--format', 'pretty', diff --git a/rust/hw/core/src/irq.rs b/rust/hw/core/src/irq.rs index fead2bbe8e..d8d964cad2 100644 --- a/rust/hw/core/src/irq.rs +++ b/rust/hw/core/src/irq.rs @@ -18,7 +18,7 @@ use crate::bindings::{self, qemu_set_irq}; /// An opaque wrapper around [`bindings::IRQState`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, qemu_macros::Wrapper)] pub struct IRQState(Opaque); /// Interrupt sources are used by devices to pass changes to a value (typically diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs index 8e9702ce0b..c9faf44a71 100644 --- a/rust/hw/core/src/qdev.rs +++ b/rust/hw/core/src/qdev.rs @@ -23,7 +23,7 @@ use crate::{ /// A safe wrapper around [`bindings::Clock`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, qemu_macros::Wrapper)] pub struct Clock(Opaque); unsafe impl Send for Clock {} @@ -31,7 +31,7 @@ unsafe impl Sync for Clock {} /// A safe wrapper around [`bindings::DeviceState`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, qemu_macros::Wrapper)] pub struct DeviceState(Opaque); unsafe impl Send for DeviceState {} @@ -101,7 +101,7 @@ unsafe extern "C" fn rust_resettable_exit_fn( /// Helper trait to return pointer to a [`bindings::PropertyInfo`] for a type. /// -/// This trait is used by [`qemu_api_macros::Device`] derive macro. +/// This trait is used by [`qemu_macros::Device`] derive macro. /// /// Base types that already have `qdev_prop_*` globals in the QEMU API should /// use those values as exported by the [`bindings`] module, instead of diff --git a/rust/hw/core/src/sysbus.rs b/rust/hw/core/src/sysbus.rs index dda71ebda7..92c7449b80 100644 --- a/rust/hw/core/src/sysbus.rs +++ b/rust/hw/core/src/sysbus.rs @@ -19,7 +19,7 @@ use crate::{ /// A safe wrapper around [`bindings::SysBusDevice`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, qemu_macros::Wrapper)] pub struct SysBusDevice(Opaque); unsafe impl Send for SysBusDevice {} diff --git a/rust/hw/core/tests/tests.rs b/rust/hw/core/tests/tests.rs index 21ee301fa6..2f08b8f3bf 100644 --- a/rust/hw/core/tests/tests.rs +++ b/rust/hw/core/tests/tests.rs @@ -17,7 +17,7 @@ pub const VMSTATE: VMStateDescription = VMStateDescriptionBuilder::< .build(); #[repr(C)] -#[derive(qemu_api_macros::Object, qemu_api_macros::Device)] +#[derive(qemu_macros::Object, qemu_macros::Device)] pub struct DummyState { parent: ParentField, #[property(rename = "migrate-clk", default = true)] @@ -54,7 +54,7 @@ impl DeviceImpl for DummyState { } #[repr(C)] -#[derive(qemu_api_macros::Object, qemu_api_macros::Device)] +#[derive(qemu_macros::Object, qemu_macros::Device)] pub struct DummyChildState { parent: ParentField, } diff --git a/rust/hw/timer/hpet/Cargo.toml b/rust/hw/timer/hpet/Cargo.toml index e28d66f645..68e8187bb8 100644 --- a/rust/hw/timer/hpet/Cargo.toml +++ b/rust/hw/timer/hpet/Cargo.toml @@ -18,7 +18,7 @@ bql = { path = "../../../bql" } qom = { path = "../../../qom" } system = { path = "../../../system" } qemu_api = { path = "../../../qemu-api" } -qemu_api_macros = { path = "../../../qemu-api-macros" } +qemu_macros = { path = "../../../qemu-macros" } hwcore = { path = "../../../hw/core" } [lints] diff --git a/rust/hw/timer/hpet/meson.build b/rust/hw/timer/hpet/meson.build index e6f99b6778..3b94d5ec0a 100644 --- a/rust/hw/timer/hpet/meson.build +++ b/rust/hw/timer/hpet/meson.build @@ -9,7 +9,7 @@ _libhpet_rs = static_library( util_rs, migration_rs, bql_rs, - qemu_api_macros, + qemu_macros, qom_rs, system_rs, hwcore_rs, @@ -20,6 +20,6 @@ rust_devices_ss.add(when: 'CONFIG_X_HPET_RUST', if_true: [declare_dependency( link_whole: [_libhpet_rs], # Putting proc macro crates in `dependencies` is necessary for Meson to find # them when compiling the root per-target static rust lib. - dependencies: [qemu_api_macros], + dependencies: [qemu_macros], variables: {'crate': 'hpet'}, )]) diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs index 3031539744..07e0f639fc 100644 --- a/rust/hw/timer/hpet/src/device.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -97,7 +97,7 @@ const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; /// Timer N Interrupt Routing Capability (bits 32:63) const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; -#[derive(qemu_api_macros::TryInto)] +#[derive(qemu_macros::TryInto)] #[repr(u64)] #[allow(non_camel_case_types)] /// Timer registers, masked by 0x18 @@ -110,7 +110,7 @@ enum TimerRegister { ROUTE = 16, } -#[derive(qemu_api_macros::TryInto)] +#[derive(qemu_macros::TryInto)] #[repr(u64)] #[allow(non_camel_case_types)] /// Global registers @@ -520,7 +520,7 @@ impl HPETTimer { /// HPET Event Timer Block Abstraction #[repr(C)] -#[derive(qemu_api_macros::Object)] +#[derive(qemu_macros::Object)] pub struct HPETState { parent_obj: ParentField, iomem: MemoryRegion, diff --git a/rust/meson.build b/rust/meson.build index 041b0a473e..9f6a0b161d 100644 --- a/rust/meson.build +++ b/rust/meson.build @@ -23,7 +23,7 @@ qemuutil_rs = qemuutil.partial_dependency(link_args: true, links: true) genrs = [] subdir('common') -subdir('qemu-api-macros') +subdir('qemu-macros') subdir('bits') subdir('util') subdir('migration') diff --git a/rust/migration/Cargo.toml b/rust/migration/Cargo.toml index 98e6df2109..66af81e0a3 100644 --- a/rust/migration/Cargo.toml +++ b/rust/migration/Cargo.toml @@ -15,7 +15,7 @@ rust-version.workspace = true [dependencies] common = { path = "../common" } util = { path = "../util" } -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } [lints] workspace = true diff --git a/rust/qemu-api-macros/Cargo.toml b/rust/qemu-api-macros/Cargo.toml deleted file mode 100644 index 0cd40c8e16..0000000000 --- a/rust/qemu-api-macros/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "qemu_api_macros" -version = "0.1.0" -authors = ["Manos Pitsidianakis "] -description = "Rust bindings for QEMU - Utility macros" -resolver = "2" -publish = false - -edition.workspace = true -homepage.workspace = true -license.workspace = true -repository.workspace = true -rust-version.workspace = true - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1" -quote = "1" -syn = { version = "2", features = ["extra-traits"] } - -[lints] -workspace = true diff --git a/rust/qemu-api-macros/meson.build b/rust/qemu-api-macros/meson.build deleted file mode 100644 index 2152bcb99b..0000000000 --- a/rust/qemu-api-macros/meson.build +++ /dev/null @@ -1,22 +0,0 @@ -_qemu_api_macros_rs = rust.proc_macro( - 'qemu_api_macros', - files('src/lib.rs'), - override_options: ['rust_std=2021', 'build.rust_std=2021'], - rust_args: [ - '--cfg', 'use_fallback', - '--cfg', 'feature="syn-error"', - '--cfg', 'feature="proc-macro"', - ], - dependencies: [ - proc_macro2_rs_native, - quote_rs_native, - syn_rs_native, - ], -) - -qemu_api_macros = declare_dependency( - link_with: _qemu_api_macros_rs, -) - -rust.test('rust-qemu-api-macros-tests', _qemu_api_macros_rs, - suite: ['unit', 'rust']) diff --git a/rust/qemu-api-macros/src/bits.rs b/rust/qemu-api-macros/src/bits.rs deleted file mode 100644 index a80a3b9fee..0000000000 --- a/rust/qemu-api-macros/src/bits.rs +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: MIT or Apache-2.0 or GPL-2.0-or-later - -// shadowing is useful together with "if let" -#![allow(clippy::shadow_unrelated)] - -use proc_macro2::{ - Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT, -}; -use syn::Error; - -pub struct BitsConstInternal { - typ: TokenTree, -} - -fn paren(ts: TokenStream) -> TokenTree { - TT::Group(Group::new(Delimiter::Parenthesis, ts)) -} - -fn ident(s: &'static str) -> TokenTree { - TT::Ident(Ident::new(s, Span::call_site())) -} - -fn punct(ch: char) -> TokenTree { - TT::Punct(Punct::new(ch, Spacing::Alone)) -} - -/// Implements a recursive-descent parser that translates Boolean expressions on -/// bitmasks to invocations of `const` functions defined by the `bits!` macro. -impl BitsConstInternal { - // primary ::= '(' or ')' - // | ident - // | '!' ident - fn parse_primary( - &self, - tok: TokenTree, - it: &mut dyn Iterator, - out: &mut TokenStream, - ) -> Result, Error> { - let next = match tok { - TT::Group(ref g) => { - if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None { - return Err(Error::new(g.span(), "expected parenthesis")); - } - let mut stream = g.stream().into_iter(); - let Some(first_tok) = stream.next() else { - return Err(Error::new(g.span(), "expected operand, found ')'")); - }; - let mut output = TokenStream::new(); - // start from the lowest precedence - let next = self.parse_or(first_tok, &mut stream, &mut output)?; - if let Some(tok) = next { - return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); - } - out.extend(Some(paren(output))); - it.next() - } - TT::Ident(_) => { - let mut output = TokenStream::new(); - output.extend([ - self.typ.clone(), - TT::Punct(Punct::new(':', Spacing::Joint)), - TT::Punct(Punct::new(':', Spacing::Joint)), - tok, - ]); - out.extend(Some(paren(output))); - it.next() - } - TT::Punct(ref p) => { - if p.as_char() != '!' { - return Err(Error::new(p.span(), "expected operand")); - } - let Some(rhs_tok) = it.next() else { - return Err(Error::new(p.span(), "expected operand at end of input")); - }; - let next = self.parse_primary(rhs_tok, it, out)?; - out.extend([punct('.'), ident("invert"), paren(TokenStream::new())]); - next - } - _ => { - return Err(Error::new(tok.span(), "unexpected literal")); - } - }; - Ok(next) - } - - fn parse_binop< - F: Fn( - &Self, - TokenTree, - &mut dyn Iterator, - &mut TokenStream, - ) -> Result, Error>, - >( - &self, - tok: TokenTree, - it: &mut dyn Iterator, - out: &mut TokenStream, - ch: char, - f: F, - method: &'static str, - ) -> Result, Error> { - let mut next = f(self, tok, it, out)?; - while next.is_some() { - let op = next.as_ref().unwrap(); - let TT::Punct(ref p) = op else { break }; - if p.as_char() != ch { - break; - } - - let Some(rhs_tok) = it.next() else { - return Err(Error::new(p.span(), "expected operand at end of input")); - }; - let mut rhs = TokenStream::new(); - next = f(self, rhs_tok, it, &mut rhs)?; - out.extend([punct('.'), ident(method), paren(rhs)]); - } - Ok(next) - } - - // sub ::= primary ('-' primary)* - pub fn parse_sub( - &self, - tok: TokenTree, - it: &mut dyn Iterator, - out: &mut TokenStream, - ) -> Result, Error> { - self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference") - } - - // and ::= sub ('&' sub)* - fn parse_and( - &self, - tok: TokenTree, - it: &mut dyn Iterator, - out: &mut TokenStream, - ) -> Result, Error> { - self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection") - } - - // xor ::= and ('&' and)* - fn parse_xor( - &self, - tok: TokenTree, - it: &mut dyn Iterator, - out: &mut TokenStream, - ) -> Result, Error> { - self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference") - } - - // or ::= xor ('|' xor)* - pub fn parse_or( - &self, - tok: TokenTree, - it: &mut dyn Iterator, - out: &mut TokenStream, - ) -> Result, Error> { - self.parse_binop(tok, it, out, '|', Self::parse_xor, "union") - } - - pub fn parse( - it: &mut dyn Iterator, - ) -> Result { - let mut pos = Span::call_site(); - let mut typ = proc_macro2::TokenStream::new(); - - // Gobble everything up to an `@` sign, which is followed by a - // parenthesized expression; that is, all token trees except the - // last two form the type. - let next = loop { - let tok = it.next(); - if let Some(ref t) = tok { - pos = t.span(); - } - match tok { - None => break None, - Some(TT::Punct(ref p)) if p.as_char() == '@' => { - let tok = it.next(); - if let Some(ref t) = tok { - pos = t.span(); - } - break tok; - } - Some(x) => typ.extend(Some(x)), - } - }; - - let Some(tok) = next else { - return Err(Error::new( - pos, - "expected expression, do not call this macro directly", - )); - }; - let TT::Group(ref _group) = tok else { - return Err(Error::new( - tok.span(), - "expected parenthesis, do not call this macro directly", - )); - }; - let mut out = TokenStream::new(); - let state = Self { - typ: TT::Group(Group::new(Delimiter::None, typ)), - }; - - let next = state.parse_primary(tok, it, &mut out)?; - - // A parenthesized expression is a single production of the grammar, - // so the input must have reached the last token. - if let Some(tok) = next { - return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); - } - Ok(out) - } -} diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs deleted file mode 100644 index 830b432698..0000000000 --- a/rust/qemu-api-macros/src/lib.rs +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright 2024, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -use proc_macro::TokenStream; -use quote::{quote, quote_spanned, ToTokens}; -use syn::{ - parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - token::Comma, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, - Variant, -}; -mod bits; -use bits::BitsConstInternal; - -#[cfg(test)] -mod tests; - -fn get_fields<'a>( - input: &'a DeriveInput, - msg: &str, -) -> Result<&'a Punctuated, Error> { - let Data::Struct(ref s) = &input.data else { - return Err(Error::new( - input.ident.span(), - format!("Struct required for {msg}"), - )); - }; - let Fields::Named(ref fs) = &s.fields else { - return Err(Error::new( - input.ident.span(), - format!("Named fields required for {msg}"), - )); - }; - Ok(&fs.named) -} - -fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, Error> { - let Data::Struct(ref s) = &input.data else { - return Err(Error::new( - input.ident.span(), - format!("Struct required for {msg}"), - )); - }; - let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { - return Err(Error::new( - s.fields.span(), - format!("Tuple struct required for {msg}"), - )); - }; - if unnamed.len() != 1 { - return Err(Error::new( - s.fields.span(), - format!("A single field is required for {msg}"), - )); - } - Ok(&unnamed[0]) -} - -fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { - let expected = parse_quote! { #[repr(C)] }; - - if input.attrs.iter().any(|attr| attr == &expected) { - Ok(()) - } else { - Err(Error::new( - input.ident.span(), - format!("#[repr(C)] required for {msg}"), - )) - } -} - -fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { - let expected = parse_quote! { #[repr(transparent)] }; - - if input.attrs.iter().any(|attr| attr == &expected) { - Ok(()) - } else { - Err(Error::new( - input.ident.span(), - format!("#[repr(transparent)] required for {msg}"), - )) - } -} - -fn derive_object_or_error(input: DeriveInput) -> Result { - is_c_repr(&input, "#[derive(Object)]")?; - - let name = &input.ident; - let parent = &get_fields(&input, "#[derive(Object)]")? - .get(0) - .ok_or_else(|| { - Error::new( - input.ident.span(), - "#[derive(Object)] requires a parent field", - ) - })? - .ident; - - Ok(quote! { - ::common::assert_field_type!(#name, #parent, - ::qom::ParentField<<#name as ::qom::ObjectImpl>::ParentType>); - - ::util::module_init! { - MODULE_INIT_QOM => unsafe { - ::qom::type_register_static(&<#name as ::qom::ObjectImpl>::TYPE_INFO); - } - } - }) -} - -#[proc_macro_derive(Object)] -pub fn derive_object(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - derive_object_or_error(input) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - -fn derive_opaque_or_error(input: DeriveInput) -> Result { - is_transparent_repr(&input, "#[derive(Wrapper)]")?; - - let name = &input.ident; - let field = &get_unnamed_field(&input, "#[derive(Wrapper)]")?; - let typ = &field.ty; - - Ok(quote! { - unsafe impl ::common::opaque::Wrapper for #name { - type Wrapped = <#typ as ::common::opaque::Wrapper>::Wrapped; - } - impl #name { - pub unsafe fn from_raw<'a>(ptr: *mut ::Wrapped) -> &'a Self { - let ptr = ::std::ptr::NonNull::new(ptr).unwrap().cast::(); - unsafe { ptr.as_ref() } - } - - pub const fn as_mut_ptr(&self) -> *mut ::Wrapped { - self.0.as_mut_ptr() - } - - pub const fn as_ptr(&self) -> *const ::Wrapped { - self.0.as_ptr() - } - - pub const fn as_void_ptr(&self) -> *mut ::core::ffi::c_void { - self.0.as_void_ptr() - } - - pub const fn raw_get(slot: *mut Self) -> *mut ::Wrapped { - slot.cast() - } - } - }) -} - -#[derive(Debug)] -enum DevicePropertyName { - CStr(syn::LitCStr), - Str(syn::LitStr), -} - -#[derive(Debug)] -struct DeviceProperty { - rename: Option, - defval: Option, -} - -impl Parse for DeviceProperty { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let _: syn::Token![#] = input.parse()?; - let bracketed; - _ = syn::bracketed!(bracketed in input); - let attribute = bracketed.parse::()?; - debug_assert_eq!(&attribute.to_string(), "property"); - let mut retval = Self { - rename: None, - defval: None, - }; - let content; - _ = syn::parenthesized!(content in bracketed); - while !content.is_empty() { - let value: syn::Ident = content.parse()?; - if value == "rename" { - let _: syn::Token![=] = content.parse()?; - if retval.rename.is_some() { - return Err(syn::Error::new( - value.span(), - "`rename` can only be used at most once", - )); - } - if content.peek(syn::LitStr) { - retval.rename = Some(DevicePropertyName::Str(content.parse::()?)); - } else { - retval.rename = - Some(DevicePropertyName::CStr(content.parse::()?)); - } - } else if value == "default" { - let _: syn::Token![=] = content.parse()?; - if retval.defval.is_some() { - return Err(syn::Error::new( - value.span(), - "`default` can only be used at most once", - )); - } - retval.defval = Some(content.parse()?); - } else { - return Err(syn::Error::new( - value.span(), - format!("unrecognized field `{value}`"), - )); - } - - if !content.is_empty() { - let _: syn::Token![,] = content.parse()?; - } - } - Ok(retval) - } -} - -#[proc_macro_derive(Device, attributes(property))] -pub fn derive_device(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - derive_device_or_error(input) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - -fn derive_device_or_error(input: DeriveInput) -> Result { - is_c_repr(&input, "#[derive(Device)]")?; - let properties: Vec<(syn::Field, DeviceProperty)> = get_fields(&input, "#[derive(Device)]")? - .iter() - .flat_map(|f| { - f.attrs - .iter() - .filter(|a| a.path().is_ident("property")) - .map(|a| Ok((f.clone(), syn::parse2(a.to_token_stream())?))) - }) - .collect::, Error>>()?; - let name = &input.ident; - let mut properties_expanded = vec![]; - - for (field, prop) in properties { - let DeviceProperty { rename, defval } = prop; - let field_name = field.ident.unwrap(); - macro_rules! str_to_c_str { - ($value:expr, $span:expr) => {{ - let (value, span) = ($value, $span); - let cstr = std::ffi::CString::new(value.as_str()).map_err(|err| { - Error::new( - span, - format!( - "Property name `{value}` cannot be represented as a C string: {err}" - ), - ) - })?; - let cstr_lit = syn::LitCStr::new(&cstr, span); - Ok(quote! { #cstr_lit }) - }}; - } - - let prop_name = rename.map_or_else( - || str_to_c_str!(field_name.to_string(), field_name.span()), - |rename| -> Result { - match rename { - DevicePropertyName::CStr(cstr_lit) => Ok(quote! { #cstr_lit }), - DevicePropertyName::Str(str_lit) => { - str_to_c_str!(str_lit.value(), str_lit.span()) - } - } - }, - )?; - let field_ty = field.ty.clone(); - let qdev_prop = quote! { <#field_ty as ::hwcore::QDevProp>::VALUE }; - let set_default = defval.is_some(); - let defval = defval.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); - properties_expanded.push(quote! { - ::hwcore::bindings::Property { - name: ::std::ffi::CStr::as_ptr(#prop_name), - info: #qdev_prop , - offset: ::core::mem::offset_of!(#name, #field_name) as isize, - set_default: #set_default, - defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: #defval as u64 }, - ..::common::Zeroable::ZERO - } - }); - } - - Ok(quote_spanned! {input.span() => - unsafe impl ::hwcore::DevicePropertiesImpl for #name { - const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ - #(#properties_expanded),* - ]; - } - }) -} - -#[proc_macro_derive(Wrapper)] -pub fn derive_opaque(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - derive_opaque_or_error(input) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - -#[allow(non_snake_case)] -fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { - let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); - if let Some(repr) = repr { - let nested = repr.parse_args_with(Punctuated::::parse_terminated)?; - for meta in nested { - match meta { - Meta::Path(path) if path.is_ident("u8") => return Ok(path), - Meta::Path(path) if path.is_ident("u16") => return Ok(path), - Meta::Path(path) if path.is_ident("u32") => return Ok(path), - Meta::Path(path) if path.is_ident("u64") => return Ok(path), - _ => {} - } - } - } - - Err(Error::new( - input.ident.span(), - format!("#[repr(u8/u16/u32/u64) required for {msg}"), - )) -} - -fn get_variants(input: &DeriveInput) -> Result<&Punctuated, Error> { - let Data::Enum(ref e) = &input.data else { - return Err(Error::new( - input.ident.span(), - "Cannot derive TryInto for union or struct.", - )); - }; - if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { - return Err(Error::new( - v.fields.span(), - "Cannot derive TryInto for enum with non-unit variants.", - )); - } - Ok(&e.variants) -} - -#[rustfmt::skip::macros(quote)] -fn derive_tryinto_body( - name: &Ident, - variants: &Punctuated, - repr: &Path, -) -> Result { - let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); - - Ok(quote! { - #(const #discriminants: #repr = #name::#discriminants as #repr;)* - match value { - #(#discriminants => core::result::Result::Ok(#name::#discriminants),)* - _ => core::result::Result::Err(value), - } - }) -} - -#[rustfmt::skip::macros(quote)] -fn derive_tryinto_or_error(input: DeriveInput) -> Result { - let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; - let name = &input.ident; - let body = derive_tryinto_body(name, get_variants(&input)?, &repr)?; - let errmsg = format!("invalid value for {name}"); - - Ok(quote! { - impl #name { - #[allow(dead_code)] - pub const fn into_bits(self) -> #repr { - self as #repr - } - - #[allow(dead_code)] - pub const fn from_bits(value: #repr) -> Self { - match ({ - #body - }) { - Ok(x) => x, - Err(_) => panic!(#errmsg), - } - } - } - impl core::convert::TryFrom<#repr> for #name { - type Error = #repr; - - #[allow(ambiguous_associated_items)] - fn try_from(value: #repr) -> Result { - #body - } - } - }) -} - -#[proc_macro_derive(TryInto)] -pub fn derive_tryinto(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - derive_tryinto_or_error(input) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} - -#[proc_macro] -pub fn bits_const_internal(ts: TokenStream) -> TokenStream { - let ts = proc_macro2::TokenStream::from(ts); - let mut it = ts.into_iter(); - - BitsConstInternal::parse(&mut it) - .unwrap_or_else(syn::Error::into_compile_error) - .into() -} diff --git a/rust/qemu-api-macros/src/tests.rs b/rust/qemu-api-macros/src/tests.rs deleted file mode 100644 index 9ab7eab7f3..0000000000 --- a/rust/qemu-api-macros/src/tests.rs +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2025, Linaro Limited -// Author(s): Manos Pitsidianakis -// SPDX-License-Identifier: GPL-2.0-or-later - -use quote::quote; - -use super::*; - -macro_rules! derive_compile_fail { - ($derive_fn:ident, $input:expr, $($error_msg:expr),+ $(,)?) => {{ - let input: proc_macro2::TokenStream = $input; - let error_msg = &[$( quote! { ::core::compile_error! { $error_msg } } ),*]; - let derive_fn: fn(input: syn::DeriveInput) -> Result = - $derive_fn; - - let input: syn::DeriveInput = syn::parse2(input).unwrap(); - let result = derive_fn(input); - let err = result.unwrap_err().into_compile_error(); - assert_eq!( - err.to_string(), - quote! { #(#error_msg)* }.to_string() - ); - }}; -} - -macro_rules! derive_compile { - ($derive_fn:ident, $input:expr, $($expected:tt)*) => {{ - let input: proc_macro2::TokenStream = $input; - let expected: proc_macro2::TokenStream = $($expected)*; - let derive_fn: fn(input: syn::DeriveInput) -> Result = - $derive_fn; - - let input: syn::DeriveInput = syn::parse2(input).unwrap(); - let result = derive_fn(input).unwrap(); - assert_eq!(result.to_string(), expected.to_string()); - }}; -} - -#[test] -fn test_derive_device() { - // Check that repr(C) is used - derive_compile_fail!( - derive_device_or_error, - quote! { - #[derive(Device)] - struct Foo { - _unused: [u8; 0], - } - }, - "#[repr(C)] required for #[derive(Device)]" - ); - // Check that invalid/misspelled attributes raise an error - derive_compile_fail!( - derive_device_or_error, - quote! { - #[repr(C)] - #[derive(Device)] - struct DummyState { - #[property(defalt = true)] - migrate_clock: bool, - } - }, - "unrecognized field `defalt`" - ); - // Check that repeated attributes are not allowed: - derive_compile_fail!( - derive_device_or_error, - quote! { - #[repr(C)] - #[derive(Device)] - struct DummyState { - #[property(rename = "migrate-clk", rename = "migrate-clk", default = true)] - migrate_clock: bool, - } - }, - "`rename` can only be used at most once" - ); - derive_compile_fail!( - derive_device_or_error, - quote! { - #[repr(C)] - #[derive(Device)] - struct DummyState { - #[property(default = true, default = true)] - migrate_clock: bool, - } - }, - "`default` can only be used at most once" - ); - // Check that the field name is preserved when `rename` isn't used: - derive_compile!( - derive_device_or_error, - quote! { - #[repr(C)] - #[derive(Device)] - pub struct DummyState { - parent: ParentField, - #[property(default = true)] - migrate_clock: bool, - } - }, - quote! { - unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { - const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ - ::hwcore::bindings::Property { - name: ::std::ffi::CStr::as_ptr(c"migrate_clock"), - info: ::VALUE, - offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, - set_default: true, - defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, - ..::common::Zeroable::ZERO - } - ]; - } - } - ); - // Check that `rename` value is used for the property name when used: - derive_compile!( - derive_device_or_error, - quote! { - #[repr(C)] - #[derive(Device)] - pub struct DummyState { - parent: ParentField, - #[property(rename = "migrate-clk", default = true)] - migrate_clock: bool, - } - }, - quote! { - unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { - const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ - ::hwcore::bindings::Property { - name: ::std::ffi::CStr::as_ptr(c"migrate-clk"), - info: ::VALUE, - offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, - set_default: true, - defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, - ..::common::Zeroable::ZERO - } - ]; - } - } - ); -} - -#[test] -fn test_derive_object() { - derive_compile_fail!( - derive_object_or_error, - quote! { - #[derive(Object)] - struct Foo { - _unused: [u8; 0], - } - }, - "#[repr(C)] required for #[derive(Object)]" - ); - derive_compile!( - derive_object_or_error, - quote! { - #[derive(Object)] - #[repr(C)] - struct Foo { - _unused: [u8; 0], - } - }, - quote! { - ::common::assert_field_type!( - Foo, - _unused, - ::qom::ParentField<::ParentType> - ); - ::util::module_init! { - MODULE_INIT_QOM => unsafe { - ::qom::type_register_static(&::TYPE_INFO); - } - } - } - ); -} - -#[test] -fn test_derive_tryinto() { - derive_compile_fail!( - derive_tryinto_or_error, - quote! { - #[derive(TryInto)] - struct Foo { - _unused: [u8; 0], - } - }, - "#[repr(u8/u16/u32/u64) required for #[derive(TryInto)]" - ); - derive_compile!( - derive_tryinto_or_error, - quote! { - #[derive(TryInto)] - #[repr(u8)] - enum Foo { - First = 0, - Second, - } - }, - quote! { - impl Foo { - #[allow(dead_code)] - pub const fn into_bits(self) -> u8 { - self as u8 - } - - #[allow(dead_code)] - pub const fn from_bits(value: u8) -> Self { - match ({ - const First: u8 = Foo::First as u8; - const Second: u8 = Foo::Second as u8; - match value { - First => core::result::Result::Ok(Foo::First), - Second => core::result::Result::Ok(Foo::Second), - _ => core::result::Result::Err(value), - } - }) { - Ok(x) => x, - Err(_) => panic!("invalid value for Foo"), - } - } - } - - impl core::convert::TryFrom for Foo { - type Error = u8; - - #[allow(ambiguous_associated_items)] - fn try_from(value: u8) -> Result { - const First: u8 = Foo::First as u8; - const Second: u8 = Foo::Second as u8; - match value { - First => core::result::Result::Ok(Foo::First), - Second => core::result::Result::Ok(Foo::Second), - _ => core::result::Result::Err(value), - } - } - } - } - ); -} diff --git a/rust/qemu-api/Cargo.toml b/rust/qemu-api/Cargo.toml index 9e7afc7e3a..9abb88aa1f 100644 --- a/rust/qemu-api/Cargo.toml +++ b/rust/qemu-api/Cargo.toml @@ -20,9 +20,9 @@ hwcore = { path = "../hw/core" } migration = { path = "../migration" } util = { path = "../util" } bql = { path = "../bql" } +qemu_macros = { path = "../qemu-macros" } qom = { path = "../qom" } system = { path = "../system" } -qemu_api_macros = { path = "../qemu-api-macros" } [lints] workspace = true diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 2dc638782c..fe81f16d99 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -52,12 +52,12 @@ _qemu_api_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', rust_args: _qemu_api_cfg, - dependencies: [anyhow_rs, bql_rs, chardev_rs, common_rs, foreign_rs, hwcore_rs, libc_rs, migration_rs, qemu_api_macros, + dependencies: [anyhow_rs, bql_rs, chardev_rs, common_rs, foreign_rs, hwcore_rs, libc_rs, migration_rs, qemu_macros, qom_rs, system_rs, util_rs, hwcore], ) qemu_api_rs = declare_dependency(link_with: [_qemu_api_rs], - dependencies: [qemu_api_macros, qom, hwcore, chardev, migration]) + dependencies: [qemu_macros, qom, hwcore, chardev, migration]) test('rust-qemu-api-integration', executable( diff --git a/rust/qemu-macros/Cargo.toml b/rust/qemu-macros/Cargo.toml new file mode 100644 index 0000000000..3b6f1d337f --- /dev/null +++ b/rust/qemu-macros/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "qemu_macros" +version = "0.1.0" +authors = ["Manos Pitsidianakis "] +description = "Rust bindings for QEMU - Utility macros" +resolver = "2" +publish = false + +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["extra-traits"] } + +[lints] +workspace = true diff --git a/rust/qemu-macros/meson.build b/rust/qemu-macros/meson.build new file mode 100644 index 0000000000..d0b2992e20 --- /dev/null +++ b/rust/qemu-macros/meson.build @@ -0,0 +1,22 @@ +_qemu_macros_rs = rust.proc_macro( + 'qemu_macros', + files('src/lib.rs'), + override_options: ['rust_std=2021', 'build.rust_std=2021'], + rust_args: [ + '--cfg', 'use_fallback', + '--cfg', 'feature="syn-error"', + '--cfg', 'feature="proc-macro"', + ], + dependencies: [ + proc_macro2_rs_native, + quote_rs_native, + syn_rs_native, + ], +) + +qemu_macros = declare_dependency( + link_with: _qemu_macros_rs, +) + +rust.test('rust-qemu-macros-tests', _qemu_macros_rs, + suite: ['unit', 'rust']) diff --git a/rust/qemu-macros/src/bits.rs b/rust/qemu-macros/src/bits.rs new file mode 100644 index 0000000000..a80a3b9fee --- /dev/null +++ b/rust/qemu-macros/src/bits.rs @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MIT or Apache-2.0 or GPL-2.0-or-later + +// shadowing is useful together with "if let" +#![allow(clippy::shadow_unrelated)] + +use proc_macro2::{ + Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT, +}; +use syn::Error; + +pub struct BitsConstInternal { + typ: TokenTree, +} + +fn paren(ts: TokenStream) -> TokenTree { + TT::Group(Group::new(Delimiter::Parenthesis, ts)) +} + +fn ident(s: &'static str) -> TokenTree { + TT::Ident(Ident::new(s, Span::call_site())) +} + +fn punct(ch: char) -> TokenTree { + TT::Punct(Punct::new(ch, Spacing::Alone)) +} + +/// Implements a recursive-descent parser that translates Boolean expressions on +/// bitmasks to invocations of `const` functions defined by the `bits!` macro. +impl BitsConstInternal { + // primary ::= '(' or ')' + // | ident + // | '!' ident + fn parse_primary( + &self, + tok: TokenTree, + it: &mut dyn Iterator, + out: &mut TokenStream, + ) -> Result, Error> { + let next = match tok { + TT::Group(ref g) => { + if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None { + return Err(Error::new(g.span(), "expected parenthesis")); + } + let mut stream = g.stream().into_iter(); + let Some(first_tok) = stream.next() else { + return Err(Error::new(g.span(), "expected operand, found ')'")); + }; + let mut output = TokenStream::new(); + // start from the lowest precedence + let next = self.parse_or(first_tok, &mut stream, &mut output)?; + if let Some(tok) = next { + return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); + } + out.extend(Some(paren(output))); + it.next() + } + TT::Ident(_) => { + let mut output = TokenStream::new(); + output.extend([ + self.typ.clone(), + TT::Punct(Punct::new(':', Spacing::Joint)), + TT::Punct(Punct::new(':', Spacing::Joint)), + tok, + ]); + out.extend(Some(paren(output))); + it.next() + } + TT::Punct(ref p) => { + if p.as_char() != '!' { + return Err(Error::new(p.span(), "expected operand")); + } + let Some(rhs_tok) = it.next() else { + return Err(Error::new(p.span(), "expected operand at end of input")); + }; + let next = self.parse_primary(rhs_tok, it, out)?; + out.extend([punct('.'), ident("invert"), paren(TokenStream::new())]); + next + } + _ => { + return Err(Error::new(tok.span(), "unexpected literal")); + } + }; + Ok(next) + } + + fn parse_binop< + F: Fn( + &Self, + TokenTree, + &mut dyn Iterator, + &mut TokenStream, + ) -> Result, Error>, + >( + &self, + tok: TokenTree, + it: &mut dyn Iterator, + out: &mut TokenStream, + ch: char, + f: F, + method: &'static str, + ) -> Result, Error> { + let mut next = f(self, tok, it, out)?; + while next.is_some() { + let op = next.as_ref().unwrap(); + let TT::Punct(ref p) = op else { break }; + if p.as_char() != ch { + break; + } + + let Some(rhs_tok) = it.next() else { + return Err(Error::new(p.span(), "expected operand at end of input")); + }; + let mut rhs = TokenStream::new(); + next = f(self, rhs_tok, it, &mut rhs)?; + out.extend([punct('.'), ident(method), paren(rhs)]); + } + Ok(next) + } + + // sub ::= primary ('-' primary)* + pub fn parse_sub( + &self, + tok: TokenTree, + it: &mut dyn Iterator, + out: &mut TokenStream, + ) -> Result, Error> { + self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference") + } + + // and ::= sub ('&' sub)* + fn parse_and( + &self, + tok: TokenTree, + it: &mut dyn Iterator, + out: &mut TokenStream, + ) -> Result, Error> { + self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection") + } + + // xor ::= and ('&' and)* + fn parse_xor( + &self, + tok: TokenTree, + it: &mut dyn Iterator, + out: &mut TokenStream, + ) -> Result, Error> { + self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference") + } + + // or ::= xor ('|' xor)* + pub fn parse_or( + &self, + tok: TokenTree, + it: &mut dyn Iterator, + out: &mut TokenStream, + ) -> Result, Error> { + self.parse_binop(tok, it, out, '|', Self::parse_xor, "union") + } + + pub fn parse( + it: &mut dyn Iterator, + ) -> Result { + let mut pos = Span::call_site(); + let mut typ = proc_macro2::TokenStream::new(); + + // Gobble everything up to an `@` sign, which is followed by a + // parenthesized expression; that is, all token trees except the + // last two form the type. + let next = loop { + let tok = it.next(); + if let Some(ref t) = tok { + pos = t.span(); + } + match tok { + None => break None, + Some(TT::Punct(ref p)) if p.as_char() == '@' => { + let tok = it.next(); + if let Some(ref t) = tok { + pos = t.span(); + } + break tok; + } + Some(x) => typ.extend(Some(x)), + } + }; + + let Some(tok) = next else { + return Err(Error::new( + pos, + "expected expression, do not call this macro directly", + )); + }; + let TT::Group(ref _group) = tok else { + return Err(Error::new( + tok.span(), + "expected parenthesis, do not call this macro directly", + )); + }; + let mut out = TokenStream::new(); + let state = Self { + typ: TT::Group(Group::new(Delimiter::None, typ)), + }; + + let next = state.parse_primary(tok, it, &mut out)?; + + // A parenthesized expression is a single production of the grammar, + // so the input must have reached the last token. + if let Some(tok) = next { + return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); + } + Ok(out) + } +} diff --git a/rust/qemu-macros/src/lib.rs b/rust/qemu-macros/src/lib.rs new file mode 100644 index 0000000000..830b432698 --- /dev/null +++ b/rust/qemu-macros/src/lib.rs @@ -0,0 +1,415 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +use proc_macro::TokenStream; +use quote::{quote, quote_spanned, ToTokens}; +use syn::{ + parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, + token::Comma, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, + Variant, +}; +mod bits; +use bits::BitsConstInternal; + +#[cfg(test)] +mod tests; + +fn get_fields<'a>( + input: &'a DeriveInput, + msg: &str, +) -> Result<&'a Punctuated, Error> { + let Data::Struct(ref s) = &input.data else { + return Err(Error::new( + input.ident.span(), + format!("Struct required for {msg}"), + )); + }; + let Fields::Named(ref fs) = &s.fields else { + return Err(Error::new( + input.ident.span(), + format!("Named fields required for {msg}"), + )); + }; + Ok(&fs.named) +} + +fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, Error> { + let Data::Struct(ref s) = &input.data else { + return Err(Error::new( + input.ident.span(), + format!("Struct required for {msg}"), + )); + }; + let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { + return Err(Error::new( + s.fields.span(), + format!("Tuple struct required for {msg}"), + )); + }; + if unnamed.len() != 1 { + return Err(Error::new( + s.fields.span(), + format!("A single field is required for {msg}"), + )); + } + Ok(&unnamed[0]) +} + +fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { + let expected = parse_quote! { #[repr(C)] }; + + if input.attrs.iter().any(|attr| attr == &expected) { + Ok(()) + } else { + Err(Error::new( + input.ident.span(), + format!("#[repr(C)] required for {msg}"), + )) + } +} + +fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { + let expected = parse_quote! { #[repr(transparent)] }; + + if input.attrs.iter().any(|attr| attr == &expected) { + Ok(()) + } else { + Err(Error::new( + input.ident.span(), + format!("#[repr(transparent)] required for {msg}"), + )) + } +} + +fn derive_object_or_error(input: DeriveInput) -> Result { + is_c_repr(&input, "#[derive(Object)]")?; + + let name = &input.ident; + let parent = &get_fields(&input, "#[derive(Object)]")? + .get(0) + .ok_or_else(|| { + Error::new( + input.ident.span(), + "#[derive(Object)] requires a parent field", + ) + })? + .ident; + + Ok(quote! { + ::common::assert_field_type!(#name, #parent, + ::qom::ParentField<<#name as ::qom::ObjectImpl>::ParentType>); + + ::util::module_init! { + MODULE_INIT_QOM => unsafe { + ::qom::type_register_static(&<#name as ::qom::ObjectImpl>::TYPE_INFO); + } + } + }) +} + +#[proc_macro_derive(Object)] +pub fn derive_object(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_object_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +fn derive_opaque_or_error(input: DeriveInput) -> Result { + is_transparent_repr(&input, "#[derive(Wrapper)]")?; + + let name = &input.ident; + let field = &get_unnamed_field(&input, "#[derive(Wrapper)]")?; + let typ = &field.ty; + + Ok(quote! { + unsafe impl ::common::opaque::Wrapper for #name { + type Wrapped = <#typ as ::common::opaque::Wrapper>::Wrapped; + } + impl #name { + pub unsafe fn from_raw<'a>(ptr: *mut ::Wrapped) -> &'a Self { + let ptr = ::std::ptr::NonNull::new(ptr).unwrap().cast::(); + unsafe { ptr.as_ref() } + } + + pub const fn as_mut_ptr(&self) -> *mut ::Wrapped { + self.0.as_mut_ptr() + } + + pub const fn as_ptr(&self) -> *const ::Wrapped { + self.0.as_ptr() + } + + pub const fn as_void_ptr(&self) -> *mut ::core::ffi::c_void { + self.0.as_void_ptr() + } + + pub const fn raw_get(slot: *mut Self) -> *mut ::Wrapped { + slot.cast() + } + } + }) +} + +#[derive(Debug)] +enum DevicePropertyName { + CStr(syn::LitCStr), + Str(syn::LitStr), +} + +#[derive(Debug)] +struct DeviceProperty { + rename: Option, + defval: Option, +} + +impl Parse for DeviceProperty { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let _: syn::Token![#] = input.parse()?; + let bracketed; + _ = syn::bracketed!(bracketed in input); + let attribute = bracketed.parse::()?; + debug_assert_eq!(&attribute.to_string(), "property"); + let mut retval = Self { + rename: None, + defval: None, + }; + let content; + _ = syn::parenthesized!(content in bracketed); + while !content.is_empty() { + let value: syn::Ident = content.parse()?; + if value == "rename" { + let _: syn::Token![=] = content.parse()?; + if retval.rename.is_some() { + return Err(syn::Error::new( + value.span(), + "`rename` can only be used at most once", + )); + } + if content.peek(syn::LitStr) { + retval.rename = Some(DevicePropertyName::Str(content.parse::()?)); + } else { + retval.rename = + Some(DevicePropertyName::CStr(content.parse::()?)); + } + } else if value == "default" { + let _: syn::Token![=] = content.parse()?; + if retval.defval.is_some() { + return Err(syn::Error::new( + value.span(), + "`default` can only be used at most once", + )); + } + retval.defval = Some(content.parse()?); + } else { + return Err(syn::Error::new( + value.span(), + format!("unrecognized field `{value}`"), + )); + } + + if !content.is_empty() { + let _: syn::Token![,] = content.parse()?; + } + } + Ok(retval) + } +} + +#[proc_macro_derive(Device, attributes(property))] +pub fn derive_device(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_device_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +fn derive_device_or_error(input: DeriveInput) -> Result { + is_c_repr(&input, "#[derive(Device)]")?; + let properties: Vec<(syn::Field, DeviceProperty)> = get_fields(&input, "#[derive(Device)]")? + .iter() + .flat_map(|f| { + f.attrs + .iter() + .filter(|a| a.path().is_ident("property")) + .map(|a| Ok((f.clone(), syn::parse2(a.to_token_stream())?))) + }) + .collect::, Error>>()?; + let name = &input.ident; + let mut properties_expanded = vec![]; + + for (field, prop) in properties { + let DeviceProperty { rename, defval } = prop; + let field_name = field.ident.unwrap(); + macro_rules! str_to_c_str { + ($value:expr, $span:expr) => {{ + let (value, span) = ($value, $span); + let cstr = std::ffi::CString::new(value.as_str()).map_err(|err| { + Error::new( + span, + format!( + "Property name `{value}` cannot be represented as a C string: {err}" + ), + ) + })?; + let cstr_lit = syn::LitCStr::new(&cstr, span); + Ok(quote! { #cstr_lit }) + }}; + } + + let prop_name = rename.map_or_else( + || str_to_c_str!(field_name.to_string(), field_name.span()), + |rename| -> Result { + match rename { + DevicePropertyName::CStr(cstr_lit) => Ok(quote! { #cstr_lit }), + DevicePropertyName::Str(str_lit) => { + str_to_c_str!(str_lit.value(), str_lit.span()) + } + } + }, + )?; + let field_ty = field.ty.clone(); + let qdev_prop = quote! { <#field_ty as ::hwcore::QDevProp>::VALUE }; + let set_default = defval.is_some(); + let defval = defval.unwrap_or(syn::Expr::Verbatim(quote! { 0 })); + properties_expanded.push(quote! { + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(#prop_name), + info: #qdev_prop , + offset: ::core::mem::offset_of!(#name, #field_name) as isize, + set_default: #set_default, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: #defval as u64 }, + ..::common::Zeroable::ZERO + } + }); + } + + Ok(quote_spanned! {input.span() => + unsafe impl ::hwcore::DevicePropertiesImpl for #name { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + #(#properties_expanded),* + ]; + } + }) +} + +#[proc_macro_derive(Wrapper)] +pub fn derive_opaque(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_opaque_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[allow(non_snake_case)] +fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result { + let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); + if let Some(repr) = repr { + let nested = repr.parse_args_with(Punctuated::::parse_terminated)?; + for meta in nested { + match meta { + Meta::Path(path) if path.is_ident("u8") => return Ok(path), + Meta::Path(path) if path.is_ident("u16") => return Ok(path), + Meta::Path(path) if path.is_ident("u32") => return Ok(path), + Meta::Path(path) if path.is_ident("u64") => return Ok(path), + _ => {} + } + } + } + + Err(Error::new( + input.ident.span(), + format!("#[repr(u8/u16/u32/u64) required for {msg}"), + )) +} + +fn get_variants(input: &DeriveInput) -> Result<&Punctuated, Error> { + let Data::Enum(ref e) = &input.data else { + return Err(Error::new( + input.ident.span(), + "Cannot derive TryInto for union or struct.", + )); + }; + if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { + return Err(Error::new( + v.fields.span(), + "Cannot derive TryInto for enum with non-unit variants.", + )); + } + Ok(&e.variants) +} + +#[rustfmt::skip::macros(quote)] +fn derive_tryinto_body( + name: &Ident, + variants: &Punctuated, + repr: &Path, +) -> Result { + let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); + + Ok(quote! { + #(const #discriminants: #repr = #name::#discriminants as #repr;)* + match value { + #(#discriminants => core::result::Result::Ok(#name::#discriminants),)* + _ => core::result::Result::Err(value), + } + }) +} + +#[rustfmt::skip::macros(quote)] +fn derive_tryinto_or_error(input: DeriveInput) -> Result { + let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; + let name = &input.ident; + let body = derive_tryinto_body(name, get_variants(&input)?, &repr)?; + let errmsg = format!("invalid value for {name}"); + + Ok(quote! { + impl #name { + #[allow(dead_code)] + pub const fn into_bits(self) -> #repr { + self as #repr + } + + #[allow(dead_code)] + pub const fn from_bits(value: #repr) -> Self { + match ({ + #body + }) { + Ok(x) => x, + Err(_) => panic!(#errmsg), + } + } + } + impl core::convert::TryFrom<#repr> for #name { + type Error = #repr; + + #[allow(ambiguous_associated_items)] + fn try_from(value: #repr) -> Result { + #body + } + } + }) +} + +#[proc_macro_derive(TryInto)] +pub fn derive_tryinto(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + derive_tryinto_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro] +pub fn bits_const_internal(ts: TokenStream) -> TokenStream { + let ts = proc_macro2::TokenStream::from(ts); + let mut it = ts.into_iter(); + + BitsConstInternal::parse(&mut it) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/rust/qemu-macros/src/tests.rs b/rust/qemu-macros/src/tests.rs new file mode 100644 index 0000000000..9ab7eab7f3 --- /dev/null +++ b/rust/qemu-macros/src/tests.rs @@ -0,0 +1,244 @@ +// Copyright 2025, Linaro Limited +// Author(s): Manos Pitsidianakis +// SPDX-License-Identifier: GPL-2.0-or-later + +use quote::quote; + +use super::*; + +macro_rules! derive_compile_fail { + ($derive_fn:ident, $input:expr, $($error_msg:expr),+ $(,)?) => {{ + let input: proc_macro2::TokenStream = $input; + let error_msg = &[$( quote! { ::core::compile_error! { $error_msg } } ),*]; + let derive_fn: fn(input: syn::DeriveInput) -> Result = + $derive_fn; + + let input: syn::DeriveInput = syn::parse2(input).unwrap(); + let result = derive_fn(input); + let err = result.unwrap_err().into_compile_error(); + assert_eq!( + err.to_string(), + quote! { #(#error_msg)* }.to_string() + ); + }}; +} + +macro_rules! derive_compile { + ($derive_fn:ident, $input:expr, $($expected:tt)*) => {{ + let input: proc_macro2::TokenStream = $input; + let expected: proc_macro2::TokenStream = $($expected)*; + let derive_fn: fn(input: syn::DeriveInput) -> Result = + $derive_fn; + + let input: syn::DeriveInput = syn::parse2(input).unwrap(); + let result = derive_fn(input).unwrap(); + assert_eq!(result.to_string(), expected.to_string()); + }}; +} + +#[test] +fn test_derive_device() { + // Check that repr(C) is used + derive_compile_fail!( + derive_device_or_error, + quote! { + #[derive(Device)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(C)] required for #[derive(Device)]" + ); + // Check that invalid/misspelled attributes raise an error + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(defalt = true)] + migrate_clock: bool, + } + }, + "unrecognized field `defalt`" + ); + // Check that repeated attributes are not allowed: + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(rename = "migrate-clk", rename = "migrate-clk", default = true)] + migrate_clock: bool, + } + }, + "`rename` can only be used at most once" + ); + derive_compile_fail!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + struct DummyState { + #[property(default = true, default = true)] + migrate_clock: bool, + } + }, + "`default` can only be used at most once" + ); + // Check that the field name is preserved when `rename` isn't used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField, + #[property(default = true)] + migrate_clock: bool, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"migrate_clock"), + info: ::VALUE, + offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); + // Check that `rename` value is used for the property name when used: + derive_compile!( + derive_device_or_error, + quote! { + #[repr(C)] + #[derive(Device)] + pub struct DummyState { + parent: ParentField, + #[property(rename = "migrate-clk", default = true)] + migrate_clock: bool, + } + }, + quote! { + unsafe impl ::hwcore::DevicePropertiesImpl for DummyState { + const PROPERTIES: &'static [::hwcore::bindings::Property] = &[ + ::hwcore::bindings::Property { + name: ::std::ffi::CStr::as_ptr(c"migrate-clk"), + info: ::VALUE, + offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize, + set_default: true, + defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 }, + ..::common::Zeroable::ZERO + } + ]; + } + } + ); +} + +#[test] +fn test_derive_object() { + derive_compile_fail!( + derive_object_or_error, + quote! { + #[derive(Object)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(C)] required for #[derive(Object)]" + ); + derive_compile!( + derive_object_or_error, + quote! { + #[derive(Object)] + #[repr(C)] + struct Foo { + _unused: [u8; 0], + } + }, + quote! { + ::common::assert_field_type!( + Foo, + _unused, + ::qom::ParentField<::ParentType> + ); + ::util::module_init! { + MODULE_INIT_QOM => unsafe { + ::qom::type_register_static(&::TYPE_INFO); + } + } + } + ); +} + +#[test] +fn test_derive_tryinto() { + derive_compile_fail!( + derive_tryinto_or_error, + quote! { + #[derive(TryInto)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(u8/u16/u32/u64) required for #[derive(TryInto)]" + ); + derive_compile!( + derive_tryinto_or_error, + quote! { + #[derive(TryInto)] + #[repr(u8)] + enum Foo { + First = 0, + Second, + } + }, + quote! { + impl Foo { + #[allow(dead_code)] + pub const fn into_bits(self) -> u8 { + self as u8 + } + + #[allow(dead_code)] + pub const fn from_bits(value: u8) -> Self { + match ({ + const First: u8 = Foo::First as u8; + const Second: u8 = Foo::Second as u8; + match value { + First => core::result::Result::Ok(Foo::First), + Second => core::result::Result::Ok(Foo::Second), + _ => core::result::Result::Err(value), + } + }) { + Ok(x) => x, + Err(_) => panic!("invalid value for Foo"), + } + } + } + + impl core::convert::TryFrom for Foo { + type Error = u8; + + #[allow(ambiguous_associated_items)] + fn try_from(value: u8) -> Result { + const First: u8 = Foo::First as u8; + const Second: u8 = Foo::Second as u8; + match value { + First => core::result::Result::Ok(Foo::First), + Second => core::result::Result::Ok(Foo::Second), + _ => core::result::Result::Err(value), + } + } + } + } + ); +} diff --git a/rust/qom/Cargo.toml b/rust/qom/Cargo.toml index 46bbf7c7fe..060ad2ec34 100644 --- a/rust/qom/Cargo.toml +++ b/rust/qom/Cargo.toml @@ -16,7 +16,7 @@ rust-version.workspace = true common = { path = "../common" } bql = { path = "../bql" } migration = { path = "../migration" } -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } util = { path = "../util" } [lints] diff --git a/rust/qom/meson.build b/rust/qom/meson.build index 84a65cb737..40c51b71b2 100644 --- a/rust/qom/meson.build +++ b/rust/qom/meson.build @@ -28,10 +28,10 @@ _qom_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', link_with: [_bql_rs, _migration_rs], - dependencies: [common_rs, qemu_api_macros], + dependencies: [common_rs, qemu_macros], ) -qom_rs = declare_dependency(link_with: [_qom_rs], dependencies: [qemu_api_macros, qom]) +qom_rs = declare_dependency(link_with: [_qom_rs], dependencies: [qemu_macros, qom]) # Doctests are essentially integration tests, so they need the same dependencies. # Note that running them requires the object files for C code, so place them diff --git a/rust/qom/src/qom.rs b/rust/qom/src/qom.rs index 3ea1ad9c5b..2cd1d85011 100644 --- a/rust/qom/src/qom.rs +++ b/rust/qom/src/qom.rs @@ -112,7 +112,7 @@ pub use crate::bindings::{type_register_static, ObjectClass}; /// A safe wrapper around [`bindings::Object`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, qemu_macros::Wrapper)] pub struct Object(Opaque); unsafe impl Send for Object {} @@ -173,7 +173,7 @@ macro_rules! qom_isa { /// /// ```ignore /// #[repr(C)] -/// #[derive(qemu_api_macros::Object)] +/// #[derive(qemu_macros::Object)] /// pub struct MyDevice { /// parent: ParentField, /// ... diff --git a/rust/system/Cargo.toml b/rust/system/Cargo.toml index 6803895e08..d8338c8348 100644 --- a/rust/system/Cargo.toml +++ b/rust/system/Cargo.toml @@ -16,7 +16,7 @@ rust-version.workspace = true common = { path = "../common" } qom = { path = "../qom" } util = { path = "../util" } -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } [lints] workspace = true diff --git a/rust/system/meson.build b/rust/system/meson.build index ae9b932d29..9f88166f3d 100644 --- a/rust/system/meson.build +++ b/rust/system/meson.build @@ -35,8 +35,8 @@ _system_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', link_with: [_bql_rs, _migration_rs, _qom_rs, _util_rs], - dependencies: [common_rs, qemu_api_macros], + dependencies: [common_rs, qemu_macros], ) system_rs = declare_dependency(link_with: [_system_rs], - dependencies: [qemu_api_macros, hwcore]) + dependencies: [qemu_macros, hwcore]) diff --git a/rust/system/src/memory.rs b/rust/system/src/memory.rs index 29568ed767..7312f809f5 100644 --- a/rust/system/src/memory.rs +++ b/rust/system/src/memory.rs @@ -129,7 +129,7 @@ impl Default for MemoryRegionOpsBuilder { /// A safe wrapper around [`bindings::MemoryRegion`]. #[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] +#[derive(qemu_macros::Wrapper)] pub struct MemoryRegion(Opaque); unsafe impl Send for MemoryRegion {} diff --git a/rust/util/Cargo.toml b/rust/util/Cargo.toml index 637df61060..18e6619ca0 100644 --- a/rust/util/Cargo.toml +++ b/rust/util/Cargo.toml @@ -17,7 +17,7 @@ anyhow = { workspace = true } foreign = { workspace = true } libc = { workspace = true } common = { path = "../common" } -qemu_api_macros = { path = "../qemu-api-macros" } +qemu_macros = { path = "../qemu-macros" } [lints] workspace = true diff --git a/rust/util/meson.build b/rust/util/meson.build index 56e929349b..197872c9b2 100644 --- a/rust/util/meson.build +++ b/rust/util/meson.build @@ -39,7 +39,7 @@ _util_rs = static_library( ), override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', - dependencies: [anyhow_rs, libc_rs, foreign_rs, common_rs, qemu_api_macros, qom, qemuutil], + dependencies: [anyhow_rs, libc_rs, foreign_rs, common_rs, qemu_macros, qom, qemuutil], ) util_rs = declare_dependency(link_with: [_util_rs], dependencies: [qemuutil, qom]) diff --git a/rust/util/src/timer.rs b/rust/util/src/timer.rs index 383e1a6e77..622b6ee309 100644 --- a/rust/util/src/timer.rs +++ b/rust/util/src/timer.rs @@ -15,14 +15,14 @@ use crate::bindings::{ /// A safe wrapper around [`bindings::QEMUTimer`]. #[repr(transparent)] -#[derive(Debug, qemu_api_macros::Wrapper)] +#[derive(Debug, qemu_macros::Wrapper)] pub struct Timer(Opaque); unsafe impl Send for Timer {} unsafe impl Sync for Timer {} #[repr(transparent)] -#[derive(qemu_api_macros::Wrapper)] +#[derive(qemu_macros::Wrapper)] pub struct TimerListGroup(Opaque); unsafe impl Send for TimerListGroup {} -- cgit 1.4.1 From b0f6bf8a5b3b3a9dd4a11f792084d39a76b4b007 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 8 Sep 2025 12:50:01 +0200 Subject: rust: re-export qemu_macros internal helper in "bits" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid the need to import "qemu_macros". Signed-off-by: Marc-André Lureau Link: https://lore.kernel.org/r/20250827104147.717203-21-marcandre.lureau@redhat.com Reviewed-by: Zhao Liu Signed-off-by: Paolo Bonzini --- rust/bits/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'rust/bits/src') diff --git a/rust/bits/src/lib.rs b/rust/bits/src/lib.rs index 1bc882fde1..d1141f7c88 100644 --- a/rust/bits/src/lib.rs +++ b/rust/bits/src/lib.rs @@ -380,14 +380,17 @@ macro_rules! bits { }; { $type:ty: $expr:expr } => { - ::qemu_macros::bits_const_internal! { $type @ ($expr) } + $crate::bits_const_internal! { $type @ ($expr) } }; { $type:ty as $int_type:ty: $expr:expr } => { - (::qemu_macros::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type + ($crate::bits_const_internal! { $type @ ($expr) }.into_bits()) as $int_type }; } +#[doc(hidden)] +pub use qemu_macros::bits_const_internal; + #[cfg(test)] mod test { bits! { -- cgit 1.4.1