xtensa_lx6_rt_proc_macros/
lib.rs1#![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#[proc_macro_attribute]
20pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
21 let mut f = parse_macro_input!(input as ItemFn);
22
23 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 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#[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#[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#[proc_macro_attribute]
428pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
429 let f = parse_macro_input!(input as ItemFn);
430
431 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)] #(#attrs)*
475 pub unsafe fn #ident() #block
476 )
477 .into()
478}
479
480fn 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
587fn eq(attr: &Attribute, name: &str) -> bool {
589 attr.style == AttrStyle::Outer && attr.path.is_ident(name)
590}