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