diff options
Diffstat (limited to 'libgrust/rustc-lib/stdarch/crates/assert-instr-macro')
3 files changed, 251 insertions, 0 deletions
diff --git a/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/Cargo.toml b/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/Cargo.toml new file mode 100644 index 0000000..eeefca2 --- /dev/null +++ b/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "assert-instr-macro" +version = "0.1.0" +authors = ["Alex Crichton <alex@alexcrichton.com>"] + +[lib] +proc-macro = true +test = false + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = ["full"] } diff --git a/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/build.rs b/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/build.rs new file mode 100644 index 0000000..45a8684 --- /dev/null +++ b/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/build.rs @@ -0,0 +1,13 @@ +use std::env; + +fn main() { +    println!("cargo:rerun-if-changed=build.rs"); +    let opt_level = env::var("OPT_LEVEL") +        .ok() +        .and_then(|s| s.parse().ok()) +        .unwrap_or(0); +    let profile = env::var("PROFILE").unwrap_or(String::new()); +    if profile == "release" || opt_level >= 2 { +        println!("cargo:rustc-cfg=optimized"); +    } +} diff --git a/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/src/lib.rs b/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/src/lib.rs new file mode 100644 index 0000000..0c03e80 --- /dev/null +++ b/libgrust/rustc-lib/stdarch/crates/assert-instr-macro/src/lib.rs @@ -0,0 +1,225 @@ +//! Implementation of the `#[assert_instr]` macro +//! +//! This macro is used when testing the `stdarch` crate and is used to generate +//! test cases to assert that functions do indeed contain the instructions that +//! we're expecting them to contain. +//! +//! The procedural macro here is relatively simple, it simply appends a +//! `#[test]` function to the original token stream which asserts that the +//! function itself contains the relevant instruction. + +extern crate proc_macro; +extern crate proc_macro2; +#[macro_use] +extern crate quote; +extern crate syn; + +use proc_macro2::TokenStream; +use quote::ToTokens; + +#[proc_macro_attribute] +pub fn assert_instr( +    attr: proc_macro::TokenStream, +    item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { +    let invoc = match syn::parse::<Invoc>(attr) { +        Ok(s) => s, +        Err(e) => return e.to_compile_error().into(), +    }; +    let item = match syn::parse::<syn::Item>(item) { +        Ok(s) => s, +        Err(e) => return e.to_compile_error().into(), +    }; +    let func = match item { +        syn::Item::Fn(ref f) => f, +        _ => panic!("must be attached to a function"), +    }; + +    let instr = &invoc.instr; +    let name = &func.sig.ident; + +    // Disable assert_instr for x86 targets compiled with avx enabled, which +    // causes LLVM to generate different intrinsics that the ones we are +    // testing for. +    let disable_assert_instr = std::env::var("STDARCH_DISABLE_ASSERT_INSTR").is_ok(); + +    // If instruction tests are disabled avoid emitting this shim at all, just +    // return the original item without our attribute. +    if !cfg!(optimized) || disable_assert_instr { +        return (quote! { #item }).into(); +    } + +    let instr_str = instr +        .replace('.', "_") +        .replace('/', "_") +        .replace(':', "_") +        .replace(char::is_whitespace, ""); +    let assert_name = syn::Ident::new(&format!("assert_{}_{}", name, instr_str), name.span()); +    // These name has to be unique enough for us to find it in the disassembly later on: +    let shim_name = syn::Ident::new( +        &format!("stdarch_test_shim_{}_{}", name, instr_str), +        name.span(), +    ); +    let mut inputs = Vec::new(); +    let mut input_vals = Vec::new(); +    let ret = &func.sig.output; +    for arg in func.sig.inputs.iter() { +        let capture = match *arg { +            syn::FnArg::Typed(ref c) => c, +            ref v => panic!( +                "arguments must not have patterns: `{:?}`", +                v.clone().into_token_stream() +            ), +        }; +        let ident = match *capture.pat { +            syn::Pat::Ident(ref i) => &i.ident, +            _ => panic!("must have bare arguments"), +        }; +        if let Some(&(_, ref tokens)) = invoc.args.iter().find(|a| *ident == a.0) { +            input_vals.push(quote! { #tokens }); +        } else { +            inputs.push(capture); +            input_vals.push(quote! { #ident }); +        } +    } + +    let attrs = func +        .attrs +        .iter() +        .filter(|attr| { +            attr.path +                .segments +                .first() +                .expect("attr.path.segments.first() failed") +                .ident +                .to_string() +                .starts_with("target") +        }) +        .collect::<Vec<_>>(); +    let attrs = Append(&attrs); + +    // Use an ABI on Windows that passes SIMD values in registers, like what +    // happens on Unix (I think?) by default. +    let abi = if cfg!(windows) { +        syn::LitStr::new("vectorcall", proc_macro2::Span::call_site()) +    } else { +        syn::LitStr::new("C", proc_macro2::Span::call_site()) +    }; +    let shim_name_str = format!("{}{}", shim_name, assert_name); +    let to_test = quote! { +        #attrs +        #[no_mangle] +        #[inline(never)] +        pub unsafe extern #abi fn #shim_name(#(#inputs),*) #ret { +            // The compiler in optimized mode by default runs a pass called +            // "mergefunc" where it'll merge functions that look identical. +            // Turns out some intrinsics produce identical code and they're +            // folded together, meaning that one just jumps to another. This +            // messes up our inspection of the disassembly of this function and +            // we're not a huge fan of that. +            // +            // To thwart this pass and prevent functions from being merged we +            // generate some code that's hopefully very tight in terms of +            // codegen but is otherwise unique to prevent code from being +            // folded. +            // +            // This is avoided on Wasm32 right now since these functions aren't +            // inlined which breaks our tests since each intrinsic looks like it +            // calls functions. Turns out functions aren't similar enough to get +            // merged on wasm32 anyway. This bug is tracked at +            // rust-lang/rust#74320. +            #[cfg(not(target_arch = "wasm32"))] +            ::stdarch_test::_DONT_DEDUP.store( +                std::mem::transmute(#shim_name_str.as_bytes().as_ptr()), +                std::sync::atomic::Ordering::Relaxed, +            ); +            #name(#(#input_vals),*) +        } +    }; + +    let tokens: TokenStream = quote! { +        #[test] +        #[allow(non_snake_case)] +        fn #assert_name() { +            #to_test + +            // Make sure that the shim is not removed by leaking it to unknown +            // code: +            unsafe { llvm_asm!("" : : "r"(#shim_name as usize) : "memory" : "volatile") }; + +            ::stdarch_test::assert(#shim_name as usize, +                                   stringify!(#shim_name), +                                   #instr); +        } +    }; + +    let tokens: TokenStream = quote! { +        #item +        #tokens +    }; +    tokens.into() +} + +struct Invoc { +    instr: String, +    args: Vec<(syn::Ident, syn::Expr)>, +} + +impl syn::parse::Parse for Invoc { +    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { +        use syn::{ext::IdentExt, Token}; + +        let mut instr = String::new(); +        while !input.is_empty() { +            if input.parse::<Token![,]>().is_ok() { +                break; +            } +            if let Ok(ident) = syn::Ident::parse_any(input) { +                instr.push_str(&ident.to_string()); +                continue; +            } +            if input.parse::<Token![.]>().is_ok() { +                instr.push_str("."); +                continue; +            } +            if let Ok(s) = input.parse::<syn::LitStr>() { +                instr.push_str(&s.value()); +                continue; +            } +            println!("{:?}", input.cursor().token_stream()); +            return Err(input.error("expected an instruction")); +        } +        if instr.is_empty() { +            return Err(input.error("expected an instruction before comma")); +        } +        let mut args = Vec::new(); +        while !input.is_empty() { +            let name = input.parse::<syn::Ident>()?; +            input.parse::<Token![=]>()?; +            let expr = input.parse::<syn::Expr>()?; +            args.push((name, expr)); + +            if input.parse::<Token![,]>().is_err() { +                if !input.is_empty() { +                    return Err(input.error("extra tokens at end")); +                } +                break; +            } +        } +        Ok(Self { instr, args }) +    } +} + +struct Append<T>(T); + +impl<T> quote::ToTokens for Append<T> +where +    T: Clone + IntoIterator, +    T::Item: quote::ToTokens, +{ +    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { +        for item in self.0.clone() { +            item.to_tokens(tokens); +        } +    } +}  | 
