esp32_hal_proc_macros/
lib.rs

1//! Internal implementation details of esp32-hal`.
2//!
3//! Do not use this crate directly.
4//!
5//! # TODO:
6//! [ ] Checking of all called functions and data are in ram
7//! [ ] Automatic checking of 0 init and then use .bss segment
8
9#![deny(warnings)]
10#![allow(unused_braces)]
11#![feature(proc_macro_diagnostic)]
12
13extern crate proc_macro;
14
15use darling::FromMeta;
16use proc_macro::Span;
17use proc_macro::TokenStream;
18use quote::quote;
19use std::collections::HashSet;
20use std::iter;
21use syn::{
22    parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, AttributeArgs, FnArg, Ident,
23    Item, ItemFn, ItemStatic, Meta::Path, ReturnType, Stmt, Type, Visibility,
24};
25
26// TODO:
27// - check if all called function are in ram
28// - check if all used data is in ram
29// - check that no constants are use in the function (these cannot be forced to ram)
30fn check_ram_function(_func: &syn::ItemFn) {
31    //    eprintln!("{:?}", func);
32}
33
34#[derive(Debug, Default, FromMeta)]
35#[darling(default)]
36struct RamArgs {
37    rtc_fast: bool,
38    rtc_slow: bool,
39    external: bool,
40    uninitialized: bool,
41    zeroed: bool,
42}
43
44/// This attribute allows placing statics, constants and functions into ram.
45///
46/// Options that can be specified are rtc_slow, rtc_fast or external to use the
47/// RTC slow, RTC fast ram or external SPI RAM instead of the normal SRAM.
48///
49/// The uninitialized option will skip initialization of the memory
50/// (e.g. to persist it across resets or deep sleep mode for the RTC RAM)
51
52#[proc_macro_attribute]
53pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
54    let attr_args = parse_macro_input!(args as AttributeArgs);
55
56    let RamArgs {
57        rtc_fast,
58        rtc_slow,
59        external,
60        uninitialized,
61        zeroed,
62    } = match FromMeta::from_list(&attr_args) {
63        Ok(v) => v,
64        Err(e) => {
65            return e.write_errors().into();
66        }
67    };
68
69    if rtc_slow && rtc_fast {
70        Span::call_site()
71            .error("Only one of rtc_slow and rtc_fast is allowed")
72            .emit();
73    }
74
75    if rtc_slow && rtc_fast {
76        Span::call_site()
77            .error("Only one of uninitialized and zeroed")
78            .emit();
79    }
80
81    if external && cfg!(not(feature = "external_ram")) {
82        Span::call_site()
83            .error("External ram support not enabled")
84            .emit();
85    }
86
87    let section_name_data = if rtc_slow {
88        if uninitialized {
89            ".rtc_slow.noinit"
90        } else if zeroed {
91            ".rtc_slow.bss"
92        } else {
93            ".rtc_slow.data"
94        }
95    } else if rtc_fast {
96        if uninitialized {
97            ".rtc_fast.noinit"
98        } else if zeroed {
99            ".rtc_fast.bss"
100        } else {
101            ".rtc_fast.data"
102        }
103    } else if external {
104        if uninitialized {
105            ".external.noinit"
106        } else if zeroed {
107            ".external.bss"
108        } else {
109            ".external.data"
110        }
111    } else {
112        if uninitialized {
113            ".noinit"
114        } else {
115            ".data"
116        }
117    };
118
119    let section_name_text = if rtc_slow {
120        ".rtc_slow.text"
121    } else if rtc_fast {
122        ".rtc_fast.text"
123    } else if external {
124        ".invalid"
125    } else {
126        ".rwtext"
127    };
128
129    let item: syn::Item = syn::parse(input).expect("failed to parse input");
130
131    let section: proc_macro2::TokenStream;
132    match item {
133        Item::Static(ref _struct_item) => section = quote! {#[link_section=#section_name_data]},
134        Item::Const(ref _struct_item) => section = quote! {#[link_section=#section_name_data]},
135        Item::Fn(ref function_item) => {
136            if zeroed {
137                Span::call_site()
138                    .error("Zeroed is not applicable to functions")
139                    .emit();
140            }
141            if uninitialized {
142                Span::call_site()
143                    .error("Uninitialized is not applicable to functions")
144                    .emit();
145            }
146            if external {
147                Span::call_site()
148                    .error("External is not applicable to functions")
149                    .emit();
150            }
151            check_ram_function(function_item);
152            section = quote! {
153                #[link_section=#section_name_text]
154                #[inline(never)] // make certain function is not inlined
155            };
156        }
157        _ => {
158            section = quote! {};
159            item.span()
160                .unstable()
161                .error("#[ram] attribute can only be applied to functions and statics")
162                .emit();
163        }
164    }
165
166    let output = quote! {
167        #section
168        #item
169    };
170    output.into()
171}
172
173/// Marks a function as an interrupt handler
174///
175/// Used to handle on of the [interrupts](enum.Interrupt.html).
176///
177/// When specified between braces (`#[interrupt(example)]`) that interrupt will be used and the function
178/// can have an arbitrary name. Otherwise the name of the function must be the name of the
179/// interrupt.
180#[proc_macro_attribute]
181pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
182    let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
183
184    let attr_args = parse_macro_input!(args as AttributeArgs);
185
186    if attr_args.len() > 1 {
187        Span::call_site()
188            .error("This attribute accepts zero or 1 arguments")
189            .emit();
190    }
191
192    let ident = f.sig.ident.clone();
193    let mut ident_s = &ident.clone();
194
195    if attr_args.len() == 1 {
196        match &attr_args[0] {
197            syn::NestedMeta::Meta(Path(x)) => {
198                ident_s = x.get_ident().unwrap();
199            }
200            _ => {
201                Span::call_site()
202                    .error(format!(
203                        "This attribute accepts a string attribute {:?}",
204                        attr_args[0]
205                    ))
206                    .emit();
207            }
208        }
209    }
210
211    // XXX should we blacklist other attributes?
212
213    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
214        return error;
215    }
216
217    let valid_signature = f.sig.constness.is_none()
218        && f.vis == Visibility::Inherited
219        && f.sig.abi.is_none()
220        && f.sig.inputs.is_empty()
221        && f.sig.generics.params.is_empty()
222        && f.sig.generics.where_clause.is_none()
223        && f.sig.variadic.is_none()
224        && match f.sig.output {
225            ReturnType::Default => true,
226            ReturnType::Type(_, ref ty) => match **ty {
227                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
228                Type::Never(..) => true,
229                _ => false,
230            },
231        };
232
233    if !valid_signature {
234        return parse::Error::new(
235            f.span(),
236            "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
237        )
238        .to_compile_error()
239        .into();
240    }
241
242    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
243        Err(e) => return e.to_compile_error().into(),
244        Ok(x) => x,
245    };
246
247    f.sig.ident = Ident::new(
248        &format!("__xtensa_lx_6_{}", f.sig.ident),
249        proc_macro2::Span::call_site(),
250    );
251    f.sig.inputs.extend(statics.iter().map(|statik| {
252        let ident = &statik.ident;
253        let ty = &statik.ty;
254        let attrs = &statik.attrs;
255        syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
256            .unwrap()
257    }));
258    f.block.stmts = iter::once(
259        syn::parse2(quote! {{
260            // Check that this interrupt actually exists
261            interrupt::#ident_s;
262        }})
263        .unwrap(),
264    )
265    .chain(stmts)
266    .collect();
267
268    let tramp_ident = Ident::new(
269        &format!("{}_trampoline", f.sig.ident),
270        proc_macro2::Span::call_site(),
271    );
272    let ident = &f.sig.ident;
273
274    let resource_args = statics
275        .iter()
276        .map(|statik| {
277            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
278            let ident = &statik.ident;
279            let ty = &statik.ty;
280            let expr = &statik.expr;
281            quote! {
282                #(#cfgs)*
283                {
284                    #(#attrs)*
285                    static mut #ident: #ty = #expr;
286                    &mut #ident
287                }
288            }
289        })
290        .collect::<Vec<_>>();
291
292    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
293
294    let export_name = ident_s.to_string();
295
296    quote!(
297        #(#cfgs)*
298        #(#attrs)*
299        #[doc(hidden)]
300        #[export_name = #export_name]
301        pub unsafe extern "C" fn #tramp_ident() {
302            #ident(
303                #(#resource_args),*
304            )
305        }
306
307        #[inline(always)]
308        #f
309    )
310    .into()
311}
312
313/// Extracts `static mut` vars from the beginning of the given statements
314fn extract_static_muts(
315    stmts: impl IntoIterator<Item = Stmt>,
316) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
317    let mut istmts = stmts.into_iter();
318
319    let mut seen = HashSet::new();
320    let mut statics = vec![];
321    let mut stmts = vec![];
322    while let Some(stmt) = istmts.next() {
323        match stmt {
324            Stmt::Item(Item::Static(var)) => {
325                if var.mutability.is_some() {
326                    if seen.contains(&var.ident) {
327                        return Err(parse::Error::new(
328                            var.ident.span(),
329                            format!("the name `{}` is defined multiple times", var.ident),
330                        ));
331                    }
332
333                    seen.insert(var.ident.clone());
334                    statics.push(var);
335                } else {
336                    stmts.push(Stmt::Item(Item::Static(var)));
337                }
338            }
339            _ => {
340                stmts.push(stmt);
341                break;
342            }
343        }
344    }
345
346    stmts.extend(istmts);
347
348    Ok((statics, stmts))
349}
350
351fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
352    let mut cfgs = vec![];
353    let mut not_cfgs = vec![];
354
355    for attr in attrs {
356        if eq(&attr, "cfg") {
357            cfgs.push(attr);
358        } else {
359            not_cfgs.push(attr);
360        }
361    }
362
363    (cfgs, not_cfgs)
364}
365
366enum WhiteListCaller {
367    Interrupt,
368}
369
370fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
371    let whitelist = &[
372        "doc",
373        "link_section",
374        "cfg",
375        "allow",
376        "warn",
377        "deny",
378        "forbid",
379        "cold",
380        "ram",
381    ];
382
383    'o: for attr in attrs {
384        for val in whitelist {
385            if eq(&attr, &val) {
386                continue 'o;
387            }
388        }
389
390        let err_str = match caller {
391            WhiteListCaller::Interrupt => {
392                "this attribute is not allowed on an interrupt handler controlled by esp32_hal"
393            }
394        };
395
396        return Err(parse::Error::new(attr.span(), &err_str)
397            .to_compile_error()
398            .into());
399    }
400
401    Ok(())
402}
403
404/// Returns `true` if `attr.path` matches `name`
405fn eq(attr: &Attribute, name: &str) -> bool {
406    attr.style == AttrStyle::Outer && attr.path.is_ident(name)
407}