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 darling::ast::NestedMeta;
12use proc_macro::TokenStream;
13use proc_macro2::Span;
14use quote::quote;
15use syn::{
16    AttrStyle,
17    Attribute,
18    FnArg,
19    Ident,
20    Item,
21    ItemFn,
22    ItemStatic,
23    ReturnType,
24    StaticMutability,
25    Stmt,
26    Type,
27    Visibility,
28    parse,
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        && 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.clone());
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.clone());
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        && 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.clone());
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 NestedMeta::parse_meta_list(args.into()) {
215        Ok(v) => v,
216        Err(e) => {
217            return TokenStream::from(darling::Error::from(e).write_errors());
218        }
219    };
220
221    if attr_args.len() > 1 {
222        return parse::Error::new(
223            Span::call_site(),
224            "This attribute accepts zero or 1 arguments",
225        )
226        .to_compile_error()
227        .into();
228    }
229
230    let mut level = 1;
231
232    if attr_args.len() == 1 {
233        match &attr_args[0] {
234            NestedMeta::Lit(syn::Lit::Int(lit_int)) => match lit_int.base10_parse::<u32>() {
235                Ok(x) => level = x,
236                Err(_) => {
237                    return parse::Error::new(
238                        Span::call_site(),
239                        "This attribute accepts an integer attribute",
240                    )
241                    .to_compile_error()
242                    .into();
243                }
244            },
245            _ => {
246                return parse::Error::new(
247                    Span::call_site(),
248                    "This attribute accepts an integer attribute",
249                )
250                .to_compile_error()
251                .into();
252            }
253        }
254    }
255
256    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
257        return error;
258    }
259
260    let naked = f.attrs.iter().any(|x| eq(x, "naked"));
261
262    let ident_s = if naked {
263        format!("__naked_level_{}_interrupt", level)
264    } else {
265        format!("__level_{}_interrupt", level)
266    };
267
268    if naked && !(2..=7).contains(&level) {
269        return parse::Error::new(
270            f.span(),
271            "`#[naked]` `#[interrupt]` handlers must have interrupt level >=2 and <=7",
272        )
273        .to_compile_error()
274        .into();
275    } else if !naked && !(1..=7).contains(&level) {
276        return parse::Error::new(
277            f.span(),
278            "`#[interrupt]` handlers must have interrupt level >=1 and <=7",
279        )
280        .to_compile_error()
281        .into();
282    }
283
284    let valid_signature = f.sig.constness.is_none()
285        && f.vis == Visibility::Inherited
286        && f.sig.abi.is_none()
287        && ((!naked && f.sig.inputs.len() <= 2) || (naked && f.sig.inputs.is_empty()))
288        && f.sig.generics.params.is_empty()
289        && f.sig.generics.where_clause.is_none()
290        && f.sig.variadic.is_none()
291        && match f.sig.output {
292            ReturnType::Default => true,
293            ReturnType::Type(_, ref ty) => match **ty {
294                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
295                Type::Never(..) => true,
296                _ => false,
297            },
298        };
299
300    if !valid_signature {
301        if naked {
302            return parse::Error::new(
303                f.span(),
304                "`#[naked]` `#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
305            )
306            .to_compile_error()
307            .into();
308        } else {
309            return parse::Error::new(
310                f.span(),
311                "`#[interrupt]` handlers must have signature `[unsafe] fn([u32[, Context]]) [-> !]`",
312            )
313            .to_compile_error()
314            .into();
315        }
316    }
317
318    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
319        Err(e) => return e.to_compile_error().into(),
320        Ok(x) => x,
321    };
322
323    let inputs = f.sig.inputs.clone();
324
325    let args = inputs.iter().map(|arg| match arg {
326        syn::FnArg::Typed(x) => {
327            let pat = &*x.pat;
328            quote!(#pat)
329        }
330        _ => quote!(#arg),
331    });
332
333    f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site());
334    f.sig.inputs.extend(statics.iter().map(|statik| {
335        let ident = &statik.ident;
336        let ty = &statik.ty;
337        let attrs = &statik.attrs;
338        syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
339            .unwrap()
340    }));
341    f.block.stmts = stmts;
342
343    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
344    let ident = &f.sig.ident;
345
346    let resource_args = statics
347        .iter()
348        .map(|statik| {
349            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
350            let ident = &statik.ident;
351            let ty = &statik.ty;
352            let expr = &statik.expr;
353            quote! {
354                #(#cfgs)*
355                {
356                    #(#attrs)*
357                    static mut #ident: #ty = #expr;
358                    &mut #ident
359                }
360            }
361        })
362        .collect::<Vec<_>>();
363
364    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
365
366    if naked {
367        quote!(
368            #(#cfgs)*
369            #(#attrs)*
370            #[doc(hidden)]
371            #[export_name = #ident_s]
372            pub unsafe extern "C" fn #tramp_ident() {
373                #ident(
374                    #(#resource_args),*
375                )
376            }
377
378            #[doc(hidden)]
379            #[allow(clippy::inline_always)]
380            #[inline(always)]
381            #f
382        )
383        .into()
384    } else {
385        quote!(
386            #(#cfgs)*
387            #(#attrs)*
388            #[doc(hidden)]
389            #[export_name = #ident_s]
390            pub unsafe extern "C" fn #tramp_ident(
391                level: u32,
392                frame: xtensa_lx_rt::exception::Context
393            ) {
394                    #ident(#(#args),*
395                    #(#resource_args),*
396                )
397            }
398
399            #[doc(hidden)]
400            #[allow(clippy::inline_always)]
401            #[inline(always)]
402            #f
403        )
404        .into()
405    }
406}
407
408/// Marks a function as the pre_init function. This function is called before
409/// main and *before the memory is initialized*.
410#[proc_macro_attribute]
411pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
412    let f = parse_macro_input!(input as ItemFn);
413
414    // check the function signature
415    let valid_signature = f.sig.constness.is_none()
416        && f.vis == Visibility::Inherited
417        && f.sig.unsafety.is_some()
418        && f.sig.abi.is_none()
419        && f.sig.inputs.is_empty()
420        && f.sig.generics.params.is_empty()
421        && f.sig.generics.where_clause.is_none()
422        && f.sig.variadic.is_none()
423        && match f.sig.output {
424            ReturnType::Default => true,
425            ReturnType::Type(_, ref ty) => match **ty {
426                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
427                _ => false,
428            },
429        };
430
431    if !valid_signature {
432        return parse::Error::new(
433            f.span(),
434            "`#[pre_init]` function must have signature `unsafe fn()`",
435        )
436        .to_compile_error()
437        .into();
438    }
439
440    if !args.is_empty() {
441        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
442            .to_compile_error()
443            .into();
444    }
445
446    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) {
447        return error;
448    }
449
450    let attrs = f.attrs;
451    let ident = f.sig.ident;
452    let block = f.block;
453
454    quote!(
455        #[export_name = "__pre_init"]
456        #[allow(missing_docs)]  // we make a private fn public, which can trigger this lint
457        #(#attrs)*
458        pub unsafe fn #ident() #block
459    )
460    .into()
461}
462
463/// Extracts `static mut` vars from the beginning of the given statements
464fn extract_static_muts(
465    stmts: impl IntoIterator<Item = Stmt>,
466) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
467    let mut istmts = stmts.into_iter();
468
469    let mut seen = HashSet::new();
470    let mut statics = vec![];
471    let mut stmts = vec![];
472    for stmt in istmts.by_ref() {
473        match stmt {
474            Stmt::Item(Item::Static(var)) => match var.mutability {
475                StaticMutability::Mut(_) => {
476                    if seen.contains(&var.ident) {
477                        return Err(parse::Error::new(
478                            var.ident.span(),
479                            format!("the name `{}` is defined multiple times", var.ident),
480                        ));
481                    }
482
483                    seen.insert(var.ident.clone());
484                    statics.push(var);
485                }
486                StaticMutability::None => {
487                    stmts.push(Stmt::Item(Item::Static(var)));
488                }
489                _ => unimplemented!(), // `StaticMutability` is `#[non_exhaustive]`
490            },
491            _ => {
492                stmts.push(stmt);
493                break;
494            }
495        }
496    }
497
498    stmts.extend(istmts);
499
500    Ok((statics, stmts))
501}
502
503fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
504    let mut cfgs = vec![];
505    let mut not_cfgs = vec![];
506
507    for attr in attrs {
508        if eq(&attr, "cfg") {
509            cfgs.push(attr);
510        } else {
511            not_cfgs.push(attr);
512        }
513    }
514
515    (cfgs, not_cfgs)
516}
517
518enum WhiteListCaller {
519    Entry,
520    Exception,
521    Interrupt,
522    PreInit,
523}
524
525fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
526    let whitelist = &[
527        "doc",
528        "link_section",
529        "cfg",
530        "allow",
531        "warn",
532        "deny",
533        "forbid",
534        "cold",
535        "ram",
536    ];
537
538    'o: for attr in attrs {
539        for val in whitelist {
540            if eq(attr, val) {
541                continue 'o;
542            }
543        }
544
545        let err_str = match caller {
546            WhiteListCaller::Entry => "this attribute is not allowed on a xtensa-lx-rt entry point",
547            WhiteListCaller::Exception => {
548                "this attribute is not allowed on an exception handler controlled by xtensa-lx-rt"
549            }
550            WhiteListCaller::Interrupt => {
551                if eq(attr, "naked") {
552                    continue 'o;
553                }
554
555                "this attribute is not allowed on an interrupt handler controlled by xtensa-lx-rt"
556            }
557            WhiteListCaller::PreInit => {
558                "this attribute is not allowed on a pre-init controlled by xtensa-lx-rt"
559            }
560        };
561
562        return Err(parse::Error::new(attr.span(), err_str)
563            .to_compile_error()
564            .into());
565    }
566
567    Ok(())
568}
569
570/// Returns `true` if `attr.path` matches `name`
571fn eq(attr: &Attribute, name: &str) -> bool {
572    attr.style == AttrStyle::Outer && attr.path().is_ident(name)
573}