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