]>
git.proxmox.com Git - rustc.git/blob - src/stdsimd/crates/assert-instr-macro/src/lib.rs
1 //! Implementation of the `#[assert_instr]` macro
3 //! This macro is used when testing the `stdsimd` crate and is used to generate
4 //! test cases to assert that functions do indeed contain the instructions that
5 //! we're expecting them to contain.
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.
11 extern crate proc_macro
;
12 extern crate proc_macro2
;
17 use proc_macro2
::TokenStream
;
20 #[proc_macro_attribute]
22 attr
: proc_macro
::TokenStream
,
23 item
: proc_macro
::TokenStream
,
24 ) -> proc_macro
::TokenStream
{
25 let invoc
= match syn
::parse
::<Invoc
>(attr
) {
27 Err(e
) => return e
.to_compile_error().into(),
29 let item
= match syn
::parse
::<syn
::Item
>(item
) {
31 Err(e
) => return e
.to_compile_error().into(),
33 let func
= match item
{
34 syn
::Item
::Fn(ref f
) => f
,
35 _
=> panic
!("must be attached to a function"),
38 let instr
= &invoc
.instr
;
39 let name
= &func
.ident
;
41 // Disable assert_instr for x86 targets compiled with avx enabled, which
42 // causes LLVM to generate different intrinsics that the ones we are
44 let disable_assert_instr
= std
::env
::var("STDSIMD_DISABLE_ASSERT_INSTR").is_ok();
50 .replace(|c
: char| c
.is_whitespace(), "");
51 let assert_name
= syn
::Ident
::new(&format
!("assert_{}_{}", name
, instr_str
), name
.span());
52 let shim_name
= syn
::Ident
::new(&format
!("{}_shim_{}", name
, instr_str
), name
.span());
53 let mut inputs
= Vec
::new();
54 let mut input_vals
= Vec
::new();
55 let ret
= &func
.decl
.output
;
56 for arg
in func
.decl
.inputs
.iter() {
57 let capture
= match *arg
{
58 syn
::FnArg
::Captured(ref c
) => c
,
60 "arguments must not have patterns: `{:?}`",
61 v
.clone().into_token_stream()
64 let ident
= match capture
.pat
{
65 syn
::Pat
::Ident(ref i
) => &i
.ident
,
66 _
=> panic
!("must have bare arguments"),
68 if let Some(&(_
, ref tts
)) = invoc
.args
.iter().find(|a
| *ident
== a
.0) {
69 input_vals
.push(quote
! { #tts }
);
72 input_vals
.push(quote
! { #ident }
);
83 .expect("attr.path.segments.first() failed")
87 .starts_with("target")
90 let attrs
= Append(&attrs
);
92 // Use an ABI on Windows that passes SIMD values in registers, like what
93 // happens on Unix (I think?) by default.
94 let abi
= if cfg
!(windows
) {
95 syn
::LitStr
::new("vectorcall", proc_macro2
::Span
::call_site())
97 syn
::LitStr
::new("C", proc_macro2
::Span
::call_site())
99 let shim_name_str
= format
!("{}{}", shim_name
, assert_name
);
100 let to_test
= quote
! {
103 unsafe extern #abi fn #shim_name(#(#inputs),*) #ret {
104 // The compiler in optimized mode by default runs a pass called
105 // "mergefunc" where it'll merge functions that look identical.
106 // Turns out some intrinsics produce identical code and they're
107 // folded together, meaning that one just jumps to another. This
108 // messes up our inspection of the disassembly of this function and
109 // we're not a huge fan of that.
111 // To thwart this pass and prevent functions from being merged we
112 // generate some code that's hopefully very tight in terms of
113 // codegen but is otherwise unique to prevent code from being
115 ::stdsimd_test
::_DONT_DEDUP
= #shim_name_str;
116 #name(#(#input_vals),*)
120 // If instruction tests are disabled avoid emitting this shim at all, just
121 // return the original item without our attribute.
122 if !cfg
!(optimized
) || disable_assert_instr
{
123 return (quote
! { #item }
).into();
126 let tts
: TokenStream
= quote
! {
127 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
128 #[cfg_attr(not(target_arch = "wasm32"), test)]
129 #[allow(non_snake_case)]
133 ::stdsimd_test
::assert(#shim_name as usize,
134 stringify
!(#shim_name),
138 // why? necessary now to get tests to work?
139 let tts
: TokenStream
= tts
.to_string().parse().expect("cannot parse tokenstream");
141 let tts
: TokenStream
= quote
! {
150 args
: Vec
<(syn
::Ident
, syn
::Expr
)>,
153 impl syn
::parse
::Parse
for Invoc
{
154 fn parse(input
: syn
::parse
::ParseStream
) -> syn
::parse
::Result
<Self> {
155 use syn
::{ext::IdentExt, Token}
;
157 let mut instr
= String
::new();
158 while !input
.is_empty() {
159 if input
.parse
::<Token
![,]>().is_ok() {
162 if let Ok(ident
) = syn
::Ident
::parse_any(input
) {
163 instr
.push_str(&ident
.to_string());
166 if input
.parse
::<Token
![.]>().is_ok() {
170 if let Ok(s
) = input
.parse
::<syn
::LitStr
>() {
171 instr
.push_str(&s
.value());
174 println
!("{:?}", input
.cursor().token_stream());
175 return Err(input
.error("expected an instruction"));
177 if instr
.is_empty() {
178 return Err(input
.error("expected an instruction before comma"));
180 let mut args
= Vec
::new();
181 while !input
.is_empty() {
182 let name
= input
.parse
::<syn
::Ident
>()?
;
183 input
.parse
::<Token
![=]>()?
;
184 let expr
= input
.parse
::<syn
::Expr
>()?
;
185 args
.push((name
, expr
));
187 if input
.parse
::<Token
![,]>().is_err() {
188 if !input
.is_empty() {
189 return Err(input
.error("extra tokens at end"));
194 Ok(Self { instr, args }
)
200 impl<T
> quote
::ToTokens
for Append
<T
>
202 T
: Clone
+ IntoIterator
,
203 T
::Item
: quote
::ToTokens
,
205 fn to_tokens(&self, tokens
: &mut proc_macro2
::TokenStream
) {
206 for item
in self.0.clone() {
207 item
.to_tokens(tokens
);