aboutsummaryrefslogtreecommitdiff
path: root/cortex-m-rt/macros/src/lib.rs
diff options
context:
space:
mode:
authorGravatar bors[bot] <26634292+bors[bot]@users.noreply.github.com> 2022-01-23 14:20:02 +0000
committerGravatar GitHub <noreply@github.com> 2022-01-23 14:20:02 +0000
commitc350114d8002d91bd71d08e7ad6ee2e960c2ed35 (patch)
treeb741c9324aca7c68fae566afdcabf34c8175a134 /cortex-m-rt/macros/src/lib.rs
parent92552c73d3b56dc86007450633950d16ebe0e495 (diff)
parent391fb7edb90131f295ae759ff780f2a4233dada2 (diff)
downloadcortex-m-c350114d8002d91bd71d08e7ad6ee2e960c2ed35.tar.gz
cortex-m-c350114d8002d91bd71d08e7ad6ee2e960c2ed35.tar.zst
cortex-m-c350114d8002d91bd71d08e7ad6ee2e960c2ed35.zip
Merge #391c-m-rt-v0.7.1
391: Merge cortex-m-rt into this repository r=thejpster a=adamgreig This PR merges the cortex-m-rt repository (with history) into this repo, inside the `cortex-m-rt` folder which is added to the workspace. The main advantage is easier combined testing of cortex-m with cortex-m-rt (including on-hardware tests e.g. #355), and in the future easier changes across the two projects. The MSRV of cortex-m-rt is bumped 1.39 -> 1.40 to align it with cortex-m itself. I've updated the CI to run the same tests and checks as before, and updated references to the old URL. If/after this is merged, I propose adding a note to the old repo's README and then archiving it. An alternative to this technique would be adding all the files in one new commit (not preserving history), if anyone thinks that would be neater. NB: This PR also adds an inline to ITM to fix a clippy hard error. For future reference, the git work was: ``` cd cortex-m-rt git filter-repo --to-subdirectory-filter cortex-m-rt cd ../cortex-m git remote add rt ../cortex-m-rt git fetch rt git merge --allow-unrelated-histories rt/master ``` Co-authored-by: bors[bot] <bors[bot]@users.noreply.github.com> Co-authored-by: Jonathan 'theJPster' Pallant <github@thejpster.org.uk> Co-authored-by: Adam Greig <adam@adamgreig.com> Co-authored-by: Jonas Schievink <jonasschievink@gmail.com> Co-authored-by: Jorge Aparicio <jorge@japaric.io> Co-authored-by: Emil Fresk <emil.fresk@gmail.com> Co-authored-by: Daniel Egger <daniel@eggers-club.de> Co-authored-by: Niklas Claesson <nicke.claesson@gmail.com> Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.com> Co-authored-by: Vadim Kaushan <admin@disasm.info>
Diffstat (limited to 'cortex-m-rt/macros/src/lib.rs')
-rw-r--r--cortex-m-rt/macros/src/lib.rs654
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)
+}