xtensa_lx6_rt_proc_macros/
lib.rs

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