]>
Commit | Line | Data |
---|---|---|
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 | extern crate proc_macro; |
13 | extern crate proc_macro2; | |
14 | #[macro_use] | |
15 | extern crate quote; | |
0531ce1d XL |
16 | extern crate syn; |
17 | ||
18 | use proc_macro2::TokenStream; | |
0731742a | 19 | use quote::ToTokens; |
0531ce1d XL |
20 | |
21 | #[proc_macro_attribute] | |
22 | pub 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 | ||
160 | struct Invoc { | |
0bf4aa26 | 161 | instr: String, |
0531ce1d XL |
162 | args: Vec<(syn::Ident, syn::Expr)>, |
163 | } | |
164 | ||
0bf4aa26 | 165 | impl 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 | ||
210 | struct Append<T>(T); | |
211 | ||
212 | impl<T> quote::ToTokens for Append<T> | |
213 | where | |
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 | } |