]> git.proxmox.com Git - rustc.git/blame - library/stdarch/crates/assert-instr-macro/src/lib.rs
New upstream version 1.53.0+dfsg1
[rustc.git] / library / stdarch / crates / assert-instr-macro / src / lib.rs
CommitLineData
0531ce1d
XL
1//! Implementation of the `#[assert_instr]` macro
2//!
416331ca 3//! This macro is used when testing the `stdarch` crate and is used to generate
0531ce1d
XL
4//! test cases to assert that functions do indeed contain the instructions that
5//! we're expecting them to contain.
6//!
7//! The procedural macro here is relatively simple, it simply appends a
8//! `#[test]` function to the original token stream which asserts that the
9//! function itself contains the relevant instruction.
cdc7bbd5 10#![deny(rust_2018_idioms)]
0531ce1d 11
0531ce1d
XL
12extern crate proc_macro;
13extern crate proc_macro2;
14#[macro_use]
15extern crate quote;
0531ce1d
XL
16extern crate syn;
17
18use proc_macro2::TokenStream;
0731742a 19use quote::ToTokens;
0531ce1d
XL
20
21#[proc_macro_attribute]
22pub fn assert_instr(
0731742a
XL
23 attr: proc_macro::TokenStream,
24 item: proc_macro::TokenStream,
0531ce1d 25) -> proc_macro::TokenStream {
0731742a
XL
26 let invoc = match syn::parse::<Invoc>(attr) {
27 Ok(s) => s,
28 Err(e) => return e.to_compile_error().into(),
29 };
30 let item = match syn::parse::<syn::Item>(item) {
31 Ok(s) => s,
32 Err(e) => return e.to_compile_error().into(),
33 };
0531ce1d
XL
34 let func = match item {
35 syn::Item::Fn(ref f) => f,
36 _ => panic!("must be attached to a function"),
37 };
38
39 let instr = &invoc.instr;
74b04a01 40 let name = &func.sig.ident;
83c7162d
XL
41
42 // Disable assert_instr for x86 targets compiled with avx enabled, which
8faf50e0
XL
43 // causes LLVM to generate different intrinsics that the ones we are
44 // testing for.
416331ca
XL
45 let disable_assert_instr = std::env::var("STDARCH_DISABLE_ASSERT_INSTR").is_ok();
46
47 // If instruction tests are disabled avoid emitting this shim at all, just
48 // return the original item without our attribute.
49 if !cfg!(optimized) || disable_assert_instr {
50 return (quote! { #item }).into();
51 }
83c7162d 52
83c7162d 53 let instr_str = instr
83c7162d 54 .replace('.', "_")
0731742a
XL
55 .replace('/', "_")
56 .replace(':', "_")
48663c56 57 .replace(char::is_whitespace, "");
0731742a 58 let assert_name = syn::Ident::new(&format!("assert_{}_{}", name, instr_str), name.span());
416331ca
XL
59 // These name has to be unique enough for us to find it in the disassembly later on:
60 let shim_name = syn::Ident::new(
61 &format!("stdarch_test_shim_{}_{}", name, instr_str),
62 name.span(),
63 );
0531ce1d
XL
64 let mut inputs = Vec::new();
65 let mut input_vals = Vec::new();
74b04a01
XL
66 let ret = &func.sig.output;
67 for arg in func.sig.inputs.iter() {
0531ce1d 68 let capture = match *arg {
74b04a01 69 syn::FnArg::Typed(ref c) => c,
8faf50e0
XL
70 ref v => panic!(
71 "arguments must not have patterns: `{:?}`",
72 v.clone().into_token_stream()
73 ),
0531ce1d 74 };
74b04a01 75 let ident = match *capture.pat {
0531ce1d
XL
76 syn::Pat::Ident(ref i) => &i.ident,
77 _ => panic!("must have bare arguments"),
78 };
74b04a01
XL
79 if let Some(&(_, ref tokens)) = invoc.args.iter().find(|a| *ident == a.0) {
80 input_vals.push(quote! { #tokens });
0731742a
XL
81 } else {
82 inputs.push(capture);
83 input_vals.push(quote! { #ident });
84 }
0531ce1d
XL
85 }
86
8faf50e0
XL
87 let attrs = func
88 .attrs
0531ce1d
XL
89 .iter()
90 .filter(|attr| {
91 attr.path
92 .segments
93 .first()
83c7162d 94 .expect("attr.path.segments.first() failed")
0531ce1d 95 .ident
8faf50e0 96 .to_string()
0531ce1d 97 .starts_with("target")
0731742a
XL
98 })
99 .collect::<Vec<_>>();
0531ce1d
XL
100 let attrs = Append(&attrs);
101
102 // Use an ABI on Windows that passes SIMD values in registers, like what
103 // happens on Unix (I think?) by default.
104 let abi = if cfg!(windows) {
105 syn::LitStr::new("vectorcall", proc_macro2::Span::call_site())
106 } else {
107 syn::LitStr::new("C", proc_macro2::Span::call_site())
108 };
8faf50e0 109 let shim_name_str = format!("{}{}", shim_name, assert_name);
0531ce1d
XL
110 let to_test = quote! {
111 #attrs
9fa01778 112 #[no_mangle]
416331ca
XL
113 #[inline(never)]
114 pub unsafe extern #abi fn #shim_name(#(#inputs),*) #ret {
8faf50e0
XL
115 // The compiler in optimized mode by default runs a pass called
116 // "mergefunc" where it'll merge functions that look identical.
117 // Turns out some intrinsics produce identical code and they're
118 // folded together, meaning that one just jumps to another. This
119 // messes up our inspection of the disassembly of this function and
120 // we're not a huge fan of that.
121 //
122 // To thwart this pass and prevent functions from being merged we
123 // generate some code that's hopefully very tight in terms of
124 // codegen but is otherwise unique to prevent code from being
125 // folded.
3dfed10e
XL
126 //
127 // This is avoided on Wasm32 right now since these functions aren't
128 // inlined which breaks our tests since each intrinsic looks like it
129 // calls functions. Turns out functions aren't similar enough to get
130 // merged on wasm32 anyway. This bug is tracked at
131 // rust-lang/rust#74320.
132 #[cfg(not(target_arch = "wasm32"))]
416331ca
XL
133 ::stdarch_test::_DONT_DEDUP.store(
134 std::mem::transmute(#shim_name_str.as_bytes().as_ptr()),
135 std::sync::atomic::Ordering::Relaxed,
136 );
0531ce1d
XL
137 #name(#(#input_vals),*)
138 }
139 };
140
74b04a01 141 let tokens: TokenStream = quote! {
3dfed10e 142 #[test]
0531ce1d 143 #[allow(non_snake_case)]
0531ce1d
XL
144 fn #assert_name() {
145 #to_test
146
416331ca 147 ::stdarch_test::assert(#shim_name as usize,
0531ce1d 148 stringify!(#shim_name),
0bf4aa26 149 #instr);
0531ce1d 150 }
0731742a 151 };
0531ce1d 152
74b04a01 153 let tokens: TokenStream = quote! {
0531ce1d 154 #item
74b04a01 155 #tokens
0731742a 156 };
74b04a01 157 tokens.into()
0531ce1d
XL
158}
159
160struct Invoc {
0bf4aa26 161 instr: String,
0531ce1d
XL
162 args: Vec<(syn::Ident, syn::Expr)>,
163}
164
0bf4aa26 165impl syn::parse::Parse for Invoc {
cdc7bbd5 166 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
0731742a 167 use syn::{ext::IdentExt, Token};
0bf4aa26 168
0731742a
XL
169 let mut instr = String::new();
170 while !input.is_empty() {
171 if input.parse::<Token![,]>().is_ok() {
172 break;
173 }
174 if let Ok(ident) = syn::Ident::parse_any(input) {
175 instr.push_str(&ident.to_string());
176 continue;
177 }
178 if input.parse::<Token![.]>().is_ok() {
fc512014 179 instr.push('.');
0731742a
XL
180 continue;
181 }
182 if let Ok(s) = input.parse::<syn::LitStr>() {
183 instr.push_str(&s.value());
184 continue;
185 }
186 println!("{:?}", input.cursor().token_stream());
187 return Err(input.error("expected an instruction"));
188 }
9fa01778 189 if instr.is_empty() {
0731742a
XL
190 return Err(input.error("expected an instruction before comma"));
191 }
0bf4aa26 192 let mut args = Vec::new();
0731742a 193 while !input.is_empty() {
0bf4aa26
XL
194 let name = input.parse::<syn::Ident>()?;
195 input.parse::<Token![=]>()?;
196 let expr = input.parse::<syn::Expr>()?;
197 args.push((name, expr));
0731742a
XL
198
199 if input.parse::<Token![,]>().is_err() {
200 if !input.is_empty() {
201 return Err(input.error("extra tokens at end"));
202 }
203 break;
204 }
0bf4aa26 205 }
0731742a 206 Ok(Self { instr, args })
0bf4aa26 207 }
0531ce1d
XL
208}
209
210struct Append<T>(T);
211
212impl<T> quote::ToTokens for Append<T>
213where
214 T: Clone + IntoIterator,
215 T::Item: quote::ToTokens,
216{
8faf50e0 217 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
0531ce1d
XL
218 for item in self.0.clone() {
219 item.to_tokens(tokens);
220 }
221 }
222}