mips_rt_macros/
lib.rs

1//! Internal implementation details of `mips-rt`.
2//!
3//! Do not use this crate directly.
4//! Almost identical to `cortex_m_rt_macros`
5
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::quote;
9use std::collections::HashSet;
10use std::iter;
11use syn::{
12    parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, FnArg, Ident, Item, ItemFn,
13    ItemStatic, ReturnType, Stmt, Type, Visibility,
14};
15
16#[proc_macro_attribute]
17pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
18    let mut f = parse_macro_input!(input as ItemFn);
19
20    // check the function signature
21    let valid_signature = f.sig.constness.is_none()
22        && f.vis == Visibility::Inherited
23        && f.sig.abi.is_none()
24        && f.sig.inputs.is_empty()
25        && f.sig.generics.params.is_empty()
26        && f.sig.generics.where_clause.is_none()
27        && f.sig.variadic.is_none()
28        && match f.sig.output {
29            ReturnType::Default => false,
30            ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
31        };
32
33    if !valid_signature {
34        return parse::Error::new(
35            f.span(),
36            "`#[entry]` function must have signature `[unsafe] fn() -> !`",
37        )
38        .to_compile_error()
39        .into();
40    }
41
42    if !args.is_empty() {
43        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
44            .to_compile_error()
45            .into();
46    }
47
48    // XXX should we blacklist other attributes?
49    let (statics, stmts) = match extract_static_muts(f.block.stmts) {
50        Err(e) => return e.to_compile_error().into(),
51        Ok(x) => x,
52    };
53
54    f.sig.ident = Ident::new(&format!("__mips_rt_{}", f.sig.ident), Span::call_site());
55    f.sig.inputs.extend(statics.iter().map(|statik| {
56        let ident = &statik.ident;
57        let ty = &statik.ty;
58        let attrs = &statik.attrs;
59
60        // Note that we use an explicit `'static` lifetime for the entry point arguments. This makes
61        // it more flexible, and is sound here, since the entry will not be called again, ever.
62        syn::parse::<FnArg>(
63            quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(),
64        )
65        .unwrap()
66    }));
67    f.block.stmts = stmts;
68
69    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
70    let ident = &f.sig.ident;
71
72    let resource_args = statics
73        .iter()
74        .map(|statik| {
75            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
76            let ident = &statik.ident;
77            let ty = &statik.ty;
78            let expr = &statik.expr;
79            quote! {
80                #(#cfgs)*
81                {
82                    #(#attrs)*
83                    static mut #ident: #ty = #expr;
84                    unsafe { &mut #ident }
85                }
86            }
87        })
88        .collect::<Vec<_>>();
89
90    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) {
91        return error;
92    }
93
94    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
95
96    quote!(
97        #(#cfgs)*
98        #(#attrs)*
99        #[doc(hidden)]
100        #[export_name = "main"]
101        pub unsafe extern "C" fn #tramp_ident() {
102            #[allow(static_mut_refs)]
103            #ident(
104                #(#resource_args),*
105            )
106        }
107
108        #f
109    )
110    .into()
111}
112
113#[proc_macro_attribute]
114pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
115    let mut f = parse_macro_input!(input as ItemFn);
116
117    if !args.is_empty() {
118        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
119            .to_compile_error()
120            .into();
121    }
122
123    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) {
124        return error;
125    }
126
127    let fspan = f.span();
128    let ident = f.sig.ident.clone();
129
130    let ident_s = ident.to_string();
131    let export_name = match &*ident_s {
132        "general_exception" => "_general_exception_handler",
133        "nmi" => "_nmi_handler",
134        _ => {
135            return parse::Error::new(ident.span(), "This is not a valid exception name")
136                .to_compile_error()
137                .into();
138        }
139    };
140
141    let valid_signature = f.sig.constness.is_none()
142        && f.vis == Visibility::Inherited
143        && f.sig.abi.is_none()
144        && f.sig.inputs.len() == 2
145        && f.sig.generics.params.is_empty()
146        && f.sig.generics.where_clause.is_none()
147        && f.sig.variadic.is_none()
148        && match f.sig.output {
149            ReturnType::Default => true,
150            ReturnType::Type(_, ref ty) => match **ty {
151                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
152                Type::Never(..) => true,
153                _ => false,
154            },
155        };
156
157    if !valid_signature {
158        return parse::Error::new(
159            fspan,
160            "`#[exception]`exception handlers must have signature `unsafe fn(u32, u32) [-> !]`",
161        )
162        .to_compile_error()
163        .into();
164    }
165
166    f.sig.ident = Ident::new(&format!("__mips_rt_{}", f.sig.ident), Span::call_site());
167    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
168    let ident = &f.sig.ident;
169
170    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
171
172    quote!(
173        #(#cfgs)*
174        #(#attrs)*
175        #[doc(hidden)]
176        #[export_name = #export_name]
177        pub unsafe extern "C" fn #tramp_ident(cp0_cause: u32, cp0_status: u32) {
178            #ident(cp0_cause, cp0_status)
179        }
180
181        #f
182    )
183    .into()
184}
185
186#[proc_macro_attribute]
187pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
188    let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
189
190    if !args.is_empty() {
191        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
192            .to_compile_error()
193            .into();
194    }
195
196    let fspan = f.span();
197    let ident = f.sig.ident.clone();
198    let ident_s = ident.to_string();
199
200    // XXX should we blacklist other attributes?
201
202    let valid_signature = f.sig.constness.is_none()
203        && f.vis == Visibility::Inherited
204        && f.sig.abi.is_none()
205        && f.sig.inputs.is_empty()
206        && f.sig.generics.params.is_empty()
207        && f.sig.generics.where_clause.is_none()
208        && f.sig.variadic.is_none()
209        && match f.sig.output {
210            ReturnType::Default => true,
211            ReturnType::Type(_, ref ty) => match **ty {
212                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
213                Type::Never(..) => true,
214                _ => false,
215            },
216        };
217
218    if !valid_signature {
219        return parse::Error::new(
220            fspan,
221            "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
222        )
223        .to_compile_error()
224        .into();
225    }
226
227    let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
228        Err(e) => return e.to_compile_error().into(),
229        Ok(x) => x,
230    };
231
232    f.sig.ident = Ident::new(&format!("__mips_rt_{}", f.sig.ident), Span::call_site());
233    f.sig.inputs.extend(statics.iter().map(|statik| {
234        let ident = &statik.ident;
235        let ty = &statik.ty;
236        let attrs = &statik.attrs;
237        syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
238            .unwrap()
239    }));
240    f.block.stmts = iter::once(
241        syn::parse2(quote! {{
242            // Check that this interrupt actually exists
243            interrupt::Interrupt::#ident;
244        }})
245        .unwrap(),
246    )
247    .chain(stmts)
248    .collect();
249
250    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
251    let ident = &f.sig.ident;
252
253    let resource_args = statics
254        .iter()
255        .map(|statik| {
256            let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
257            let ident = &statik.ident;
258            let ty = &statik.ty;
259            let expr = &statik.expr;
260            quote! {
261                #(#cfgs)*
262                {
263                    #(#attrs)*
264                    static mut #ident: #ty = #expr;
265                    unsafe { &mut #ident }
266                }
267            }
268        })
269        .collect::<Vec<_>>();
270
271    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
272        return error;
273    }
274
275    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
276
277    quote!(
278        #(#cfgs)*
279        #(#attrs)*
280        #[doc(hidden)]
281        #[export_name = #ident_s]
282        pub unsafe extern "C" fn #tramp_ident() {
283            #[allow(static_mut_refs)]
284            #ident(
285                #(#resource_args),*
286            )
287        }
288
289        #f
290    )
291    .into()
292}
293
294#[proc_macro_attribute]
295pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
296    let f = parse_macro_input!(input as ItemFn);
297
298    // check the function signature
299    let valid_signature = f.sig.constness.is_none()
300        && f.vis == Visibility::Inherited
301        && f.sig.unsafety.is_some()
302        && f.sig.abi.is_none()
303        && f.sig.inputs.is_empty()
304        && f.sig.generics.params.is_empty()
305        && f.sig.generics.where_clause.is_none()
306        && f.sig.variadic.is_none()
307        && match f.sig.output {
308            ReturnType::Default => true,
309            ReturnType::Type(_, ref ty) => match **ty {
310                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
311                _ => false,
312            },
313        };
314
315    if !valid_signature {
316        return parse::Error::new(
317            f.span(),
318            "`#[pre_init]` function must have signature `unsafe fn()`",
319        )
320        .to_compile_error()
321        .into();
322    }
323
324    if !args.is_empty() {
325        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
326            .to_compile_error()
327            .into();
328    }
329
330    if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) {
331        return error;
332    }
333
334    // XXX should we blacklist other attributes?
335    let attrs = f.attrs;
336    let ident = f.sig.ident;
337    let block = f.block;
338
339    quote!(
340        #[export_name = "__pre_init"]
341        #[allow(missing_docs)]  // we make a private fn public, which can trigger this lint
342        #(#attrs)*
343        pub unsafe fn #ident() #block
344    )
345    .into()
346}
347
348/// Extracts `static mut` vars from the beginning of the given statements
349fn extract_static_muts(
350    stmts: impl IntoIterator<Item = Stmt>,
351) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
352    let mut istmts = stmts.into_iter();
353
354    let mut seen = HashSet::new();
355    let mut statics = vec![];
356    let mut stmts = vec![];
357    for stmt in istmts.by_ref() {
358        match stmt {
359            Stmt::Item(Item::Static(var)) => match var.mutability {
360                syn::StaticMutability::Mut(_) => {
361                    if seen.contains(&var.ident) {
362                        return Err(parse::Error::new(
363                            var.ident.span(),
364                            format!("the name `{}` is defined multiple times", var.ident),
365                        ));
366                    }
367
368                    seen.insert(var.ident.clone());
369                    statics.push(var);
370                }
371                _ => stmts.push(Stmt::Item(Item::Static(var))),
372            },
373            _ => {
374                stmts.push(stmt);
375                break;
376            }
377        }
378    }
379
380    stmts.extend(istmts);
381
382    Ok((statics, stmts))
383}
384
385fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
386    let mut cfgs = vec![];
387    let mut not_cfgs = vec![];
388
389    for attr in attrs {
390        if eq(&attr, "cfg") {
391            cfgs.push(attr);
392        } else {
393            not_cfgs.push(attr);
394        }
395    }
396
397    (cfgs, not_cfgs)
398}
399
400enum WhiteListCaller {
401    Entry,
402    Exception,
403    Interrupt,
404    PreInit,
405}
406
407fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
408    let whitelist = &[
409        "doc",
410        "link_section",
411        "cfg",
412        "allow",
413        "warn",
414        "deny",
415        "forbid",
416        "cold",
417        "naked",
418    ];
419
420    'o: for attr in attrs {
421        for val in whitelist {
422            if eq(attr, val) {
423                continue 'o;
424            }
425        }
426
427        let err_str = match caller {
428            WhiteListCaller::Entry => "this attribute is not allowed on a mips-rt entry point",
429            WhiteListCaller::Exception => {
430                 "this attribute is not allowed on an exception handler controlled by mips-rt"
431            }
432            WhiteListCaller::Interrupt => {
433                "this attribute is not allowed on an interrupt handler controlled by mips-rt"
434            }
435            WhiteListCaller::PreInit => {
436                "this attribute is not allowed on a pre-init controlled by mips-rt"
437            }
438        };
439
440        return Err(parse::Error::new(attr.span(), err_str)
441            .to_compile_error()
442            .into());
443    }
444
445    Ok(())
446}
447
448/// Returns `true` if `attr.path` matches `name`
449fn eq(attr: &Attribute, name: &str) -> bool {
450    attr.style == AttrStyle::Outer && attr.path().is_ident(name)
451}