esp8266_hal_proc_macros/
lib.rs

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