xtensa_lx_rt_proc_macros/
lib.rs

1//! Internal implementation details of `xtensa-lx-rt`.
2//!
3//! Do not use this crate directly.
4
5#![deny(warnings)]
6
7extern crate proc_macro;
8
9use std::collections::HashSet;
10
11use proc_macro::TokenStream;
12use proc_macro2::Span;
13use quote::quote;
14use syn::{
15    AttrStyle,
16    Attribute,
17    FnArg,
18    Ident,
19    Item,
20    ItemFn,
21    ItemStatic,
22    ReturnType,
23    StaticMutability,
24    Stmt,
25    Token,
26    Type,
27    Visibility,
28    parse::{self, Parser},
29    parse_macro_input,
30    spanned::Spanned,
31};
32
33/// Marks a function as the main function to be called on program start
34#[proc_macro_attribute]
35pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
36    let mut f = parse_macro_input!(input as ItemFn);
37
38    // check the function signature
39    let valid_signature = f.sig.constness.is_none()
40        && matches!(f.vis, Visibility::Inherited)
41        && f.sig.abi.is_none()
42        && f.sig.inputs.is_empty()
43        && f.sig.generics.params.is_empty()
44        && f.sig.generics.where_clause.is_none()
45        && f.sig.variadic.is_none()
46        && match f.sig.output {
47            ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
48            _ => false,
49        };
50
51    if !valid_signature {
52        return parse::Error::new(
53            f.span(),
54            "`#[entry]` function must have signature `[unsafe] fn() -> !`",
55        )
56        .to_compile_error()
57        .into();
58    }
59
60    if !args.is_empty() {
61        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
62            .to_compile_error()
63            .into();
64    }
65
66    let (statics, stmts) = match extract_static_muts(f.block.stmts) {
67        Err(e) => return e.to_compile_error().into(),
68        Ok(x) => x,
69    };
70
71    f.sig.ident = Ident::new(
72        &format!("__xtensa_lx_rt_{}", f.sig.ident),
73        Span::call_site(),
74    );
75    f.sig.inputs.extend(statics.iter().map(|statik| {
76        let ident = &statik.ident;
77        let ty = &statik.ty;
78        let attrs = &statik.attrs;
79
80        // Note that we use an explicit `'static` lifetime for the entry point
81        // arguments. This makes it more flexible, and is sound here, since the
82        // entry will not be called again, ever.
83        syn::parse::<FnArg>(
84            quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(),
85        )
86        .unwrap()
87    }));
88    f.block.stmts = stmts;
89
90    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
91    let ident = &f.sig.ident;
92
93    let resource_args = statics
94        .iter()
95        .map(|statik| {
96            let (ref cfgs, ref attrs) = extract_cfgs(&statik.attrs);
97            let ident = &statik.ident;
98            let ty = &statik.ty;
99            let expr = &statik.expr;
100            quote! {
101                #(#cfgs)*
102                {
103                    #(#attrs)*
104                    static mut #ident: #ty = #expr;
105                    &mut #ident
106                }
107            }
108        })
109        .collect::<Vec<_>>();
110
111    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) {
112        return error;
113    }
114
115    let (ref cfgs, ref attrs) = extract_cfgs(&f.attrs);
116
117    quote!(
118        #(#cfgs)*
119        #(#attrs)*
120        #[doc(hidden)]
121        #[unsafe(export_name = "main")]
122        pub unsafe extern "C" fn #tramp_ident() {
123            #ident(
124                #(#resource_args),*
125            )
126        }
127
128        #[doc(hidden)]
129        #[allow(clippy::inline_always)]
130        #[inline(always)]
131        #f
132    )
133    .into()
134}
135
136/// Marks a function as the exception handler
137#[proc_macro_attribute]
138pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
139    let mut f = parse_macro_input!(input as ItemFn);
140
141    if !args.is_empty() {
142        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
143            .to_compile_error()
144            .into();
145    }
146
147    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) {
148        return error;
149    }
150
151    let valid_signature = f.sig.constness.is_none()
152        && matches!(f.vis, Visibility::Inherited)
153        && f.sig.abi.is_none()
154        && f.sig.inputs.len() <= 2
155        && f.sig.generics.params.is_empty()
156        && f.sig.generics.where_clause.is_none()
157        && f.sig.variadic.is_none()
158        && match f.sig.output {
159            ReturnType::Default => true,
160            ReturnType::Type(_, ref ty) => match **ty {
161                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
162                Type::Never(..) => true,
163                _ => false,
164            },
165        };
166
167    if !valid_signature {
168        return parse::Error::new(
169            f.span(),
170            "`#[exception]` handlers must have signature `[unsafe] fn([ExceptionCause[, Context]) [-> !]`",
171        )
172        .to_compile_error()
173        .into();
174    }
175
176    let (statics, stmts) = match extract_static_muts(f.block.stmts) {
177        Err(e) => return e.to_compile_error().into(),
178        Ok(x) => x,
179    };
180
181    f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site());
182    f.sig.inputs.extend(statics.iter().map(|statik| {
183        let ident = &statik.ident;
184        let ty = &statik.ty;
185        let attrs = &statik.attrs;
186        syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
187            .unwrap()
188    }));
189    f.block.stmts = stmts;
190
191    let (ref cfgs, ref attrs) = extract_cfgs(&f.attrs);
192
193    quote!(
194        #(#cfgs)*
195        #(#attrs)*
196        #[doc(hidden)]
197        #[export_name = "__user_exception"]
198        #f
199    )
200    .into()
201}
202
203/// Marks a function as the interrupt handler, with optional interrupt level
204/// indicated
205///
206/// When the function is also marked `#[naked]`, it is a low-level interrupt
207/// handler: no entry and exit code to store processor state will be generated.
208/// The user needs to ensure that all registers which are used are saved and
209/// restored and that the proper return instruction is used.
210#[proc_macro_attribute]
211pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
212    let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
213
214    let attr_args = match syn::punctuated::Punctuated::<syn::Lit, Token![,]>::parse_terminated
215        .parse2(args.into())
216        .map(|punctuated| punctuated.into_iter().collect::<Vec<_>>())
217    {
218        Ok(v) => v,
219        Err(e) => return e.into_compile_error().into(),
220    };
221
222    if attr_args.len() > 1 {
223        return parse::Error::new(
224            Span::call_site(),
225            "This attribute accepts zero or 1 arguments",
226        )
227        .to_compile_error()
228        .into();
229    }
230
231    let mut level = 1;
232
233    if attr_args.len() == 1 {
234        match &attr_args[0] {
235            syn::Lit::Int(lit_int) => match lit_int.base10_parse::<u32>() {
236                Ok(x) => level = x,
237                Err(_) => {
238                    return parse::Error::new(
239                        Span::call_site(),
240                        "This attribute accepts an integer attribute",
241                    )
242                    .to_compile_error()
243                    .into();
244                }
245            },
246            _ => {
247                return parse::Error::new(
248                    Span::call_site(),
249                    "This attribute accepts an integer attribute",
250                )
251                .to_compile_error()
252                .into();
253            }
254        }
255    }
256
257    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
258        return error;
259    }
260
261    let naked = f.attrs.iter().any(|x| eq(x, "naked"));
262
263    let ident_s = if naked {
264        format!("__naked_level_{level}_interrupt")
265    } else {
266        format!("__level_{level}_interrupt")
267    };
268
269    if naked && !(2..=7).contains(&level) {
270        return parse::Error::new(
271            f.span(),
272            "`#[naked]` `#[interrupt]` handlers must have interrupt level >=2 and <=7",
273        )
274        .to_compile_error()
275        .into();
276    } else if !naked && !(1..=7).contains(&level) {
277        return parse::Error::new(
278            f.span(),
279            "`#[interrupt]` handlers must have interrupt level >=1 and <=7",
280        )
281        .to_compile_error()
282        .into();
283    }
284
285    let valid_signature = f.sig.constness.is_none()
286        && matches!(f.vis, Visibility::Inherited)
287        && f.sig.abi.is_none()
288        && ((!naked && f.sig.inputs.len() <= 2) || (naked && f.sig.inputs.is_empty()))
289        && f.sig.generics.params.is_empty()
290        && f.sig.generics.where_clause.is_none()
291        && f.sig.variadic.is_none()
292        && match f.sig.output {
293            ReturnType::Default => true,
294            ReturnType::Type(_, ref ty) => match **ty {
295                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
296                Type::Never(..) => true,
297                _ => false,
298            },
299        };
300
301    if !valid_signature {
302        if naked {
303            return parse::Error::new(
304                f.span(),
305                "`#[naked]` `#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
306            )
307            .to_compile_error()
308            .into();
309        } else {
310            return parse::Error::new(
311                f.span(),
312                "`#[interrupt]` handlers must have signature `[unsafe] fn([u32[, Context]]) [-> !]`",
313            )
314            .to_compile_error()
315            .into();
316        }
317    }
318
319    let (statics, stmts) = match extract_static_muts(f.block.stmts.clone()) {
320        Err(e) => return e.to_compile_error().into(),
321        Ok(x) => x,
322    };
323
324    let inputs = f.sig.inputs.clone();
325
326    let args = inputs.iter().map(|arg| match arg {
327        syn::FnArg::Typed(x) => {
328            let pat = &*x.pat;
329            quote!(#pat)
330        }
331        _ => quote!(#arg),
332    });
333
334    f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site());
335    f.sig.inputs.extend(statics.iter().map(|statik| {
336        let ident = &statik.ident;
337        let ty = &statik.ty;
338        let attrs = &statik.attrs;
339        syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
340            .unwrap()
341    }));
342    f.block.stmts = stmts;
343
344    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
345    let ident = &f.sig.ident;
346
347    let resource_args = statics
348        .iter()
349        .map(|statik| {
350            let (ref cfgs, ref attrs) = extract_cfgs(&statik.attrs);
351            let ident = &statik.ident;
352            let ty = &statik.ty;
353            let expr = &statik.expr;
354            quote! {
355                #(#cfgs)*
356                {
357                    #(#attrs)*
358                    static mut #ident: #ty = #expr;
359                    &mut #ident
360                }
361            }
362        })
363        .collect::<Vec<_>>();
364
365    let (ref cfgs, ref attrs) = extract_cfgs(&f.attrs);
366
367    if naked {
368        quote!(
369            #(#cfgs)*
370            #(#attrs)*
371            #[doc(hidden)]
372            #[export_name = #ident_s]
373            pub unsafe extern "C" fn #tramp_ident() {
374                #ident(
375                    #(#resource_args),*
376                )
377            }
378
379            #[doc(hidden)]
380            #[allow(clippy::inline_always)]
381            #[inline(always)]
382            #f
383        )
384        .into()
385    } else {
386        quote!(
387            #(#cfgs)*
388            #(#attrs)*
389            #[doc(hidden)]
390            #[export_name = #ident_s]
391            pub unsafe extern "C" fn #tramp_ident(
392                level: u32,
393                frame: xtensa_lx_rt::exception::Context
394            ) {
395                    #ident(#(#args),*
396                    #(#resource_args),*
397                )
398            }
399
400            #[doc(hidden)]
401            #[allow(clippy::inline_always)]
402            #[inline(always)]
403            #f
404        )
405        .into()
406    }
407}
408
409/// Marks a function as the pre_init function. This function is called before
410/// main and *before the memory is initialized*.
411#[proc_macro_attribute]
412pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
413    let f = parse_macro_input!(input as ItemFn);
414
415    // check the function signature
416    let valid_signature = f.sig.constness.is_none()
417        && matches!(f.vis, Visibility::Inherited)
418        && f.sig.unsafety.is_some()
419        && f.sig.abi.is_none()
420        && f.sig.inputs.is_empty()
421        && f.sig.generics.params.is_empty()
422        && f.sig.generics.where_clause.is_none()
423        && f.sig.variadic.is_none()
424        && match f.sig.output {
425            ReturnType::Default => true,
426            ReturnType::Type(_, ref ty) => match **ty {
427                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
428                _ => false,
429            },
430        };
431
432    if !valid_signature {
433        return parse::Error::new(
434            f.span(),
435            "`#[pre_init]` function must have signature `unsafe fn()`",
436        )
437        .to_compile_error()
438        .into();
439    }
440
441    if !args.is_empty() {
442        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
443            .to_compile_error()
444            .into();
445    }
446
447    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) {
448        return error;
449    }
450
451    let attrs = f.attrs;
452    let ident = f.sig.ident;
453    let block = f.block;
454
455    quote!(
456        #[export_name = "__pre_init"]
457        #[allow(missing_docs)]  // we make a private fn public, which can trigger this lint
458        #(#attrs)*
459        pub unsafe fn #ident() #block
460    )
461    .into()
462}
463
464/// Extracts `static mut` vars from the beginning of the given statements
465fn extract_static_muts(
466    stmts: impl IntoIterator<Item = Stmt>,
467) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
468    let mut istmts = stmts.into_iter();
469
470    let mut seen = HashSet::new();
471    let mut statics = vec![];
472    let mut stmts = vec![];
473    for stmt in istmts.by_ref() {
474        match stmt {
475            Stmt::Item(Item::Static(var)) => match var.mutability {
476                StaticMutability::Mut(_) => {
477                    if seen.contains(&var.ident) {
478                        return Err(parse::Error::new(
479                            var.ident.span(),
480                            format!("the name `{}` is defined multiple times", var.ident),
481                        ));
482                    }
483
484                    seen.insert(var.ident.clone());
485                    statics.push(var);
486                }
487                StaticMutability::None => {
488                    stmts.push(Stmt::Item(Item::Static(var)));
489                }
490                _ => unimplemented!(), // `StaticMutability` is `#[non_exhaustive]`
491            },
492            _ => {
493                stmts.push(stmt);
494                break;
495            }
496        }
497    }
498
499    stmts.extend(istmts);
500
501    Ok((statics, stmts))
502}
503
504fn extract_cfgs(attrs: &[Attribute]) -> (Vec<&Attribute>, Vec<&Attribute>) {
505    let mut cfgs = vec![];
506    let mut not_cfgs = vec![];
507
508    for attr in attrs {
509        if eq(attr, "cfg") {
510            cfgs.push(attr);
511        } else {
512            not_cfgs.push(attr);
513        }
514    }
515
516    (cfgs, not_cfgs)
517}
518
519enum WhiteListCaller {
520    Entry,
521    Exception,
522    Interrupt,
523    PreInit,
524}
525
526fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
527    let whitelist = &[
528        "doc",
529        "link_section",
530        "cfg",
531        "allow",
532        "warn",
533        "deny",
534        "forbid",
535        "cold",
536        "ram",
537    ];
538
539    'o: for attr in attrs {
540        for val in whitelist {
541            if eq(attr, val) {
542                continue 'o;
543            }
544        }
545
546        let err_str = match caller {
547            WhiteListCaller::Entry => "this attribute is not allowed on a xtensa-lx-rt entry point",
548            WhiteListCaller::Exception => {
549                "this attribute is not allowed on an exception handler controlled by xtensa-lx-rt"
550            }
551            WhiteListCaller::Interrupt => {
552                if eq(attr, "naked") {
553                    continue 'o;
554                }
555
556                "this attribute is not allowed on an interrupt handler controlled by xtensa-lx-rt"
557            }
558            WhiteListCaller::PreInit => {
559                "this attribute is not allowed on a pre-init controlled by xtensa-lx-rt"
560            }
561        };
562
563        return Err(parse::Error::new(attr.span(), err_str)
564            .to_compile_error()
565            .into());
566    }
567
568    Ok(())
569}
570
571/// Returns `true` if `attr.path` matches `name`
572fn eq(attr: &Attribute, name: &str) -> bool {
573    matches!(attr.style, AttrStyle::Outer) && attr.path().is_ident(name)
574}