avr_device_macros/
lib.rs

1//! Adapted from <https://github.com/rust-embedded/cortex-m/blob/ce12620be0e51f9a8bbe6bb67cfc131201b55f34/cortex-m-rt/macros/src/lib.rs>
2//!
3//! Do not use this crate directly.
4
5extern crate proc_macro;
6
7use syn::spanned::Spanned;
8
9#[proc_macro_attribute]
10pub fn entry(
11    args: proc_macro::TokenStream,
12    input: proc_macro::TokenStream,
13) -> proc_macro::TokenStream {
14    let mut f = syn::parse_macro_input!(input as syn::ItemFn);
15
16    // check the function signature
17    let valid_signature = f.sig.constness.is_none()
18        && f.vis == syn::Visibility::Inherited
19        && f.sig.abi.is_none()
20        && f.sig.inputs.is_empty()
21        && f.sig.generics.params.is_empty()
22        && f.sig.generics.where_clause.is_none()
23        && f.sig.variadic.is_none()
24        && match f.sig.output {
25            syn::ReturnType::Default => false,
26            syn::ReturnType::Type(_, ref ty) => matches!(**ty, syn::Type::Never(_)),
27        };
28
29    if !valid_signature {
30        return syn::parse::Error::new(
31            f.span(),
32            "`#[entry]` function must have signature `[unsafe] fn() -> !`",
33        )
34        .to_compile_error()
35        .into();
36    }
37
38    if !args.is_empty() {
39        return syn::parse::Error::new(
40            proc_macro2::Span::call_site(),
41            "This attribute accepts no arguments",
42        )
43        .to_compile_error()
44        .into();
45    }
46
47    let (statics, stmts) = match extract_static_muts(f.block.stmts) {
48        Err(e) => return e.to_compile_error().into(),
49        Ok(x) => x,
50    };
51
52    // Rename the function so it is not callable
53    f.sig.ident = syn::Ident::new(
54        &format!("__avr_device_rt_{}", f.sig.ident),
55        proc_macro2::Span::call_site(),
56    );
57    f.sig.inputs.extend(statics.iter().map(|statik| {
58        let ident = &statik.ident;
59        let ty = &statik.ty;
60        let attrs = &statik.attrs;
61
62        // Note that we use an explicit `'static` lifetime for the entry point arguments. This makes
63        // it more flexible, and is sound here, since the entry will not be called again, ever.
64        syn::parse::<syn::FnArg>(
65            quote::quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(),
66        )
67        .unwrap()
68    }));
69    f.block.stmts = stmts;
70
71    let tramp_ident = syn::Ident::new(
72        &format!("{}_trampoline", f.sig.ident),
73        proc_macro2::Span::call_site(),
74    );
75    let ident = &f.sig.ident;
76
77    let resource_args = statics
78        .iter()
79        .map(|statik| {
80            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
81            let ident = &statik.ident;
82            let ty = &statik.ty;
83            let expr = &statik.expr;
84            quote::quote! {
85                #(#cfgs)*
86                unsafe {
87                    #(#attrs)*
88                    static mut #ident: #ty = #expr;
89                    &mut #ident
90                }
91            }
92        })
93        .collect::<Vec<_>>();
94
95    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) {
96        return error;
97    }
98
99    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
100
101    quote::quote! (
102        #[cfg(not(any(doc, target_arch = "avr")))]
103        compile_error!(
104            "Ensure that you are using an AVR target! You may need to change \
105       directories or pass a --target flag to cargo. See
106       https://github.com/Rahix/avr-device/pull/41 for more details."
107        );
108
109        #(#cfgs)*
110        #(#attrs)*
111        #[doc(hidden)]
112        // Since avr-libc >= 2.3.0, pulling main in the .init9
113        // section is required to include it in the init sequence.
114        // Keeping the export name "main" is enough to maintain
115        // backward compatibility.
116        #[link_section = ".init9"]
117        #[export_name = "main"]
118        pub unsafe extern "C" fn #tramp_ident() {
119            #ident(
120                #(#resource_args),*
121            )
122        }
123
124        #[doc(hidden)]
125        #f
126    )
127    .into()
128}
129
130#[proc_macro_attribute]
131pub fn interrupt(
132    args: proc_macro::TokenStream,
133    input: proc_macro::TokenStream,
134) -> proc_macro::TokenStream {
135    let mut f: syn::ItemFn =
136        syn::parse(input).expect("`#[interrupt]` must be applied to a function");
137    let args: Vec<_> = args.into_iter().collect();
138
139    let fspan = f.span();
140    let ident = f.sig.ident.clone();
141
142    let chip = if let Some(tree) = args.get(0) {
143        if let proc_macro::TokenTree::Ident(ident) = tree {
144            syn::Ident::new(&ident.to_string(), fspan)
145        } else {
146            return syn::parse::Error::new(
147                proc_macro2::Span::call_site(),
148                "#[interrupt(chip)]: chip must be an ident",
149            )
150            .to_compile_error()
151            .into();
152        }
153    } else {
154        return syn::parse::Error::new(
155            proc_macro2::Span::call_site(),
156            "#[interrupt(chip)] needs a chip argument",
157        )
158        .to_compile_error()
159        .into();
160    };
161
162    let valid_signature = f.sig.constness.is_none()
163        && f.vis == syn::Visibility::Inherited
164        && f.sig.abi.is_none()
165        && f.sig.inputs.is_empty()
166        && f.sig.generics.params.is_empty()
167        && f.sig.generics.where_clause.is_none()
168        && f.sig.variadic.is_none()
169        && match f.sig.output {
170            syn::ReturnType::Default => true,
171            syn::ReturnType::Type(_, ref ty) => match **ty {
172                syn::Type::Tuple(ref tuple) => tuple.elems.is_empty(),
173                syn::Type::Never(..) => true,
174                _ => false,
175            },
176        };
177
178    if !valid_signature {
179        return syn::parse::Error::new(
180            fspan,
181            "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
182        )
183        .to_compile_error()
184        .into();
185    }
186
187    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
188        Err(e) => return e.to_compile_error().into(),
189        Ok(x) => x,
190    };
191
192    f.sig.ident = syn::Ident::new(
193        &format!("__avr_device_rt_{}", f.sig.ident),
194        proc_macro2::Span::call_site(),
195    );
196    f.sig.inputs.extend(statics.iter().map(|statik| {
197        let ident = &statik.ident;
198        let ty = &statik.ty;
199        let attrs = &statik.attrs;
200        syn::parse::<syn::FnArg>(
201            quote::quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into(),
202        )
203        .unwrap()
204    }));
205    f.block.stmts = stmts;
206
207    let resource_args = statics
208        .iter()
209        .map(|statik| {
210            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
211            let ident = &statik.ident;
212            let ty = &statik.ty;
213            let expr = &statik.expr;
214            quote::quote! {
215                #(#cfgs)*
216                unsafe {
217                    #(#attrs)*
218                    static mut #ident: #ty = #expr;
219                    &mut #ident
220                }
221            }
222        })
223        .collect::<Vec<_>>();
224
225    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
226        return error;
227    }
228
229    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
230
231    let tramp_ident = syn::Ident::new(
232        &format!("{}_trampoline", f.sig.ident),
233        proc_macro2::Span::call_site(),
234    );
235    let interrupt_ident = &f.sig.ident;
236
237    quote::quote! {
238        #(#cfgs)*
239        #(#attrs)*
240        ::avr_device::__avr_device_trampoline!(@#chip, #ident, pub extern "avr-interrupt" fn #tramp_ident() {
241            #[allow(static_mut_refs)]
242            #interrupt_ident(
243                #(#resource_args),*
244            )
245        });
246
247        #f
248    }
249    .into()
250}
251
252/// Extracts `static mut` vars from the beginning of the given statements
253fn extract_static_muts(
254    stmts: impl IntoIterator<Item = syn::Stmt>,
255) -> Result<(Vec<syn::ItemStatic>, Vec<syn::Stmt>), syn::parse::Error> {
256    let mut istmts = stmts.into_iter();
257
258    let mut seen = std::collections::HashSet::new();
259    let mut statics = vec![];
260    let mut stmts = vec![];
261    while let Some(stmt) = istmts.next() {
262        match stmt {
263            syn::Stmt::Item(syn::Item::Static(var)) => {
264                if var.mutability.is_some() {
265                    if seen.contains(&var.ident) {
266                        return Err(syn::parse::Error::new(
267                            var.ident.span(),
268                            format!("the name `{}` is defined multiple times", var.ident),
269                        ));
270                    }
271
272                    seen.insert(var.ident.clone());
273                    statics.push(var);
274                } else {
275                    stmts.push(syn::Stmt::Item(syn::Item::Static(var)));
276                }
277            }
278            _ => {
279                stmts.push(stmt);
280                break;
281            }
282        }
283    }
284
285    stmts.extend(istmts);
286
287    Ok((statics, stmts))
288}
289
290fn extract_cfgs(attrs: Vec<syn::Attribute>) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
291    let mut cfgs = vec![];
292    let mut not_cfgs = vec![];
293
294    for attr in attrs {
295        if eq(&attr, "cfg") {
296            cfgs.push(attr);
297        } else {
298            not_cfgs.push(attr);
299        }
300    }
301
302    (cfgs, not_cfgs)
303}
304
305enum WhiteListCaller {
306    Entry,
307    Interrupt,
308}
309
310fn check_attr_whitelist(attrs: &[syn::Attribute], caller: WhiteListCaller) -> Result<(), proc_macro::TokenStream> {
311    let whitelist = &[
312        "doc",
313        "link_section",
314        "cfg",
315        "allow",
316        "warn",
317        "deny",
318        "forbid",
319        "cold",
320        "naked",
321    ];
322
323    'o: for attr in attrs {
324        for val in whitelist {
325            if eq(attr, val) {
326                continue 'o;
327            }
328        }
329
330        let err_str = match caller {
331            WhiteListCaller::Entry => "this attribute is not allowed on an avr-device entry point",
332            WhiteListCaller::Interrupt => {
333                "this attribute is not allowed on an interrupt handler controlled by avr-device"
334            }
335        };
336
337        return Err(syn::parse::Error::new(attr.span(), err_str)
338            .to_compile_error()
339            .into());
340    }
341
342    Ok(())
343}
344
345/// Returns `true` if `attr.path` matches `name`
346fn eq(attr: &syn::Attribute, name: &str) -> bool {
347    attr.style == syn::AttrStyle::Outer && attr.path.is_ident(name)
348}