1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
//! Implementation of the `#[assert_instr]` macro
//!
//! This macro is used when testing the `stdsimd` 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.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("STDSIMD_DISABLE_ASSERT_INSTR").is_ok();
let instr_str = instr
.replace('.', "_")
.replace('/', "_")
.replace(':', "_")
.replace(|c: char| c.is_whitespace(), "");
let assert_name = syn::Ident::new(&format!("assert_{}_{}", name, instr_str), name.span());
let shim_name = syn::Ident::new(&format!("{}_shim", name), name.span());
let mut inputs = Vec::new();
let mut input_vals = Vec::new();
let ret = &func.decl.output;
for arg in func.decl.inputs.iter() {
let capture = match *arg {
syn::FnArg::Captured(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 tts)) = invoc.args.iter().find(|a| *ident == a.0) {
input_vals.push(quote! { #tts });
} 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")
.value()
.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
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.
::stdsimd_test::_DONT_DEDUP = #shim_name_str;
#name(#(#input_vals),*)
}
};
// 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 tts: TokenStream = quote! {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[allow(non_snake_case)]
fn #assert_name() {
#to_test
::stdsimd_test::assert(#shim_name as usize,
stringify!(#shim_name),
#instr);
}
};
// why? necessary now to get tests to work?
let tts: TokenStream = tts.to_string().parse().expect("cannot parse tokenstream");
let tts: TokenStream = quote! {
#item
#tts
};
tts.into()
}
struct Invoc {
instr: String,
args: Vec<(syn::Ident, syn::Expr)>,
}
impl syn::parse::Parse for Invoc {
fn parse(input: syn::parse::ParseStream) -> syn::parse::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);
}
}
}
|