diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index bde00a48..b27917a0 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -14,6 +14,7 @@ - [Filtering](./filtering.md) - [#[timestamp]](./timestamp.md) - [#[global_logger]](./global-logger.md) + - [panic! and assert!](./panic.md) - [Printers](./printers.md) - [Migrating from git defmt to stable defmt](./migration.md) - [Design & impl details](./design.md) diff --git a/book/src/panic.md b/book/src/panic.md new file mode 100644 index 00000000..b7870fe0 --- /dev/null +++ b/book/src/panic.md @@ -0,0 +1,38 @@ +# `panic!` and `assert!` + +The `defmt` crate provides its own version of `panic!`-like and `assert!`-like macros. +The `defmt` version of these macros will log the panic message using `defmt` and then call `core::panic!` (by default). +Because the panic message is formatted using `defmt!` the format string must use the same syntax as the logging macros (e.g. `info!`). + +## `#[defmt::panic_handler]` + +Because `defmt::panic!` invokes `core::panic!` this can result in the panic message being printed twice if your `#[core::panic_handler]` also prints the panic message. +This is the case if you use [`panic-probe`] with the `print-defmt` feature enabled but not an issue if you are using the [`panic-abort`] crate, for example. + +[`panic-probe`]: https://crates.io/crates/panic-probe +[`panic-abort`]: https://crates.io/crates/panic-abort + +To avoid this issue you can use the `#[defmt::panic_handler]` to *override* the panicking behavior of `defmt::panic`-like and `defmt::assert`-like macros. +This attribute must be placed on a function with signature `fn() -> !`. +In this function you'll want to replicate the panicking behavior of the Rust `#[panic_handler]` but leave out the part that prints the panic message. +For example: + + + +``` rust, ignore +#[panic_handler] // built-in ("core") attribute +fn core_panic(info: &core::panic::PanicInfo) -> ! { + print(info); // e.g. using RTT + reset() +} + +#[defmt::panic_handler] // defmt's attribute +fn defmt_panic() -> ! { + // leave out the printing part here + reset() +} +``` + +If you are using the `panic-probe` crate then you should "abort" (call `cortex_m::asm::udf`) from `#[defmt::panic_handler]` to match its behavior. + +NOTE: even if you don't run into the "double panic message printed" issue you may still want to use `#[defmt::panic_handler]` because this way `defmt::panic` and `defmt::assert` will *not* go through the `core::panic` machinery and that *may* reduce code size (we recommend you measure the effect of the change). diff --git a/defmt.x.in b/defmt.x.in index 8fdcc3d3..d34c9f22 100644 --- a/defmt.x.in +++ b/defmt.x.in @@ -3,6 +3,7 @@ EXTERN(_defmt_acquire); EXTERN(_defmt_release); EXTERN(__defmt_default_timestamp); PROVIDE(_defmt_timestamp = __defmt_default_timestamp); +PROVIDE(_defmt_panic = __defmt_default_panic); SECTIONS { diff --git a/firmware/qemu/Cargo.toml b/firmware/qemu/Cargo.toml index db3c8c6e..bab2bb25 100644 --- a/firmware/qemu/Cargo.toml +++ b/firmware/qemu/Cargo.toml @@ -24,6 +24,22 @@ defmt-error = [] # log at ERROR level default = ["defmt-default"] alloc = ["defmt/alloc", "alloc-cortex-m"] +[[bin]] +name = "assert" +test = false + +[[bin]] +name = "assert-eq" +test = false + +[[bin]] +name = "assert-ne" +test = false + +[[bin]] +name = "panic" +test = false + [[bin]] name = "log" test = false diff --git a/firmware/qemu/src/bin/assert-eq.out b/firmware/qemu/src/bin/assert-eq.out new file mode 100644 index 00000000..fe17c3f3 --- /dev/null +++ b/firmware/qemu/src/bin/assert-eq.out @@ -0,0 +1,3 @@ +0.000000 ERROR panicked at 'assertion failed: `(left == right)`: dev' + left: `41` +right: `43` diff --git a/firmware/qemu/src/bin/assert-eq.release.out b/firmware/qemu/src/bin/assert-eq.release.out new file mode 100644 index 00000000..06e706db --- /dev/null +++ b/firmware/qemu/src/bin/assert-eq.release.out @@ -0,0 +1,3 @@ +0.000000 ERROR panicked at 'assertion failed: `(left == right)`: release' + left: `41` +right: `43` diff --git a/firmware/qemu/src/bin/assert-eq.rs b/firmware/qemu/src/bin/assert-eq.rs new file mode 100644 index 00000000..0aa88bae --- /dev/null +++ b/firmware/qemu/src/bin/assert-eq.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; + +use defmt_semihosting as _; // global logger + +#[entry] +fn main() -> ! { + let x = 42; + defmt::debug_assert_eq!(x - 1, x + 1, "dev"); + defmt::assert_eq!(x - 1, x + 1, "release"); + defmt::unreachable!(); +} + +// 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) -> ! { + use cortex_m_semihosting::debug; + + loop { + debug::exit(debug::EXIT_SUCCESS) + } +} diff --git a/firmware/qemu/src/bin/assert-ne.out b/firmware/qemu/src/bin/assert-ne.out new file mode 100644 index 00000000..34238191 --- /dev/null +++ b/firmware/qemu/src/bin/assert-ne.out @@ -0,0 +1,2 @@ +0.000000 ERROR panicked at 'assertion failed: `(left != right)`: dev' +left/right: `42` diff --git a/firmware/qemu/src/bin/assert-ne.release.out b/firmware/qemu/src/bin/assert-ne.release.out new file mode 100644 index 00000000..7afb9a46 --- /dev/null +++ b/firmware/qemu/src/bin/assert-ne.release.out @@ -0,0 +1,2 @@ +0.000000 ERROR panicked at 'assertion failed: `(left != right)`: release' +left/right: `42` diff --git a/firmware/qemu/src/bin/assert-ne.rs b/firmware/qemu/src/bin/assert-ne.rs new file mode 100644 index 00000000..82cb4423 --- /dev/null +++ b/firmware/qemu/src/bin/assert-ne.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; + +use defmt_semihosting as _; // global logger + +#[entry] +fn main() -> ! { + let x = 42; + defmt::debug_assert_ne!(x, x, "dev"); + defmt::assert_ne!(x, x, "release"); + defmt::unreachable!(); +} + +// 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) -> ! { + use cortex_m_semihosting::debug; + + loop { + debug::exit(debug::EXIT_SUCCESS) + } +} diff --git a/firmware/qemu/src/bin/assert.out b/firmware/qemu/src/bin/assert.out new file mode 100644 index 00000000..c725b0cd --- /dev/null +++ b/firmware/qemu/src/bin/assert.out @@ -0,0 +1 @@ +0.000000 ERROR panicked at 'assertion failed: dev' diff --git a/firmware/qemu/src/bin/assert.release.out b/firmware/qemu/src/bin/assert.release.out new file mode 100644 index 00000000..9cee8e99 --- /dev/null +++ b/firmware/qemu/src/bin/assert.release.out @@ -0,0 +1 @@ +0.000000 ERROR panicked at 'assertion failed: release' diff --git a/firmware/qemu/src/bin/assert.rs b/firmware/qemu/src/bin/assert.rs new file mode 100644 index 00000000..8664f1ee --- /dev/null +++ b/firmware/qemu/src/bin/assert.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; + +use defmt_semihosting as _; // global logger + +#[entry] +fn main() -> ! { + let dev = false; + let release = false; + defmt::debug_assert!(dev); + defmt::assert!(release); + defmt::unreachable!(); +} + +// 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) -> ! { + use cortex_m_semihosting::debug; + + loop { + debug::exit(debug::EXIT_SUCCESS) + } +} diff --git a/firmware/qemu/src/bin/panic.out b/firmware/qemu/src/bin/panic.out new file mode 100644 index 00000000..c67a5e08 --- /dev/null +++ b/firmware/qemu/src/bin/panic.out @@ -0,0 +1 @@ +0.000000 ERROR panicked at 'The answer is 42' diff --git a/firmware/qemu/src/bin/panic.release.out b/firmware/qemu/src/bin/panic.release.out new file mode 100644 index 00000000..c67a5e08 --- /dev/null +++ b/firmware/qemu/src/bin/panic.release.out @@ -0,0 +1 @@ +0.000000 ERROR panicked at 'The answer is 42' diff --git a/firmware/qemu/src/bin/panic.rs b/firmware/qemu/src/bin/panic.rs new file mode 100644 index 00000000..9e83a01d --- /dev/null +++ b/firmware/qemu/src/bin/panic.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; + +use defmt_semihosting as _; // global logger + +#[entry] +fn main() -> ! { + defmt::panic!("The answer is {:?}", 42); +} + +// 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) -> ! { + use cortex_m_semihosting::debug; + + loop { + debug::exit(debug::EXIT_SUCCESS) + } +} diff --git a/firmware/qemu/test.sh b/firmware/qemu/test.sh index 3638e962..aed3c47b 100755 --- a/firmware/qemu/test.sh +++ b/firmware/qemu/test.sh @@ -5,12 +5,16 @@ set -o errexit function test() { local bin=$1 local features=${2-,} - + cargo rb "$bin" --features "$features" | tee "src/bin/$bin.out.new" | diff "src/bin/$bin.out" - cargo rrb "$bin" --features "$features" | tee "src/bin/$bin.release.out.new" | diff "src/bin/$bin.release.out" - } test "log" +test "panic" +test "assert" +test "assert-eq" +test "assert-ne" if rustc -V | grep nightly; then test "alloc" "alloc" fi diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5cc6bc25..275f3c8c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -7,15 +7,14 @@ use proc_macro::TokenStream; use defmt_parser::{Fragment, Level}; use proc_macro2::{Ident as Ident2, Span as Span2, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use syn::GenericParam; -use syn::WhereClause; use syn::{ parse::{self, Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, spanned::Spanned as _, - Data, DeriveInput, Expr, Fields, FieldsNamed, FieldsUnnamed, ItemFn, ItemStruct, LitStr, - ReturnType, Token, Type, WherePredicate, + Data, DeriveInput, Expr, ExprPath, Fields, FieldsNamed, FieldsUnnamed, GenericParam, ItemFn, + ItemStruct, LitStr, Path, PathArguments, PathSegment, ReturnType, Token, Type, WhereClause, + WherePredicate, }; #[proc_macro_attribute] @@ -60,6 +59,52 @@ pub fn global_logger(args: TokenStream, input: TokenStream) -> TokenStream { .into() } +#[proc_macro_attribute] +pub fn panic_handler(args: TokenStream, input: TokenStream) -> TokenStream { + if !args.is_empty() { + return parse::Error::new( + Span2::call_site(), + "`#[defmt::panic_handler]` attribute takes no arguments", + ) + .to_compile_error() + .into(); + } + let f = parse_macro_input!(input as ItemFn); + + let rety_is_ok = match &f.sig.output { + ReturnType::Default => false, + ReturnType::Type(_, ty) => match &**ty { + Type::Never(_) => true, + _ => false, + }, + }; + + let ident = &f.sig.ident; + if f.sig.constness.is_some() + || f.sig.asyncness.is_some() + || f.sig.unsafety.is_some() + || f.sig.abi.is_some() + || !f.sig.generics.params.is_empty() + || f.sig.generics.where_clause.is_some() + || f.sig.variadic.is_some() + || !f.sig.inputs.is_empty() + || !rety_is_ok + { + return parse::Error::new(ident.span(), "function must have signature `fn() -> !`") + .to_compile_error() + .into(); + } + + let block = &f.block; + quote!( + #[export_name = "_defmt_panic"] + fn #ident() -> ! { + #block + } + ) + .into() +} + #[proc_macro_attribute] pub fn timestamp(args: TokenStream, input: TokenStream) -> TokenStream { if !args.is_empty() { @@ -393,8 +438,11 @@ fn is_logging_enabled(level: Level) -> TokenStream2 { // note that we are not using a `Level` type shared with `decoder` due to Cargo bugs in crate sharing // TODO -> move Level to parser? -fn log(level: Level, ts: TokenStream) -> TokenStream { - let log = parse_macro_input!(ts as Log); +fn log_ts(level: Level, ts: TokenStream) -> TokenStream { + log(level, parse_macro_input!(ts as FormatArgs)).into() +} + +fn log(level: Level, log: FormatArgs) -> TokenStream2 { let ls = log.litstr.value(); let fragments = match defmt_parser::parse(&ls) { Ok(args) => args, @@ -431,32 +479,275 @@ fn log(level: Level, ts: TokenStream) -> TokenStream { } } }) - .into() } #[proc_macro] pub fn trace(ts: TokenStream) -> TokenStream { - log(Level::Trace, ts) + log_ts(Level::Trace, ts) } #[proc_macro] pub fn debug(ts: TokenStream) -> TokenStream { - log(Level::Debug, ts) + log_ts(Level::Debug, ts) } #[proc_macro] pub fn info(ts: TokenStream) -> TokenStream { - log(Level::Info, ts) + log_ts(Level::Info, ts) } #[proc_macro] pub fn warn(ts: TokenStream) -> TokenStream { - log(Level::Warn, ts) + log_ts(Level::Warn, ts) } #[proc_macro] pub fn error(ts: TokenStream) -> TokenStream { - log(Level::Error, ts) + log_ts(Level::Error, ts) +} + +fn panic( + ts: TokenStream, + zero_args_string: &str, + string_transform: impl FnOnce(&str) -> String, +) -> TokenStream { + let log_stmt = if ts.is_empty() { + // panic!() -> error!("panicked at 'explicit panic'") + log( + Level::Error, + FormatArgs { + litstr: LitStr::new(zero_args_string, Span2::call_site()), + rest: None, + }, + ) + } else { + // panic!("a", b, c) -> error!("panicked at 'a'", b, c) + let args = parse_macro_input!(ts as FormatArgs); + log( + Level::Error, + FormatArgs { + litstr: LitStr::new(&string_transform(&args.litstr.value()), Span2::call_site()), + rest: args.rest, + }, + ) + }; + + quote!( + #log_stmt; + defmt::export::panic() + ) + .into() +} + +// not naming this `panic` to avoid shadowing `core::panic` in this scope +#[proc_macro] +pub fn panic_(ts: TokenStream) -> TokenStream { + panic(ts, "panicked at 'explicit panic'", |format_string| { + format!("panicked at '{}'", format_string) + }) +} + +// not naming this `todo` to avoid shadowing `core::todo` in this scope +#[proc_macro] +pub fn todo_(ts: TokenStream) -> TokenStream { + panic(ts, "panicked at 'not yet implemented'", |format_string| { + format!("panicked at 'not yet implemented: {}'", format_string) + }) +} + +// not naming this `unreachable` to avoid shadowing `core::unreachable` in this scope +#[proc_macro] +pub fn unreachable_(ts: TokenStream) -> TokenStream { + panic( + ts, + "panicked at 'internal error: entered unreachable code'", + |format_string| { + format!( + "panicked at 'internal error: entered unreachable code: {}'", + format_string + ) + }, + ) +} + +// not naming this `assert` to avoid shadowing `core::assert` in this scope +#[proc_macro] +pub fn assert_(ts: TokenStream) -> TokenStream { + let assert = parse_macro_input!(ts as Assert); + + let condition = assert.condition; + let log_stmt = if let Some(args) = assert.args { + log( + Level::Error, + FormatArgs { + litstr: LitStr::new( + &format!("panicked at '{}'", args.litstr.value()), + Span2::call_site(), + ), + rest: args.rest, + }, + ) + } else { + log( + Level::Error, + FormatArgs { + litstr: LitStr::new( + &format!("panicked at 'assertion failed: {}'", quote!(#condition)), + Span2::call_site(), + ), + rest: None, + }, + ) + }; + + quote!( + if !(#condition) { + #log_stmt; + defmt::export::panic() + } + ) + .into() +} + +#[derive(PartialEq)] +enum BinOp { + Eq, + Ne, +} + +// not naming this `assert_eq` to avoid shadowing `core::assert_eq` in this scope +#[proc_macro] +pub fn assert_eq_(ts: TokenStream) -> TokenStream { + assert_binop(ts, BinOp::Eq) +} + +// not naming this `assert_ne` to avoid shadowing `core::assert_ne` in this scope +#[proc_macro] +pub fn assert_ne_(ts: TokenStream) -> TokenStream { + assert_binop(ts, BinOp::Ne) +} + +// not naming this `assert_eq` to avoid shadowing `core::assert_eq` in this scope +fn assert_binop(ts: TokenStream, binop: BinOp) -> TokenStream { + let assert = parse_macro_input!(ts as AssertEq); + + let left = assert.left; + let right = assert.right; + + let mut log_args = Punctuated::new(); + + let extra_string = if let Some(args) = assert.args { + if let Some(rest) = args.rest { + log_args.extend(rest.1); + } + format!(": {}", args.litstr.value()) + } else { + String::new() + }; + + let vals = match binop { + BinOp::Eq => &["left_val", "right_val"][..], + BinOp::Ne => &["left_val"][..], + }; + + for val in vals { + let mut segments = Punctuated::new(); + segments.push(PathSegment { + ident: Ident2::new(*val, Span2::call_site()), + arguments: PathArguments::None, + }); + + log_args.push(Expr::Path(ExprPath { + attrs: vec![], + qself: None, + path: Path { + leading_colon: None, + segments, + }, + })); + } + + let log_stmt = match binop { + BinOp::Eq => log( + Level::Error, + FormatArgs { + litstr: LitStr::new( + &format!( + "panicked at 'assertion failed: `(left == right)`{}' + left: `{{:?}}` +right: `{{:?}}`", + extra_string + ), + Span2::call_site(), + ), + rest: Some((syn::token::Comma::default(), log_args)), + }, + ), + BinOp::Ne => log( + Level::Error, + FormatArgs { + litstr: LitStr::new( + &format!( + "panicked at 'assertion failed: `(left != right)`{}' +left/right: `{{:?}}`", + extra_string + ), + Span2::call_site(), + ), + rest: Some((syn::token::Comma::default(), log_args)), + }, + ), + }; + + let mut cond = quote!(*left_val == *right_val); + if binop == BinOp::Eq { + cond = quote!(!(#cond)); + } + + quote!( + // evaluate arguments first + match (&(#left), &(#right)) { + (left_val, right_val) => { + // following `core::assert_eq!` + if #cond { + #log_stmt; + defmt::export::panic() + } + } + } + ) + .into() +} + +// NOTE these `debug_*` macros can be written using `macro_rules!` (that'd be simpler) but that +// results in an incorrect source code location being reported: the location of the `macro_rules!` +// statement is reported. Using a proc-macro results in the call site being reported, which is what +// we want +#[proc_macro] +pub fn debug_assert_(ts: TokenStream) -> TokenStream { + let assert = TokenStream2::from(assert_(ts)); + quote!(if cfg!(debug_assertions) { + #assert + }) + .into() +} + +#[proc_macro] +pub fn debug_assert_eq_(ts: TokenStream) -> TokenStream { + let assert = TokenStream2::from(assert_eq_(ts)); + quote!(if cfg!(debug_assertions) { + #assert + }) + .into() +} + +#[proc_macro] +pub fn debug_assert_ne_(ts: TokenStream) -> TokenStream { + let assert = TokenStream2::from(assert_ne_(ts)); + quote!(if cfg!(debug_assertions) { + #assert + }) + .into() } // TODO share more code with `log` @@ -497,12 +788,87 @@ pub fn winfo(ts: TokenStream) -> TokenStream { .into() } -struct Log { +struct Assert { + condition: Expr, + args: Option, +} + +impl Parse for Assert { + fn parse(input: ParseStream) -> parse::Result { + let condition = input.parse()?; + if input.is_empty() { + // assert!(a) + return Ok(Assert { + condition, + args: None, + }); + } + + let _comma: Token![,] = input.parse()?; + + if input.is_empty() { + // assert!(a,) + Ok(Assert { + condition, + args: None, + }) + } else { + // assert!(a, "b", c) + Ok(Assert { + condition, + args: Some(input.parse()?), + }) + } + } +} + +struct AssertEq { + left: Expr, + right: Expr, + args: Option, +} + +impl Parse for AssertEq { + fn parse(input: ParseStream) -> parse::Result { + let left = input.parse()?; + let _comma: Token![,] = input.parse()?; + let right = input.parse()?; + + if input.is_empty() { + // assert_eq!(a, b) + return Ok(AssertEq { + left, + right, + args: None, + }); + } + + let _comma: Token![,] = input.parse()?; + + if input.is_empty() { + // assert_eq!(a, b,) + Ok(AssertEq { + left, + right, + args: None, + }) + } else { + // assert_eq!(a, b, "c", d) + Ok(AssertEq { + left, + right, + args: Some(input.parse()?), + }) + } + } +} + +struct FormatArgs { litstr: LitStr, rest: Option<(Token![,], Punctuated)>, } -impl Parse for Log { +impl Parse for FormatArgs { fn parse(input: ParseStream) -> parse::Result { Ok(Self { litstr: input.parse()?, diff --git a/src/export.rs b/src/export.rs index 71bf4530..dc33352f 100644 --- a/src/export.rs +++ b/src/export.rs @@ -54,12 +54,12 @@ pub fn release(fmt: Formatter) { unsafe { _defmt_release(fmt) } } +/// For testing purposes #[cfg(target_arch = "x86_64")] pub fn timestamp() -> u64 { (T.with(|i| i.fetch_add(1, core::sync::atomic::Ordering::Relaxed)) & 0x7f) as u64 } -/// For testing purposes #[cfg(not(target_arch = "x86_64"))] pub fn timestamp() -> u64 { extern "Rust" { @@ -148,3 +148,17 @@ mod sealed { pub fn truncate(x: impl sealed::Truncate) -> T { x.truncate() } + +/// For testing purposes +#[cfg(target_arch = "x86_64")] +pub fn panic() -> ! { + panic!() +} + +#[cfg(not(target_arch = "x86_64"))] +pub fn panic() -> ! { + extern "Rust" { + fn _defmt_panic() -> !; + } + unsafe { _defmt_panic() } +} diff --git a/src/lib.rs b/src/lib.rs index d7f35d24..16176fcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,8 +16,7 @@ #[cfg(feature = "alloc")] extern crate alloc; -use core::mem::MaybeUninit; -use core::ptr::NonNull; +use core::{mem::MaybeUninit, ptr::NonNull}; #[doc(hidden)] pub mod export; @@ -26,6 +25,105 @@ mod leb; #[cfg(test)] mod tests; +/// Just like the [`core::assert!`] macro but `defmt` is used to log the panic message +/// +/// [`core::assert!`]: https://doc.rust-lang.org/core/macro.assert.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::assert_ as assert; + +/// Just like the [`core::assert_eq!`] macro but `defmt` is used to log the panic message +/// +/// [`core::assert_eq!`]: https://doc.rust-lang.org/core/macro.assert_eq.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::assert_eq_ as assert_eq; + +/// Just like the [`core::assert_ne!`] macro but `defmt` is used to log the panic message +/// +/// [`core::assert_ne!`]: https://doc.rust-lang.org/core/macro.assert_ne.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::assert_ne_ as assert_ne; + +/// Just like the [`core::debug_assert!`] macro but `defmt` is used to log the panic message +/// +/// [`core::debug_assert!`]: https://doc.rust-lang.org/core/macro.debug_assert.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::debug_assert_ as debug_assert; + +/// Just like the [`core::debug_assert_eq!`] macro but `defmt` is used to log the panic message +/// +/// [`core::debug_assert_eq!`]: https://doc.rust-lang.org/core/macro.debug_assert_eq.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::debug_assert_eq_ as debug_assert_eq; + +/// Just like the [`core::debug_assert_ne!`] macro but `defmt` is used to log the panic message +/// +/// [`core::debug_assert_ne!`]: https://doc.rust-lang.org/core/macro.debug_assert_ne.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::debug_assert_ne_ as debug_assert_ne; + +/// Just like the [`core::unreachable!`] macro but `defmt` is used to log the panic message +/// +/// [`core::unreachable!`]: https://doc.rust-lang.org/core/macro.unreachable.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::unreachable_ as unreachable; + +/// Just like the [`core::todo!`] macro but `defmt` is used to log the panic message +/// +/// [`core::todo!`]: https://doc.rust-lang.org/core/macro.todo.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::todo_ as todo; + +/// Just like the [`core::unimplemented!`] macro but `defmt` is used to log the panic message +/// +/// [`core::unimplemented!`]: https://doc.rust-lang.org/core/macro.unimplemented.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::todo_ as unimplemented; + +/// Just like the [`core::panic!`] macro but `defmt` is used to log the panic message +/// +/// [`core::panic!`]: https://doc.rust-lang.org/core/macro.panic.html +/// +/// If used, the format string must follow the defmt syntax (documented in [the manual]) +/// +/// [the manual]: https://defmt.ferrous-systems.com/macros.html +pub use defmt_macros::panic_ as panic; + +/// Overrides the panicking behavior of `defmt::panic!` +/// +/// By default, `defmt::panic!` calls `core::panic!` after logging the panic message using `defmt`. +/// This can result in the panic message being printed twice in some cases. To avoid that issue use +/// this macro. See [the manual] for details. +/// +/// [the manual]: https://defmt.ferrous-systems.com/panic.html +pub use defmt_macros::panic_handler; + /// Creates an interned string ([`Str`]) from a string literal. /// /// This must be called on a string literal, and will allocate the literal in the object file. At @@ -209,12 +307,6 @@ impl Formatter { unsafe { self.writer.as_mut().write(bytes) } } - /// Implementation detail - #[cfg(target_arch = "x86_64")] - pub unsafe fn from_raw(_: NonNull) -> Self { - unreachable!() - } - /// Implementation detail #[cfg(not(target_arch = "x86_64"))] pub unsafe fn from_raw(writer: NonNull) -> Self { @@ -226,12 +318,6 @@ impl Formatter { } } - /// Implementation detail - #[cfg(target_arch = "x86_64")] - pub unsafe fn into_raw(self) -> NonNull { - unreachable!() - } - /// Implementation detail #[cfg(not(target_arch = "x86_64"))] pub unsafe fn into_raw(self) -> NonNull { @@ -426,6 +512,29 @@ impl Formatter { } } +// these need to be in a separate module or `unreachable!` will end up calling `defmt::panic` and +// this will not compile +// (using `core::unreachable!` instead of `unreachable!` doesn't help) +#[cfg(target_arch = "x86_64")] +mod x86_64 { + use core::ptr::NonNull; + + use super::Write; + + #[doc(hidden)] + impl super::Formatter { + /// Implementation detail + pub unsafe fn from_raw(_: NonNull) -> Self { + unreachable!() + } + + /// Implementation detail + pub unsafe fn into_raw(self) -> NonNull { + unreachable!() + } + } +} + /// Trait for defmt logging targets. pub trait Write { /// Writes `bytes` to the destination. @@ -467,3 +576,8 @@ pub trait Format { fn default_timestamp() -> u64 { 0 } + +#[export_name = "__defmt_default_panic"] +fn default_panic() -> ! { + core::panic!() +}