~ruther/avr-device

9aef8977ef52af652dfa44bf699bd459c22968e0 — tones111 3 years ago 8c8d274
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
1 files changed, 157 insertions(+), 28 deletions(-)

M macros/src/lib.rs
M macros/src/lib.rs => macros/src/lib.rs +157 -28
@@ 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::<syn::FnArg>(
            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::<Vec<_>>();

    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::<syn::FnArg>(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::<Vec<_>>();

    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<Item = syn::Stmt>,
) -> Result<(Vec<syn::ItemStatic>, Vec<syn::Stmt>), 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<syn::Attribute>) -> (Vec<syn::Attribute>, Vec<syn::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)
}

/// 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)
}

Do not follow this link