diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /library/stdarch/crates/assert-instr-macro | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/stdarch/crates/assert-instr-macro')
-rw-r--r-- | library/stdarch/crates/assert-instr-macro/Cargo.toml | 14 | ||||
-rw-r--r-- | library/stdarch/crates/assert-instr-macro/build.rs | 13 | ||||
-rw-r--r-- | library/stdarch/crates/assert-instr-macro/src/lib.rs | 251 |
3 files changed, 278 insertions, 0 deletions
diff --git a/library/stdarch/crates/assert-instr-macro/Cargo.toml b/library/stdarch/crates/assert-instr-macro/Cargo.toml new file mode 100644 index 000000000..3d9b32067 --- /dev/null +++ b/library/stdarch/crates/assert-instr-macro/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "assert-instr-macro" +version = "0.1.0" +authors = ["Alex Crichton <alex@alexcrichton.com>"] +edition = "2018" + +[lib] +proc-macro = true +test = false + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", features = ["full"] } diff --git a/library/stdarch/crates/assert-instr-macro/build.rs b/library/stdarch/crates/assert-instr-macro/build.rs new file mode 100644 index 000000000..01ae79660 --- /dev/null +++ b/library/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_default(); + if profile == "release" || opt_level >= 2 { + println!("cargo:rustc-cfg=optimized"); + } +} diff --git a/library/stdarch/crates/assert-instr-macro/src/lib.rs b/library/stdarch/crates/assert-instr-macro/src/lib.rs new file mode 100644 index 000000000..9fa411df3 --- /dev/null +++ b/library/stdarch/crates/assert-instr-macro/src/lib.rs @@ -0,0 +1,251 @@ +//! 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. +#![deny(rust_2018_idioms)] + +#[macro_use] +extern crate quote; + +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(); + + // Disable dedup guard. Only works if the LLVM MergeFunctions pass is disabled, e.g. + // with `-Z merge-functions=disabled` in RUSTFLAGS. + let disable_dedup_guard = std::env::var("STDARCH_DISABLE_DEDUP_GUARD").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 shim_name_ptr = syn::Ident::new( + &format!("stdarch_test_shim_{}_{}_ptr", name, instr_str).to_ascii_uppercase(), + name.span(), + ); + let mut inputs = Vec::new(); + let mut input_vals = Vec::new(); + let mut const_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 }); + } + } + for arg in func.sig.generics.params.iter() { + let c = match *arg { + syn::GenericParam::Const(ref c) => c, + ref v => panic!( + "only const generics are allowed: `{:?}`", + v.clone().into_token_stream() + ), + }; + if let Some(&(_, ref tokens)) = invoc.args.iter().find(|a| c.ident == a.0) { + const_vals.push(quote! { #tokens }); + } else { + panic!("const generics must have a value for tests"); + } + } + + 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) { + let target = std::env::var("TARGET").unwrap(); + if target.contains("x86_64") { + syn::LitStr::new("sysv64", proc_macro2::Span::call_site()) + } else { + 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 = if disable_dedup_guard { + quote! { + #attrs + #[no_mangle] + #[inline(never)] + pub unsafe extern #abi fn #shim_name(#(#inputs),*) #ret { + #name::<#(#const_vals),*>(#(#input_vals),*) + } + } + } else { + quote! { + + const #shim_name_ptr : *const u8 = #shim_name_str.as_ptr(); + + #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. + ::stdarch_test::_DONT_DEDUP = #shim_name_ptr; + #name::<#(#const_vals),*>(#(#input_vals),*) + } + } + }; + + let tokens: TokenStream = quote! { + #[test] + #[allow(non_snake_case)] + fn #assert_name() { + #to_test + + ::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('.'); + 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); + } + } +} |