From 9aef8977ef52af652dfa44bf699bd459c22968e0 Mon Sep 17 00:00:00 2001 From: tones111 Date: Tue, 29 Jun 2021 10:43:11 -0500 Subject: [PATCH] rt: Implement cortex-m-rt-macros static mut conversion Inside the entrypoint and interrupt handlers, perform a conversion which turns `static mut`s into &mut references to the static. This is safe because exception handlers and the entrypoint are guaranteed to not be reentrant. This is the same behavior as `cortex-m-rt` where it is documented in the Rust Embedded Book [1]. [1]: https://docs.rust-embedded.org/book/start/exceptions.html --- macros/src/lib.rs | 185 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 157 insertions(+), 28 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index eb1f158..c9aa3c3 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -11,7 +11,7 @@ pub fn entry( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let f = syn::parse_macro_input!(input as syn::ItemFn); + let mut f = syn::parse_macro_input!(input as syn::ItemFn); // check the function signature let valid_signature = f.sig.constness.is_none() @@ -23,10 +23,7 @@ pub fn entry( && f.sig.variadic.is_none() && match f.sig.output { syn::ReturnType::Default => false, - syn::ReturnType::Type(_, ref ty) => match **ty { - syn::Type::Never(_) => true, - _ => false, - }, + syn::ReturnType::Type(_, ref ty) => matches!(**ty, syn::Type::Never(_)), }; if !valid_signature { @@ -47,16 +44,53 @@ pub fn entry( .into(); } + let (statics, stmts) = match extract_static_muts(f.block.stmts) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + // Rename the function so it is not callable - let ident = syn::Ident::new( - &format!("_avr_device_rt_{}", f.sig.ident), + f.sig.ident = syn::Ident::new( + &format!("__avr_device_rt_{}", f.sig.ident), + proc_macro2::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::( + quote::quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(), + ) + .unwrap() + })); + f.block.stmts = stmts; + + let tramp_ident = syn::Ident::new( + &format!("{}_trampoline", f.sig.ident), proc_macro2::Span::call_site(), ); + let ident = &f.sig.ident; - let attrs = f.attrs; - let block = f.block; - let stmts = block.stmts; - let unsafety = f.sig.unsafety; + 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::quote! { + #(#cfgs)* + { + #(#attrs)* + static mut #ident: #ty = #expr; + &mut #ident + } + } + }) + .collect::>(); quote::quote! ( #[cfg(not(any(doc, target_arch = "avr")))] @@ -66,12 +100,16 @@ pub fn entry( https://github.com/Rahix/avr-device/pull/41 for more details." ); - #(#attrs)* #[doc(hidden)] #[export_name = "main"] - pub #unsafety extern "C" fn #ident() -> ! { - #(#stmts)* + pub unsafe extern "C" fn #tramp_ident() { + #ident( + #(#resource_args),* + ) } + + #[doc(hidden)] + #f ) .into() } @@ -81,16 +119,12 @@ pub fn interrupt( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let f = syn::parse_macro_input!(input as syn::ItemFn); + let mut f: syn::ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); let args: Vec<_> = args.into_iter().collect(); let fspan = f.span(); - let ident = f.sig.ident; + let ident = f.sig.ident.clone(); let ident_s = ident.to_string(); - let attrs = f.attrs; - let block = f.block; - let stmts = block.stmts; - let unsafety = f.sig.unsafety; let chip = if let Some(tree) = args.get(0) { if let proc_macro::TokenTree::Ident(ident) = tree { @@ -137,6 +171,42 @@ pub fn interrupt( .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 = syn::Ident::new(&format!("__avr_device_rt_{}", f.sig.ident), proc_macro2::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::(quote::quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into()) + .unwrap() + })); + f.block.stmts = stmts; + + let tramp_ident = syn::Ident::new(&format!("{}_trampoline", f.sig.ident), proc_macro2::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::quote! { + #(#cfgs)* + { + #(#attrs)* + static mut #ident: #ty = #expr; + &mut #ident + } + } + }) + .collect::>(); + let vect = if let Some(v) = vector::lookup_vector(&chip, &ident_s) { v } else { @@ -149,18 +219,77 @@ pub fn interrupt( }; let vector = format!("__vector_{}", vect); let vector_ident = syn::Ident::new(&vector, proc_macro2::Span::call_site()); + let vector_ident_s = vector_ident.to_string(); quote::quote! ( - #[no_mangle] - pub unsafe extern "avr-interrupt" fn #vector_ident() { - #ident(); + #[doc(hidden)] + #[export_name = #vector_ident_s] + pub unsafe extern "avr-interrupt" fn #tramp_ident() { + #ident( + #(#resource_args),* + ) } - #(#attrs)* - #[allow(non_snake_case)] - #unsafety fn #ident() { - #(#stmts)* - } + #[doc(hidden)] + #f ) .into() } + +/// Extracts `static mut` vars from the beginning of the given statements +fn extract_static_muts( + stmts: impl IntoIterator, +) -> Result<(Vec, Vec), syn::parse::Error> { + let mut istmts = stmts.into_iter(); + + let mut seen = std::collections::HashSet::new(); + let mut statics = vec![]; + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + syn::Stmt::Item(syn::Item::Static(var)) => { + if var.mutability.is_some() { + if seen.contains(&var.ident) { + return Err(syn::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(syn::Stmt::Item(syn::Item::Static(var))); + } + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + Ok((statics, stmts)) +} + +fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { + 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) +} + +/// Returns `true` if `attr.path` matches `name` +fn eq(attr: &syn::Attribute, name: &str) -> bool { + attr.style == syn::AttrStyle::Outer && attr.path.is_ident(name) +} -- 2.49.0