xtensa_lx_rt_proc_macros/
lib.rs1#![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#[proc_macro_attribute]
35pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
36 let mut f = parse_macro_input!(input as ItemFn);
37
38 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 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#[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#[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#[proc_macro_attribute]
412pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
413 let f = parse_macro_input!(input as ItemFn);
414
415 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)] #(#attrs)*
459 pub unsafe fn #ident() #block
460 )
461 .into()
462}
463
464fn 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!(), },
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
571fn eq(attr: &Attribute, name: &str) -> bool {
573 matches!(attr.style, AttrStyle::Outer) && attr.path().is_ident(name)
574}