1 | use std::{ |
2 | collections::hash_map::DefaultHasher, |
3 | hash::{Hash as _, Hasher as _}, |
4 | }; |
5 | |
6 | use proc_macro::Span; |
7 | use proc_macro2::{Ident as Ident2, Span as Span2, TokenStream as TokenStream2}; |
8 | use quote::{format_ident, quote}; |
9 | use syn::{parse_quote, Expr, Ident, LitStr}; |
10 | |
11 | pub(crate) use symbol::mangled as mangled_symbol_name; |
12 | |
13 | mod symbol; |
14 | |
15 | pub(crate) fn crate_local_disambiguator() -> u64 { |
16 | // We want a deterministic, but unique-per-macro-invocation identifier. For that we |
17 | // hash the call site `Span`'s debug representation, which contains a counter that |
18 | // should disambiguate macro invocations within a crate. |
19 | hash(&format!(" {:?}" , Span::call_site())) |
20 | } |
21 | |
22 | pub(crate) fn escaped_expr_string(expr: &Expr) -> String { |
23 | quote!(#expr) |
24 | .to_string() |
25 | .replace('{' , "{{" ) |
26 | .replace(from:'}' , to:"}}" ) |
27 | } |
28 | |
29 | pub(crate) fn interned_string( |
30 | string: &str, |
31 | tag: &str, |
32 | is_log_statement: bool, |
33 | prefix: Option<&str>, |
34 | defmt_path: &syn::Path, |
35 | ) -> TokenStream2 { |
36 | // NOTE we rely on this variable name when extracting file location information from the DWARF |
37 | // without it we have no other mean to differentiate static variables produced by `info!` vs |
38 | // produced by `intern!` (or `internp`) |
39 | let var_name: Ident = if is_log_statement { |
40 | format_ident!("DEFMT_LOG_STATEMENT" ) |
41 | } else { |
42 | format_ident!("S" ) |
43 | }; |
44 | |
45 | let var_addr: TokenStream = if cfg!(feature = "unstable-test" ) { |
46 | quote!({ #defmt_path::export::fetch_add_string_index() }) |
47 | } else { |
48 | let var_item: TokenStream = static_variable(&var_name, data:string, tag, prefix); |
49 | quote!({ |
50 | #var_item |
51 | &#var_name as *const u8 as u16 |
52 | }) |
53 | }; |
54 | |
55 | quote!({ |
56 | #defmt_path::export::make_istr(#var_addr) |
57 | }) |
58 | } |
59 | |
60 | /// work around restrictions on length and allowed characters imposed by macos linker |
61 | /// returns (note the comma character for macos): |
62 | /// under macos: ".defmt," + 16 character hex digest of symbol's hash |
63 | /// otherwise: ".defmt." + prefix + symbol |
64 | pub(crate) fn linker_section(for_macos: bool, prefix: Option<&str>, symbol: &str) -> String { |
65 | let mut sub_section: String = if let Some(prefix: &str) = prefix { |
66 | format!(". {prefix}. {symbol}" ) |
67 | } else { |
68 | format!(". {symbol}" ) |
69 | }; |
70 | |
71 | if for_macos { |
72 | sub_section = format!(", {:x}" , hash(&sub_section)); |
73 | } |
74 | |
75 | format!(".defmt {sub_section}" ) |
76 | } |
77 | |
78 | pub(crate) fn static_variable( |
79 | name: &Ident2, |
80 | data: &str, |
81 | tag: &str, |
82 | prefix: Option<&str>, |
83 | ) -> TokenStream2 { |
84 | let sym_name: String = mangled_symbol_name(tag, data); |
85 | let section: String = linker_section(for_macos:false, prefix, &sym_name); |
86 | let section_for_macos: String = linker_section(for_macos:true, prefix, &sym_name); |
87 | |
88 | quote!( |
89 | #[cfg_attr(target_os = "macos" , link_section = #section_for_macos)] |
90 | #[cfg_attr(not(target_os = "macos" ), link_section = #section)] |
91 | #[export_name = #sym_name] |
92 | static #name: u8 = 0; |
93 | ) |
94 | } |
95 | |
96 | pub(crate) fn string_literal(content: &str) -> LitStr { |
97 | LitStr::new(value:content, span:Span2::call_site()) |
98 | } |
99 | |
100 | pub(crate) fn variable(name: &str) -> Expr { |
101 | let ident: Ident = Ident::new(string:name, span:Span2::call_site()); |
102 | parse_quote!(#ident) |
103 | } |
104 | |
105 | fn hash(string: &str) -> u64 { |
106 | let mut hasher: DefaultHasher = DefaultHasher::new(); |
107 | string.hash(&mut hasher); |
108 | hasher.finish() |
109 | } |
110 | |