esp32_hal_proc_macros/
lib.rs1#![deny(warnings)]
10#![allow(unused_braces)]
11#![feature(proc_macro_diagnostic)]
12
13extern crate proc_macro;
14
15use darling::FromMeta;
16use proc_macro::Span;
17use proc_macro::TokenStream;
18use quote::quote;
19use std::collections::HashSet;
20use std::iter;
21use syn::{
22 parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, AttributeArgs, FnArg, Ident,
23 Item, ItemFn, ItemStatic, Meta::Path, ReturnType, Stmt, Type, Visibility,
24};
25
26fn check_ram_function(_func: &syn::ItemFn) {
31 }
33
34#[derive(Debug, Default, FromMeta)]
35#[darling(default)]
36struct RamArgs {
37 rtc_fast: bool,
38 rtc_slow: bool,
39 external: bool,
40 uninitialized: bool,
41 zeroed: bool,
42}
43
44#[proc_macro_attribute]
53pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
54 let attr_args = parse_macro_input!(args as AttributeArgs);
55
56 let RamArgs {
57 rtc_fast,
58 rtc_slow,
59 external,
60 uninitialized,
61 zeroed,
62 } = match FromMeta::from_list(&attr_args) {
63 Ok(v) => v,
64 Err(e) => {
65 return e.write_errors().into();
66 }
67 };
68
69 if rtc_slow && rtc_fast {
70 Span::call_site()
71 .error("Only one of rtc_slow and rtc_fast is allowed")
72 .emit();
73 }
74
75 if rtc_slow && rtc_fast {
76 Span::call_site()
77 .error("Only one of uninitialized and zeroed")
78 .emit();
79 }
80
81 if external && cfg!(not(feature = "external_ram")) {
82 Span::call_site()
83 .error("External ram support not enabled")
84 .emit();
85 }
86
87 let section_name_data = if rtc_slow {
88 if uninitialized {
89 ".rtc_slow.noinit"
90 } else if zeroed {
91 ".rtc_slow.bss"
92 } else {
93 ".rtc_slow.data"
94 }
95 } else if rtc_fast {
96 if uninitialized {
97 ".rtc_fast.noinit"
98 } else if zeroed {
99 ".rtc_fast.bss"
100 } else {
101 ".rtc_fast.data"
102 }
103 } else if external {
104 if uninitialized {
105 ".external.noinit"
106 } else if zeroed {
107 ".external.bss"
108 } else {
109 ".external.data"
110 }
111 } else {
112 if uninitialized {
113 ".noinit"
114 } else {
115 ".data"
116 }
117 };
118
119 let section_name_text = if rtc_slow {
120 ".rtc_slow.text"
121 } else if rtc_fast {
122 ".rtc_fast.text"
123 } else if external {
124 ".invalid"
125 } else {
126 ".rwtext"
127 };
128
129 let item: syn::Item = syn::parse(input).expect("failed to parse input");
130
131 let section: proc_macro2::TokenStream;
132 match item {
133 Item::Static(ref _struct_item) => section = quote! {#[link_section=#section_name_data]},
134 Item::Const(ref _struct_item) => section = quote! {#[link_section=#section_name_data]},
135 Item::Fn(ref function_item) => {
136 if zeroed {
137 Span::call_site()
138 .error("Zeroed is not applicable to functions")
139 .emit();
140 }
141 if uninitialized {
142 Span::call_site()
143 .error("Uninitialized is not applicable to functions")
144 .emit();
145 }
146 if external {
147 Span::call_site()
148 .error("External is not applicable to functions")
149 .emit();
150 }
151 check_ram_function(function_item);
152 section = quote! {
153 #[link_section=#section_name_text]
154 #[inline(never)] };
156 }
157 _ => {
158 section = quote! {};
159 item.span()
160 .unstable()
161 .error("#[ram] attribute can only be applied to functions and statics")
162 .emit();
163 }
164 }
165
166 let output = quote! {
167 #section
168 #item
169 };
170 output.into()
171}
172
173#[proc_macro_attribute]
181pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
182 let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
183
184 let attr_args = parse_macro_input!(args as AttributeArgs);
185
186 if attr_args.len() > 1 {
187 Span::call_site()
188 .error("This attribute accepts zero or 1 arguments")
189 .emit();
190 }
191
192 let ident = f.sig.ident.clone();
193 let mut ident_s = &ident.clone();
194
195 if attr_args.len() == 1 {
196 match &attr_args[0] {
197 syn::NestedMeta::Meta(Path(x)) => {
198 ident_s = x.get_ident().unwrap();
199 }
200 _ => {
201 Span::call_site()
202 .error(format!(
203 "This attribute accepts a string attribute {:?}",
204 attr_args[0]
205 ))
206 .emit();
207 }
208 }
209 }
210
211 if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
214 return error;
215 }
216
217 let valid_signature = f.sig.constness.is_none()
218 && f.vis == Visibility::Inherited
219 && f.sig.abi.is_none()
220 && f.sig.inputs.is_empty()
221 && f.sig.generics.params.is_empty()
222 && f.sig.generics.where_clause.is_none()
223 && f.sig.variadic.is_none()
224 && match f.sig.output {
225 ReturnType::Default => true,
226 ReturnType::Type(_, ref ty) => match **ty {
227 Type::Tuple(ref tuple) => tuple.elems.is_empty(),
228 Type::Never(..) => true,
229 _ => false,
230 },
231 };
232
233 if !valid_signature {
234 return parse::Error::new(
235 f.span(),
236 "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
237 )
238 .to_compile_error()
239 .into();
240 }
241
242 let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
243 Err(e) => return e.to_compile_error().into(),
244 Ok(x) => x,
245 };
246
247 f.sig.ident = Ident::new(
248 &format!("__xtensa_lx_6_{}", f.sig.ident),
249 proc_macro2::Span::call_site(),
250 );
251 f.sig.inputs.extend(statics.iter().map(|statik| {
252 let ident = &statik.ident;
253 let ty = &statik.ty;
254 let attrs = &statik.attrs;
255 syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
256 .unwrap()
257 }));
258 f.block.stmts = iter::once(
259 syn::parse2(quote! {{
260 interrupt::#ident_s;
262 }})
263 .unwrap(),
264 )
265 .chain(stmts)
266 .collect();
267
268 let tramp_ident = Ident::new(
269 &format!("{}_trampoline", f.sig.ident),
270 proc_macro2::Span::call_site(),
271 );
272 let ident = &f.sig.ident;
273
274 let resource_args = statics
275 .iter()
276 .map(|statik| {
277 let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
278 let ident = &statik.ident;
279 let ty = &statik.ty;
280 let expr = &statik.expr;
281 quote! {
282 #(#cfgs)*
283 {
284 #(#attrs)*
285 static mut #ident: #ty = #expr;
286 &mut #ident
287 }
288 }
289 })
290 .collect::<Vec<_>>();
291
292 let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
293
294 let export_name = ident_s.to_string();
295
296 quote!(
297 #(#cfgs)*
298 #(#attrs)*
299 #[doc(hidden)]
300 #[export_name = #export_name]
301 pub unsafe extern "C" fn #tramp_ident() {
302 #ident(
303 #(#resource_args),*
304 )
305 }
306
307 #[inline(always)]
308 #f
309 )
310 .into()
311}
312
313fn extract_static_muts(
315 stmts: impl IntoIterator<Item = Stmt>,
316) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
317 let mut istmts = stmts.into_iter();
318
319 let mut seen = HashSet::new();
320 let mut statics = vec![];
321 let mut stmts = vec![];
322 while let Some(stmt) = istmts.next() {
323 match stmt {
324 Stmt::Item(Item::Static(var)) => {
325 if var.mutability.is_some() {
326 if seen.contains(&var.ident) {
327 return Err(parse::Error::new(
328 var.ident.span(),
329 format!("the name `{}` is defined multiple times", var.ident),
330 ));
331 }
332
333 seen.insert(var.ident.clone());
334 statics.push(var);
335 } else {
336 stmts.push(Stmt::Item(Item::Static(var)));
337 }
338 }
339 _ => {
340 stmts.push(stmt);
341 break;
342 }
343 }
344 }
345
346 stmts.extend(istmts);
347
348 Ok((statics, stmts))
349}
350
351fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
352 let mut cfgs = vec![];
353 let mut not_cfgs = vec![];
354
355 for attr in attrs {
356 if eq(&attr, "cfg") {
357 cfgs.push(attr);
358 } else {
359 not_cfgs.push(attr);
360 }
361 }
362
363 (cfgs, not_cfgs)
364}
365
366enum WhiteListCaller {
367 Interrupt,
368}
369
370fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
371 let whitelist = &[
372 "doc",
373 "link_section",
374 "cfg",
375 "allow",
376 "warn",
377 "deny",
378 "forbid",
379 "cold",
380 "ram",
381 ];
382
383 'o: for attr in attrs {
384 for val in whitelist {
385 if eq(&attr, &val) {
386 continue 'o;
387 }
388 }
389
390 let err_str = match caller {
391 WhiteListCaller::Interrupt => {
392 "this attribute is not allowed on an interrupt handler controlled by esp32_hal"
393 }
394 };
395
396 return Err(parse::Error::new(attr.span(), &err_str)
397 .to_compile_error()
398 .into());
399 }
400
401 Ok(())
402}
403
404fn eq(attr: &Attribute, name: &str) -> bool {
406 attr.style == AttrStyle::Outer && attr.path.is_ident(name)
407}