avr_device_macros/
lib.rs

1// Adapted from https://github.com/rust-embedded/cortex-m-rt/blob/master/macros/src/lib.rs
2
3extern crate proc_macro;
4
5mod vector;
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                {
87                    #(#attrs)*
88                    static mut #ident: #ty = #expr;
89                    &mut #ident
90                }
91            }
92        })
93        .collect::<Vec<_>>();
94
95    quote::quote! (
96        #[cfg(not(any(doc, target_arch = "avr")))]
97        compile_error!(
98            "Ensure that you are using an AVR target! You may need to change \
99       directories or pass a --target flag to cargo. See
100       https://github.com/Rahix/avr-device/pull/41 for more details."
101        );
102
103        #[doc(hidden)]
104        #[export_name = "main"]
105        pub unsafe extern "C" fn #tramp_ident() {
106            #ident(
107                #(#resource_args),*
108            )
109        }
110
111        #[doc(hidden)]
112        #f
113    )
114    .into()
115}
116
117#[proc_macro_attribute]
118pub fn interrupt(
119    args: proc_macro::TokenStream,
120    input: proc_macro::TokenStream,
121) -> proc_macro::TokenStream {
122    let mut f: syn::ItemFn =
123        syn::parse(input).expect("`#[interrupt]` must be applied to a function");
124    let args: Vec<_> = args.into_iter().collect();
125
126    let fspan = f.span();
127    let ident = f.sig.ident.clone();
128    let ident_s = ident.to_string();
129
130    let chip = if let Some(tree) = args.get(0) {
131        if let proc_macro::TokenTree::Ident(ident) = tree {
132            ident.to_string()
133        } else {
134            return syn::parse::Error::new(
135                proc_macro2::Span::call_site(),
136                "#[interrupt(chip)]: chip must be an ident",
137            )
138            .to_compile_error()
139            .into();
140        }
141    } else {
142        return syn::parse::Error::new(
143            proc_macro2::Span::call_site(),
144            "#[interrupt(chip)] needs a chip argument",
145        )
146        .to_compile_error()
147        .into();
148    };
149
150    let valid_signature = f.sig.constness.is_none()
151        && f.vis == syn::Visibility::Inherited
152        && f.sig.abi.is_none()
153        && f.sig.inputs.is_empty()
154        && f.sig.generics.params.is_empty()
155        && f.sig.generics.where_clause.is_none()
156        && f.sig.variadic.is_none()
157        && match f.sig.output {
158            syn::ReturnType::Default => true,
159            syn::ReturnType::Type(_, ref ty) => match **ty {
160                syn::Type::Tuple(ref tuple) => tuple.elems.is_empty(),
161                syn::Type::Never(..) => true,
162                _ => false,
163            },
164        };
165
166    if !valid_signature {
167        return syn::parse::Error::new(
168            fspan,
169            "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
170        )
171        .to_compile_error()
172        .into();
173    }
174
175    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
176        Err(e) => return e.to_compile_error().into(),
177        Ok(x) => x,
178    };
179
180    f.sig.ident = syn::Ident::new(
181        &format!("__avr_device_rt_{}", f.sig.ident),
182        proc_macro2::Span::call_site(),
183    );
184    f.sig.inputs.extend(statics.iter().map(|statik| {
185        let ident = &statik.ident;
186        let ty = &statik.ty;
187        let attrs = &statik.attrs;
188        syn::parse::<syn::FnArg>(
189            quote::quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into(),
190        )
191        .unwrap()
192    }));
193    f.block.stmts = stmts;
194
195    let tramp_ident = syn::Ident::new(
196        &format!("{}_trampoline", f.sig.ident),
197        proc_macro2::Span::call_site(),
198    );
199    let ident = &f.sig.ident;
200
201    let resource_args = statics
202        .iter()
203        .map(|statik| {
204            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
205            let ident = &statik.ident;
206            let ty = &statik.ty;
207            let expr = &statik.expr;
208            quote::quote! {
209                #(#cfgs)*
210                {
211                    #(#attrs)*
212                    static mut #ident: #ty = #expr;
213                    &mut #ident
214                }
215            }
216        })
217        .collect::<Vec<_>>();
218
219    let vect = if let Some(v) = vector::lookup_vector(&chip, &ident_s) {
220        v
221    } else {
222        return syn::parse::Error::new(
223            proc_macro2::Span::call_site(),
224            &format!("Chip `{}` or interrupt `{}` unknown", chip, ident_s),
225        )
226        .to_compile_error()
227        .into();
228    };
229    let vector = format!("__vector_{}", vect);
230    let vector_ident = syn::Ident::new(&vector, proc_macro2::Span::call_site());
231    let vector_ident_s = vector_ident.to_string();
232
233    quote::quote! (
234        #[doc(hidden)]
235        #[export_name = #vector_ident_s]
236        pub unsafe extern "avr-interrupt" fn #tramp_ident() {
237            #ident(
238                #(#resource_args),*
239            )
240        }
241
242        #[doc(hidden)]
243        #f
244    )
245    .into()
246}
247
248/// Extracts `static mut` vars from the beginning of the given statements
249fn extract_static_muts(
250    stmts: impl IntoIterator<Item = syn::Stmt>,
251) -> Result<(Vec<syn::ItemStatic>, Vec<syn::Stmt>), syn::parse::Error> {
252    let mut istmts = stmts.into_iter();
253
254    let mut seen = std::collections::HashSet::new();
255    let mut statics = vec![];
256    let mut stmts = vec![];
257    while let Some(stmt) = istmts.next() {
258        match stmt {
259            syn::Stmt::Item(syn::Item::Static(var)) => {
260                if var.mutability.is_some() {
261                    if seen.contains(&var.ident) {
262                        return Err(syn::parse::Error::new(
263                            var.ident.span(),
264                            format!("the name `{}` is defined multiple times", var.ident),
265                        ));
266                    }
267
268                    seen.insert(var.ident.clone());
269                    statics.push(var);
270                } else {
271                    stmts.push(syn::Stmt::Item(syn::Item::Static(var)));
272                }
273            }
274            _ => {
275                stmts.push(stmt);
276                break;
277            }
278        }
279    }
280
281    stmts.extend(istmts);
282
283    Ok((statics, stmts))
284}
285
286fn extract_cfgs(attrs: Vec<syn::Attribute>) -> (Vec<syn::Attribute>, Vec<syn::Attribute>) {
287    let mut cfgs = vec![];
288    let mut not_cfgs = vec![];
289
290    for attr in attrs {
291        if eq(&attr, "cfg") {
292            cfgs.push(attr);
293        } else {
294            not_cfgs.push(attr);
295        }
296    }
297
298    (cfgs, not_cfgs)
299}
300
301/// Returns `true` if `attr.path` matches `name`
302fn eq(attr: &syn::Attribute, name: &str) -> bool {
303    attr.style == syn::AttrStyle::Outer && attr.path.is_ident(name)
304}