diff options
Diffstat (limited to 'cortex-m-rt/macros/src/lib.rs')
-rw-r--r-- | cortex-m-rt/macros/src/lib.rs | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs new file mode 100644 index 0000000..817e9a1 --- /dev/null +++ b/cortex-m-rt/macros/src/lib.rs @@ -0,0 +1,654 @@ +//! Internal implementation details of `cortex-m-rt`. +//! +//! Do not use this crate directly. + +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use std::collections::HashSet; +use std::iter; +use syn::{ + parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, FnArg, Ident, Item, ItemFn, + ItemStatic, ReturnType, Stmt, Type, Visibility, +}; + +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f = parse_macro_input!(input as ItemFn); + + // check the function signature + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => match **ty { + Type::Never(_) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + f.span(), + "`#[entry]` function must have signature `[unsafe] fn() -> !`", + ) + .to_compile_error() + .into(); + } + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + // XXX should we blacklist other attributes? + let (statics, stmts) = match extract_static_muts(f.block.stmts) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + f.sig.inputs.extend(statics.iter().map(|statik| { + let ident = &statik.ident; + let ty = &statik.ty; + let attrs = &statik.attrs; + + // Note that we use an explicit `'static` lifetime for the entry point arguments. This makes + // it more flexible, and is sound here, since the entry will not be called again, ever. + syn::parse::<FnArg>( + quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(), + ) + .unwrap() + })); + f.block.stmts = stmts; + + let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let ident = &f.sig.ident; + + let resource_args = statics + .iter() + .map(|statik| { + let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone()); + let ident = &statik.ident; + let ty = &statik.ty; + let expr = &statik.expr; + quote! { + #(#cfgs)* + { + #(#attrs)* + static mut #ident: #ty = #expr; + &mut #ident + } + } + }) + .collect::<Vec<_>>(); + + if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) { + return error; + } + + let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); + + quote!( + #(#cfgs)* + #(#attrs)* + #[doc(hidden)] + #[export_name = "main"] + pub unsafe extern "C" fn #tramp_ident() { + #ident( + #(#resource_args),* + ) + } + + #f + ) + .into() +} + +#[derive(Debug, PartialEq)] +enum Exception { + DefaultHandler, + HardFault, + NonMaskableInt, + Other, +} + +#[proc_macro_attribute] +pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f = parse_macro_input!(input as ItemFn); + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) { + return error; + } + + let fspan = f.span(); + let ident = f.sig.ident.clone(); + + let ident_s = ident.to_string(); + let exn = match &*ident_s { + "DefaultHandler" => Exception::DefaultHandler, + "HardFault" => Exception::HardFault, + "NonMaskableInt" => Exception::NonMaskableInt, + // NOTE that at this point we don't check if the exception is available on the target (e.g. + // MemoryManagement is not available on Cortex-M0) + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" | "SysTick" => Exception::Other, + _ => { + return parse::Error::new(ident.span(), "This is not a valid exception name") + .to_compile_error() + .into(); + } + }; + + if f.sig.unsafety.is_none() { + match exn { + Exception::DefaultHandler | Exception::HardFault | Exception::NonMaskableInt => { + // These are unsafe to define. + let name = if exn == Exception::DefaultHandler { + format!("`DefaultHandler`") + } else { + format!("`{:?}` handler", exn) + }; + return parse::Error::new(ident.span(), format_args!("defining a {} is unsafe and requires an `unsafe fn` (see the cortex-m-rt docs)", name)) + .to_compile_error() + .into(); + } + Exception::Other => {} + } + } + + // Emit a reference to the `Exception` variant corresponding to our exception. + // This will fail compilation when the target doesn't have that exception. + let assertion = match exn { + Exception::Other => { + quote! { + const _: () = { + let _ = cortex_m_rt::Exception::#ident; + }; + } + } + _ => quote!(), + }; + + let handler = match exn { + Exception::DefaultHandler => { + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.len() == 1 + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + fspan, + "`DefaultHandler` must have signature `unsafe fn(i16) [-> !]`", + ) + .to_compile_error() + .into(); + } + + f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let ident = &f.sig.ident; + + let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); + + quote!( + #(#cfgs)* + #(#attrs)* + #[doc(hidden)] + #[export_name = #ident_s] + pub unsafe extern "C" fn #tramp_ident() { + extern crate core; + + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + + let irqn = unsafe { (core::ptr::read_volatile(SCB_ICSR) & 0x1FF) as i16 - 16 }; + + #ident(irqn) + } + + #f + ) + } + Exception::HardFault => { + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.len() == 1 + && match &f.sig.inputs[0] { + FnArg::Typed(arg) => match arg.ty.as_ref() { + Type::Reference(r) => r.lifetime.is_none() && r.mutability.is_none(), + _ => false, + }, + _ => false, + } + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => match **ty { + Type::Never(_) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + fspan, + "`HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`", + ) + .to_compile_error() + .into(); + } + + f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let ident = &f.sig.ident; + + let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); + + quote!( + #(#cfgs)* + #(#attrs)* + #[doc(hidden)] + #[export_name = "HardFault"] + // Only emit link_section when building for embedded targets, + // because some hosted platforms (used to check the build) + // cannot handle the long link section names. + #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] + pub unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) { + #ident(frame) + } + + #f + ) + } + Exception::NonMaskableInt | Exception::Other => { + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + fspan, + "`#[exception]` handlers other than `DefaultHandler` and `HardFault` must have \ + signature `[unsafe] fn() [-> !]`", + ) + .to_compile_error() + .into(); + } + + let (statics, stmts) = match extract_static_muts(f.block.stmts) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + f.sig.inputs.extend(statics.iter().map(|statik| { + let ident = &statik.ident; + let ty = &statik.ty; + let attrs = &statik.attrs; + syn::parse::<FnArg>( + quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into(), + ) + .unwrap() + })); + f.block.stmts = iter::once( + syn::parse2(quote! {{ + // check that this exception actually exists + exception::#ident; + }}) + .unwrap(), + ) + .chain(stmts) + .collect(); + + let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let ident = &f.sig.ident; + + let resource_args = statics + .iter() + .map(|statik| { + let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone()); + let ident = &statik.ident; + let ty = &statik.ty; + let expr = &statik.expr; + quote! { + #(#cfgs)* + { + #(#attrs)* + static mut #ident: #ty = #expr; + &mut #ident + } + } + }) + .collect::<Vec<_>>(); + + let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); + + quote!( + #(#cfgs)* + #(#attrs)* + #[doc(hidden)] + #[export_name = #ident_s] + pub unsafe extern "C" fn #tramp_ident() { + #ident( + #(#resource_args),* + ) + } + + #f + ) + } + }; + + quote!( + #assertion + #handler + ) + .into() +} + +#[proc_macro_attribute] +pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + let fspan = f.span(); + let ident = f.sig.ident.clone(); + let ident_s = ident.to_string(); + + // XXX should we blacklist other attributes? + + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + fspan, + "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", + ) + .to_compile_error() + .into(); + } + + let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + f.sig.inputs.extend(statics.iter().map(|statik| { + let ident = &statik.ident; + let ty = &statik.ty; + let attrs = &statik.attrs; + syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into()) + .unwrap() + })); + f.block.stmts = iter::once( + syn::parse2(quote! {{ + // Check that this interrupt actually exists + interrupt::#ident; + }}) + .unwrap(), + ) + .chain(stmts) + .collect(); + + let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let ident = &f.sig.ident; + + let resource_args = statics + .iter() + .map(|statik| { + let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone()); + let ident = &statik.ident; + let ty = &statik.ty; + let expr = &statik.expr; + quote! { + #(#cfgs)* + { + #(#attrs)* + static mut #ident: #ty = #expr; + &mut #ident + } + } + }) + .collect::<Vec<_>>(); + + if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) { + return error; + } + + let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); + + quote!( + #(#cfgs)* + #(#attrs)* + #[doc(hidden)] + #[export_name = #ident_s] + pub unsafe extern "C" fn #tramp_ident() { + #ident( + #(#resource_args),* + ) + } + + #f + ) + .into() +} + +#[proc_macro_attribute] +pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream { + let f = parse_macro_input!(input as ItemFn); + + // check the function signature + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.unsafety.is_some() + && f.sig.abi.is_none() + && f.sig.inputs.is_empty() + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + _ => false, + }, + }; + + if !valid_signature { + return parse::Error::new( + f.span(), + "`#[pre_init]` function must have signature `unsafe fn()`", + ) + .to_compile_error() + .into(); + } + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) { + return error; + } + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let ident = f.sig.ident; + let block = f.block; + + quote!( + #[export_name = "__pre_init"] + #[allow(missing_docs)] // we make a private fn public, which can trigger this lint + #(#attrs)* + pub unsafe fn #ident() #block + ) + .into() +} + +/// Extracts `static mut` vars from the beginning of the given statements +fn extract_static_muts( + stmts: impl IntoIterator<Item = Stmt>, +) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> { + let mut istmts = stmts.into_iter(); + + let mut seen = HashSet::new(); + let mut statics = vec![]; + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Item(Item::Static(var)) => { + if var.mutability.is_some() { + if seen.contains(&var.ident) { + return Err(parse::Error::new( + var.ident.span(), + format!("the name `{}` is defined multiple times", var.ident), + )); + } + + seen.insert(var.ident.clone()); + statics.push(var); + } else { + stmts.push(Stmt::Item(Item::Static(var))); + } + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + Ok((statics, stmts)) +} + +fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) { + let mut cfgs = vec![]; + let mut not_cfgs = vec![]; + + for attr in attrs { + if eq(&attr, "cfg") { + cfgs.push(attr); + } else { + not_cfgs.push(attr); + } + } + + (cfgs, not_cfgs) +} + +enum WhiteListCaller { + Entry, + Exception, + Interrupt, + PreInit, +} + +fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> { + let whitelist = &[ + "doc", + "link_section", + "cfg", + "allow", + "warn", + "deny", + "forbid", + "cold", + ]; + + 'o: for attr in attrs { + for val in whitelist { + if eq(&attr, &val) { + continue 'o; + } + } + + let err_str = match caller { + WhiteListCaller::Entry => "this attribute is not allowed on a cortex-m-rt entry point", + WhiteListCaller::Exception => { + "this attribute is not allowed on an exception handler controlled by cortex-m-rt" + } + WhiteListCaller::Interrupt => { + "this attribute is not allowed on an interrupt handler controlled by cortex-m-rt" + } + WhiteListCaller::PreInit => { + "this attribute is not allowed on a pre-init controlled by cortex-m-rt" + } + }; + + return Err(parse::Error::new(attr.span(), &err_str) + .to_compile_error() + .into()); + } + + Ok(()) +} + +/// Returns `true` if `attr.path` matches `name` +fn eq(attr: &Attribute, name: &str) -> bool { + attr.style == AttrStyle::Outer && attr.path.is_ident(name) +} |