From 87e207a741b65b4a246973db8ffc64b5486b5b58 Mon Sep 17 00:00:00 2001 From: Manfred Tonch Date: Wed, 27 Nov 2024 15:42:32 +0100 Subject: [PATCH 1/5] Allow bitflags v1 and v2 to coexist --- defmt/Cargo.toml | 2 + defmt/src/export/mod.rs | 3 ++ defmt/src/lib.rs | 12 ++++- firmware/qemu/Cargo.toml | 2 +- firmware/qemu/src/bin/bitflagsv2.out | 12 +++++ firmware/qemu/src/bin/bitflagsv2.rs | 81 ++++++++++++++++++++++++++++ macros/src/items/bitflags.rs | 28 +++++++--- macros/src/lib.rs | 9 +++- 8 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 firmware/qemu/src/bin/bitflagsv2.out create mode 100644 firmware/qemu/src/bin/bitflagsv2.rs diff --git a/defmt/Cargo.toml b/defmt/Cargo.toml index d64a4a2e..1162c427 100644 --- a/defmt/Cargo.toml +++ b/defmt/Cargo.toml @@ -21,6 +21,7 @@ version = "0.3.10" alloc = [] avoid-default-panic = [] ip_in_core = [] +bitflagsv2 = ["dep:bitflagsv2"] # Encoding feature flags. These should only be set by end-user crates, not by library crates. # @@ -47,6 +48,7 @@ unstable-test = [ "defmt-macros/unstable-test" ] # defmt. Although, multiple versions of defmt might use the *same* defmt-macros. defmt-macros = { path = "../macros", version = "=0.4.0" } bitflags = "1" +bitflagsv2 = { package = "bitflags", version = "2", optional = true } [dev-dependencies] rustc_version = "0.4" diff --git a/defmt/src/export/mod.rs b/defmt/src/export/mod.rs index c0cd7be3..088268d6 100644 --- a/defmt/src/export/mod.rs +++ b/defmt/src/export/mod.rs @@ -8,6 +8,9 @@ use crate::{Format, Formatter, Str}; pub use self::integers::*; pub use bitflags::bitflags; +#[cfg(feature = "bitflagsv2")] +pub use bitflagsv2::bitflags as bitflagsv2; + pub trait UnsignedInt {} impl UnsignedInt for u8 {} impl UnsignedInt for u16 {} diff --git a/defmt/src/lib.rs b/defmt/src/lib.rs index abffdf8f..bde0e227 100644 --- a/defmt/src/lib.rs +++ b/defmt/src/lib.rs @@ -361,11 +361,21 @@ pub use defmt_macros::timestamp; /// const ABC = Self::A.bits | Self::B.bits | Self::C.bits; /// } /// } -/// +/// #[cfg(feature = "bitflagsv2")] +/// defmt::bitflagsv2! { +/// struct Flags: u32 { +/// const A = 0b00000001; +/// const B = 0b00000010; +/// const C = 0b00000100; +/// const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits() +/// } +/// } /// defmt::info!("Flags::ABC: {}", Flags::ABC); /// defmt::info!("Flags::empty(): {}", Flags::empty()); /// ``` pub use defmt_macros::bitflags; +#[cfg(feature = "bitflagsv2")] +pub use defmt_macros::bitflagsv2; #[doc(hidden)] // documented as the `Format` trait instead pub use defmt_macros::Format; diff --git a/firmware/qemu/Cargo.toml b/firmware/qemu/Cargo.toml index 6279fee3..79b05bbf 100644 --- a/firmware/qemu/Cargo.toml +++ b/firmware/qemu/Cargo.toml @@ -11,7 +11,7 @@ name = "defmt-test" harness = false [dependencies] -defmt = { path = "../../defmt" } +defmt = { path = "../../defmt", default-features = true, features = ["bitflagsv2"]} defmt-semihosting = { path = "../defmt-semihosting" } defmt-test = { path = "../defmt-test" } cortex-m = "0.7" diff --git a/firmware/qemu/src/bin/bitflagsv2.out b/firmware/qemu/src/bin/bitflagsv2.out new file mode 100644 index 00000000..1a4d6ea6 --- /dev/null +++ b/firmware/qemu/src/bin/bitflagsv2.out @@ -0,0 +1,12 @@ +INFO Flags::empty(): FLAG_0 +INFO Flags::empty(): Flags(0x0) (fmt::Debug) +INFO Flags::all(): FLAG_1 | FLAG_2 | FLAG_7 | FLAG_7_COPY +INFO Flags::all(): Flags(FLAG_1 | FLAG_2 | FLAG_7) (fmt::Debug) +INFO Flags::FLAG_1: FLAG_1 +INFO Flags::FLAG_1: Flags(FLAG_1) (fmt::Debug) +INFO Flags::FLAG_7: FLAG_7 | FLAG_7_COPY +INFO Flags::FLAG_7: Flags(FLAG_7) (fmt::Debug) +INFO LargeFlags::ALL: MSB | ALL | NON_LITERAL +INFO LargeFlags::ALL: LargeFlags(MSB | ALL) (fmt::Debug) +INFO LargeFlags::empty(): (empty) +INFO LargeFlags::empty(): LargeFlags(0x0) (fmt::Debug) diff --git a/firmware/qemu/src/bin/bitflagsv2.rs b/firmware/qemu/src/bin/bitflagsv2.rs new file mode 100644 index 00000000..3a71baf9 --- /dev/null +++ b/firmware/qemu/src/bin/bitflagsv2.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +#![allow(clippy::bad_bit_mask)] + +use cortex_m_rt::entry; +use cortex_m_semihosting::debug; +use defmt::{bitflagsv2, Debug2Format}; + +use defmt_semihosting as _; // global logger + +bitflagsv2! { + #[derive(Debug)] + struct Flags: u8 { + #[cfg(not(never))] + const FLAG_0 = 0b00; + const FLAG_1 = 0b01; + const FLAG_2 = 0b10; + const FLAG_7 = 1 << 7; + const FLAG_7_COPY = Self::FLAG_7.bits(); + #[cfg(never)] + const CFGD_OUT = 1; + } +} + +bitflagsv2! { + #[derive(Debug)] + struct LargeFlags: u128 { + const MSB = 1 << 127; + const ALL = !0; + const NON_LITERAL = compute_flag_value(0x346934553632462); + } +} + +const fn compute_flag_value(x: u128) -> u128 { + x ^ 0xdeadbeef +} + +#[entry] +fn main() -> ! { + defmt::info!("Flags::empty(): {}", Flags::empty()); + defmt::info!( + "Flags::empty(): {} (fmt::Debug)", + Debug2Format(&Flags::empty()) + ); + defmt::info!("Flags::all(): {}", Flags::all()); + defmt::info!("Flags::all(): {} (fmt::Debug)", Debug2Format(&Flags::all())); + defmt::info!("Flags::FLAG_1: {}", Flags::FLAG_1); + defmt::info!( + "Flags::FLAG_1: {} (fmt::Debug)", + Debug2Format(&Flags::FLAG_1) + ); + defmt::info!("Flags::FLAG_7: {}", Flags::FLAG_7); + defmt::info!( + "Flags::FLAG_7: {} (fmt::Debug)", + Debug2Format(&Flags::FLAG_7) + ); + + defmt::info!("LargeFlags::ALL: {}", LargeFlags::ALL); + defmt::info!( + "LargeFlags::ALL: {} (fmt::Debug)", + Debug2Format(&LargeFlags::ALL) + ); + defmt::info!("LargeFlags::empty(): {}", LargeFlags::empty()); + defmt::info!( + "LargeFlags::empty(): {} (fmt::Debug)", + Debug2Format(&LargeFlags::empty()) + ); + + loop { + debug::exit(debug::EXIT_SUCCESS) + } +} + +// like `panic-semihosting` but doesn't print to stdout (that would corrupt the defmt stream) +#[cfg(target_os = "none")] +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop { + debug::exit(debug::EXIT_FAILURE) + } +} diff --git a/macros/src/items/bitflags.rs b/macros/src/items/bitflags.rs index d3d71e23..3c906d41 100644 --- a/macros/src/items/bitflags.rs +++ b/macros/src/items/bitflags.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote, ToTokens}; use syn::parse_macro_input; use crate::{cargo, construct}; @@ -9,7 +9,7 @@ use self::input::Input; mod input; -pub(crate) fn expand(input: TokenStream) -> TokenStream { +pub(crate) fn expand(input: TokenStream, is_v2: bool) -> TokenStream { let bitflags_input = TokenStream2::from(input.clone()); let input = parse_macro_input!(input as Input); @@ -23,11 +23,13 @@ pub(crate) fn expand(input: TokenStream) -> TokenStream { construct::crate_local_disambiguator(), cargo::crate_name(), ); - let format_tag = construct::interned_string(&format_string, "bitflags", false, None); + let bitflags_tag = if is_v2 { "bitflagsv2" } else { "bitflags" }; + let format_tag = construct::interned_string(&format_string, bitflags_tag, false, None); let ident = input.ident(); let ty = input.ty(); - let flag_statics = codegen_flag_statics(&input); + let flag_statics = codegen_flag_statics(&input, is_v2); + let bitflag_macro = format_ident!("{bitflags_tag}"); quote!( const _: () = { fn assert() {} @@ -36,7 +38,7 @@ pub(crate) fn expand(input: TokenStream) -> TokenStream { #(#flag_statics)* }; - defmt::export::bitflags! { + defmt::export::#bitflag_macro! { #bitflags_input } @@ -59,7 +61,7 @@ pub(crate) fn expand(input: TokenStream) -> TokenStream { .into() } -fn codegen_flag_statics(input: &Input) -> Vec { +fn codegen_flag_statics(input: &Input, is_v2: bool) -> Vec { input .flags() .enumerate() @@ -69,11 +71,21 @@ fn codegen_flag_statics(input: &Input) -> Vec { let struct_name = input.ident(); let repr_ty = input.ty(); + let _tag = if is_v2 { + "bitflagsv2_value" + } else { + "bitflags_value" + }; let sym_name = construct::mangled_symbol_name( - "bitflags_value", + _tag, &format!("{}::{i}::{}", input.ident(), flag.ident()), ); + let bits_access = if is_v2 { + quote! { bits() } + } else { + quote! { bits} + }; quote! { #(#cfg_attrs)* #[cfg_attr(target_os = "macos", link_section = ".defmt,end")] @@ -84,7 +96,7 @@ fn codegen_flag_statics(input: &Input) -> Vec { // causes a value such as `1 << 127` to be evaluated as an `i32`, which // overflows. So we instead coerce (but don't cast) it to the bitflags' raw // type, and then cast that to u128. - let coerced_value: #repr_ty = #struct_name::#var_name.bits; + let coerced_value: #repr_ty = #struct_name::#var_name.#bits_access; coerced_value as u128 }; } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 19e6ca8a..ad2b6b89 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -199,7 +199,14 @@ pub fn write(args: TokenStream) -> TokenStream { #[proc_macro] #[proc_macro_error] pub fn bitflags(ts: TokenStream) -> TokenStream { - items::bitflags::expand(ts) + items::bitflags::expand(ts, false) +} + +/* # Items */ +#[proc_macro] +#[proc_macro_error] +pub fn bitflagsv2(ts: TokenStream) -> TokenStream { + items::bitflags::expand(ts, true) } #[proc_macro] From 2c89bc7288603d786b979e21361a801f0f80e891 Mon Sep 17 00:00:00 2001 From: Manfred Tonch Date: Fri, 29 Nov 2024 16:04:54 +0100 Subject: [PATCH 2/5] Fix change-log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 620a7e04..27c5f401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ We have several packages which live in this repository. Changes are tracked sepa [defmt-v0.1.0]: https://github.com/knurling-rs/defmt/releases/tag/defmt-v0.1.0 ### [defmt-next] +* [#894] Add optional support for bitflags v2 * [#938] Emit `option_env!("DEFMT_LOG")` so rustc depinfo can track it * [#935] Add note in book's setup chapter to clarify staticlib setup @@ -845,6 +846,7 @@ Initial release [#901]: https://github.com/knurling-rs/defmt/pull/901 [#899]: https://github.com/knurling-rs/defmt/pull/899 [#897]: https://github.com/knurling-rs/defmt/pull/897 +[#894]: https://github.com/knurling-rs/defmt/pull/894 [#889]: https://github.com/knurling-rs/defmt/pull/889 [#887]: https://github.com/knurling-rs/defmt/pull/887 [#884]: https://github.com/knurling-rs/defmt/pull/884 From cc505634751f21c996fe0a27e0b79710ec555b9c Mon Sep 17 00:00:00 2001 From: Manfred Tonch Date: Mon, 2 Dec 2024 14:24:59 +0100 Subject: [PATCH 3/5] Fix format --- CHANGELOG.md | 1 + macros/src/items/bitflags.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c5f401..2ef49ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ We have several packages which live in this repository. Changes are tracked sepa [defmt-v0.1.0]: https://github.com/knurling-rs/defmt/releases/tag/defmt-v0.1.0 ### [defmt-next] + * [#894] Add optional support for bitflags v2 * [#938] Emit `option_env!("DEFMT_LOG")` so rustc depinfo can track it diff --git a/macros/src/items/bitflags.rs b/macros/src/items/bitflags.rs index 3c906d41..3cb2cff2 100644 --- a/macros/src/items/bitflags.rs +++ b/macros/src/items/bitflags.rs @@ -84,7 +84,7 @@ fn codegen_flag_statics(input: &Input, is_v2: bool) -> Vec { let bits_access = if is_v2 { quote! { bits() } } else { - quote! { bits} + quote! { bits } }; quote! { #(#cfg_attrs)* From bb4baed0b2ce78f74e4a03fca1b8f8d116cd93b6 Mon Sep 17 00:00:00 2001 From: Manfred Tonch Date: Wed, 15 Jan 2025 10:49:09 +0100 Subject: [PATCH 4/5] Adds documentation for bitflagsv2 --- defmt/src/lib.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/defmt/src/lib.rs b/defmt/src/lib.rs index bde0e227..dde9c82e 100644 --- a/defmt/src/lib.rs +++ b/defmt/src/lib.rs @@ -337,7 +337,7 @@ pub use defmt_macros::timestamp; /// Generates a bitflags structure that can be formatted with defmt. /// -/// This macro is a wrapper around the [`bitflags!`] crate, and provides an (almost) identical +/// This macro is a wrapper around the [`bitflags!`] macro of the bitflags version 1 crate, and provides an (almost) identical /// interface. Refer to [its documentation] for an explanation of the syntax. /// /// [its documentation]: https://docs.rs/bitflags/1/bitflags/ @@ -361,6 +361,35 @@ pub use defmt_macros::timestamp; /// const ABC = Self::A.bits | Self::B.bits | Self::C.bits; /// } /// } +/// +/// defmt::info!("Flags::ABC: {}", Flags::ABC); +/// defmt::info!("Flags::empty(): {}", Flags::empty()); +/// ``` +pub use defmt_macros::bitflags; + +/// Generates a bitflags structure that can be formatted with defmt (using version 2 of the bitflags crate) +/// + +/// Users of the defmt crate can enable the bitflagsv2 feature by adding defmt = { features = ["bitflagsv2"] } +/// to their Cargo.toml. Bitflags version 2 introduces significant improvements over version 1, including a safer +/// API with the replacement of the unsafe from_bits_unchecked method by the safe from_bits_retain method +/// and enhanced serialization support via an optional serde feature. +/// This macro is a wrapper around the [`bitflags!`][bitflagsv2] macro of the bitflags version 2 crate, and provides an (almost) identical +/// interface. Refer to [its documentation][bitflagsv2] for an explanation of the syntax. +/// +/// [bitflagsv2]: https://docs.rs/bitflags/2/bitflags/macro.bitflags.html +/// +/// # Limitations +/// +/// This macro only supports bitflags structs represented as one of Rust's built-in unsigned integer +/// types (`u8`, `u16`, `u32`, `u64`, or `u128`). Custom types are not supported. This restriction +/// is necessary to support defmt's efficient encoding. +/// +/// # Examples +/// +/// The example from the bitflagsv2 crate works as-is: +/// +/// ``` /// #[cfg(feature = "bitflagsv2")] /// defmt::bitflagsv2! { /// struct Flags: u32 { @@ -373,7 +402,6 @@ pub use defmt_macros::timestamp; /// defmt::info!("Flags::ABC: {}", Flags::ABC); /// defmt::info!("Flags::empty(): {}", Flags::empty()); /// ``` -pub use defmt_macros::bitflags; #[cfg(feature = "bitflagsv2")] pub use defmt_macros::bitflagsv2; From c4e940a21bc0dbe4e4ab668cd5bdd916af20b8ef Mon Sep 17 00:00:00 2001 From: Manfred Tonch Date: Thu, 30 Jan 2025 17:33:33 +0100 Subject: [PATCH 5/5] Fix documentation formatting --- defmt/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defmt/src/lib.rs b/defmt/src/lib.rs index dde9c82e..0d30dbc1 100644 --- a/defmt/src/lib.rs +++ b/defmt/src/lib.rs @@ -370,7 +370,7 @@ pub use defmt_macros::bitflags; /// Generates a bitflags structure that can be formatted with defmt (using version 2 of the bitflags crate) /// -/// Users of the defmt crate can enable the bitflagsv2 feature by adding defmt = { features = ["bitflagsv2"] } +/// Users of the defmt crate can enable the bitflagsv2 feature by adding `defmt = {version = "1", features = ["bitflagsv2"] }` /// to their Cargo.toml. Bitflags version 2 introduces significant improvements over version 1, including a safer /// API with the replacement of the unsafe from_bits_unchecked method by the safe from_bits_retain method /// and enhanced serialization support via an optional serde feature.