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