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        #[export_name = "main"]
113        pub unsafe extern "C" fn #tramp_ident() {
114            #ident(
115                #(#resource_args),*
116            )
117        }
118
119        #[doc(hidden)]
120        #f
121    )
122    .into()
123}
124
125#[proc_macro_attribute]
126pub fn interrupt(
127    args: proc_macro::TokenStream,
128    input: proc_macro::TokenStream,
129) -> proc_macro::TokenStream {
130    let mut f: syn::ItemFn =
131        syn::parse(input).expect("`#[interrupt]` must be applied to a function");
132    let args: Vec<_> = args.into_iter().collect();
133
134    let fspan = f.span();
135    let ident = f.sig.ident.clone();
136
137    let chip = if let Some(tree) = args.get(0) {
138        if let proc_macro::TokenTree::Ident(ident) = tree {
139            syn::Ident::new(&ident.to_string(), fspan)
140        } else {
141            return syn::parse::Error::new(
142                proc_macro2::Span::call_site(),
143                "#[interrupt(chip)]: chip must be an ident",
144            )
145            .to_compile_error()
146            .into();
147        }
148    } else {
149        return syn::parse::Error::new(
150            proc_macro2::Span::call_site(),
151            "#[interrupt(chip)] needs a chip argument",
152        )
153        .to_compile_error()
154        .into();
155    };
156
157    let valid_signature = f.sig.constness.is_none()
158        && f.vis == syn::Visibility::Inherited
159        && f.sig.abi.is_none()
160        && f.sig.inputs.is_empty()
161        && f.sig.generics.params.is_empty()
162        && f.sig.generics.where_clause.is_none()
163        && f.sig.variadic.is_none()
164        && match f.sig.output {
165            syn::ReturnType::Default => true,
166            syn::ReturnType::Type(_, ref ty) => match **ty {
167                syn::Type::Tuple(ref tuple) => tuple.elems.is_empty(),
168                syn::Type::Never(..) => true,
169                _ => false,
170            },
171        };
172
173    if !valid_signature {
174        return syn::parse::Error::new(
175            fspan,
176            "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
177        )
178        .to_compile_error()
179        .into();
180    }
181
182    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
183        Err(e) => return e.to_compile_error().into(),
184        Ok(x) => x,
185    };
186
187    f.sig.ident = syn::Ident::new(
188        &format!("__avr_device_rt_{}", f.sig.ident),
189        proc_macro2::Span::call_site(),
190    );
191    f.sig.inputs.extend(statics.iter().map(|statik| {
192        let ident = &statik.ident;
193        let ty = &statik.ty;
194        let attrs = &statik.attrs;
195        syn::parse::<syn::FnArg>(
196            quote::quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into(),
197        )
198        .unwrap()
199    }));
200    f.block.stmts = stmts;
201
202    let resource_args = statics
203        .iter()
204        .map(|statik| {
205            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
206            let ident = &statik.ident;
207            let ty = &statik.ty;
208            let expr = &statik.expr;
209            quote::quote! {
210                #(#cfgs)*
211                unsafe {
212                    #(#attrs)*
213                    static mut #ident: #ty = #expr;
214                    &mut #ident
215                }
216            }
217        })
218        .collect::<Vec<_>>();
219
220    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
221        return error;
222    }
223
224    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
225
226    let tramp_ident = syn::Ident::new(
227        &format!("{}_trampoline", f.sig.ident),
228        proc_macro2::Span::call_site(),
229    );
230    let interrupt_ident = &f.sig.ident;
231
232    quote::quote! {
233        #(#cfgs)*
234        #(#attrs)*
235        ::avr_device::__avr_device_trampoline!(@#chip, #ident, pub extern "avr-interrupt" fn #tramp_ident() {
236            #[allow(static_mut_refs)]
237            #interrupt_ident(
238                #(#resource_args),*
239            )
240        });
241
242        #f
243    }
244    .into()
245}
246
247/// Extracts `static mut` vars from the beginning of the given statements
248fn extract_static_muts(
249    stmts: impl IntoIterator<Item = syn::Stmt>,
250) -> Result<(Vec<syn::ItemStatic>, Vec<syn::Stmt>), syn::parse::Error> {
251    let mut istmts = stmts.into_iter();
252
253    let mut seen = std::collections::HashSet::new();
254    let mut statics = vec![];
255    let mut stmts = vec![];
256    while let Some(stmt) = istmts.next() {
257        match stmt {
258            syn::Stmt::Item(syn::Item::Static(var)) => {
259                if var.mutability.is_some() {
260                    if seen.contains(&var.ident) {
261                        return Err(syn::parse::Error::new(
262                            var.ident.span(),
263                            format!("the name `{}` is defined multiple times", var.ident),
264                        ));
265                    }
266
267                    seen.insert(var.ident.clone());
268                    statics.push(var);
269                } else {
270                    stmts.push(syn::Stmt::Item(syn::Item::Static(var)));
271                }
272            }
273            _ => {
274                stmts.push(stmt);
275                break;
276            }
277        }
278    }
279
280    stmts.extend(istmts);
281
282    Ok((statics, stmts))
283}
284
285fn extract_cfgs(attrs: Vec<syn::Attribute>) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
286    let mut cfgs = vec![];
287    let mut not_cfgs = vec![];
288
289    for attr in attrs {
290        if eq(&attr, "cfg") {
291            cfgs.push(attr);
292        } else {
293            not_cfgs.push(attr);
294        }
295    }
296
297    (cfgs, not_cfgs)
298}
299
300enum WhiteListCaller {
301    Entry,
302    Interrupt,
303}
304
305fn check_attr_whitelist(attrs: &[syn::Attribute], caller: WhiteListCaller) -> Result<(), proc_macro::TokenStream> {
306    let whitelist = &[
307        "doc",
308        "link_section",
309        "cfg",
310        "allow",
311        "warn",
312        "deny",
313        "forbid",
314        "cold",
315        "naked",
316    ];
317
318    'o: for attr in attrs {
319        for val in whitelist {
320            if eq(attr, val) {
321                continue 'o;
322            }
323        }
324
325        let err_str = match caller {
326            WhiteListCaller::Entry => "this attribute is not allowed on an avr-device entry point",
327            WhiteListCaller::Interrupt => {
328                "this attribute is not allowed on an interrupt handler controlled by avr-device"
329            }
330        };
331
332        return Err(syn::parse::Error::new(attr.span(), err_str)
333            .to_compile_error()
334            .into());
335    }
336
337    Ok(())
338}
339
340/// Returns `true` if `attr.path` matches `name`
341fn eq(attr: &syn::Attribute, name: &str) -> bool {
342    attr.style == syn::AttrStyle::Outer && attr.path.is_ident(name)
343}