| //! Implementation of the `#[assert_instr]` macro | |
| //! | |
| //! This macro is used when testing the `stdarch` 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. | |
| #![deny(rust_2018_idioms)] | |
| #[macro_use] | |
| extern crate quote; | |
| use proc_macro2::TokenStream; | |
| use quote::ToTokens; | |
| use syn::spanned::Spanned; | |
| #[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.sig.ident; | |
| let maybe_allow_deprecated = if func | |
| .attrs | |
| .iter() | |
| .any(|attr| attr.path().is_ident("deprecated")) | |
| { | |
| quote! { #[allow(deprecated)] } | |
| } else { | |
| quote! {} | |
| }; | |
| // 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("STDARCH_DISABLE_ASSERT_INSTR").is_ok(); | |
| // 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 instr_str = instr | |
| .replace(['.', '/', ':'], "_") | |
| .replace(char::is_whitespace, ""); | |
| let assert_name = syn::Ident::new(&format!("assert_{name}_{instr_str}"), name.span()); | |
| // These name has to be unique enough for us to find it in the disassembly later on: | |
| let shim_name = syn::Ident::new( | |
| &format!("stdarch_test_shim_{name}_{instr_str}"), | |
| name.span(), | |
| ); | |
| let mut inputs = Vec::new(); | |
| let mut input_vals = Vec::new(); | |
| let mut param_vals = Vec::new(); | |
| let ret = &func.sig.output; | |
| for arg in func.sig.inputs.iter() { | |
| let capture = match *arg { | |
| syn::FnArg::Typed(ref c) => c.to_owned(), | |
| ref v => panic!( | |
| "arguments must not have patterns: `{:?}`", | |
| v.clone().into_token_stream() | |
| ), | |
| }; | |
| let ident = match capture.pat.as_ref() { | |
| syn::Pat::Ident(i) => &i.ident.to_owned(), | |
| _ => panic!("must have bare arguments"), | |
| }; | |
| if let Some(&(_, ref tokens)) = invoc.args.iter().find(|a| *ident == a.0) { | |
| input_vals.push(quote! { #tokens }); | |
| } else { | |
| inputs.push(capture); | |
| input_vals.push(quote! { #ident }); | |
| } | |
| } | |
| for arg in func.sig.generics.params.iter() { | |
| match *arg { | |
| syn::GenericParam::Const(ref c) => { | |
| if let Some((_, tokens)) = invoc.args.iter().find(|a| c.ident == a.0) { | |
| param_vals.push(quote! { #tokens }); | |
| } else { | |
| panic!("const generics must have a value for tests"); | |
| } | |
| } | |
| syn::GenericParam::Type(ref t) => { | |
| if let Some((_, tokens)) = invoc.args.iter().find(|a| t.ident == a.0) | |
| && let syn::Expr::Path(syn::ExprPath { qself, path, .. }) = tokens | |
| { | |
| param_vals.push(syn::Token).to_token_stream()); | |
| let generic_ty_value = syn::TypePath { | |
| qself: qself.clone(), | |
| path: path.clone(), | |
| }; | |
| // Replace any function arguments that use generic parameters with the | |
| // instantiation provided in the macro invocation. | |
| inputs.iter_mut().for_each(|arg| { | |
| update_type_path(arg.ty.as_mut(), |type_path: &mut syn::TypePath| { | |
| if let Some(syn::PathSegment { | |
| ident: last_ident, .. | |
| }) = type_path.path.segments.last_mut() | |
| { | |
| if *last_ident == t.ident { | |
| *type_path = generic_ty_value.to_owned() | |
| } | |
| } | |
| }) | |
| }); | |
| } else { | |
| panic!("type generics must have a type for tests"); | |
| } | |
| } | |
| ref v => panic!( | |
| "only type and const generics are allowed: `{:?}`", | |
| v.clone().into_token_stream() | |
| ), | |
| }; | |
| } | |
| let attrs = func | |
| .attrs | |
| .iter() | |
| .filter(|attr| { | |
| attr.path() | |
| .segments | |
| .first() | |
| .expect("attr.path.segments.first() failed") | |
| .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) { | |
| let target = std::env::var("TARGET").unwrap(); | |
| if target.contains("x86_64") { | |
| syn::LitStr::new("sysv64", proc_macro2::Span::call_site()) | |
| } else if target.contains("86") { | |
| syn::LitStr::new("vectorcall", proc_macro2::Span::call_site()) | |
| } else { | |
| syn::LitStr::new("C", proc_macro2::Span::call_site()) | |
| } | |
| } else { | |
| syn::LitStr::new("C", proc_macro2::Span::call_site()) | |
| }; | |
| let to_test = quote! { | |
| #attrs | |
| #maybe_allow_deprecated | |
| #[unsafe(no_mangle)] | |
| #[inline(never)] | |
| pub unsafe extern #abi fn #shim_name(#(#inputs),*) #ret { | |
| #name::<#(#param_vals),*>(#(#input_vals),*) | |
| } | |
| }; | |
| let tokens: TokenStream = quote! { | |
| #[test] | |
| #[allow(non_snake_case)] | |
| fn #assert_name() { | |
| #to_test | |
| ::stdarch_test::assert(#shim_name as usize, stringify!(#shim_name), #instr); | |
| } | |
| }; | |
| let tokens: TokenStream = quote! { | |
| #item | |
| #tokens | |
| }; | |
| tokens.into() | |
| } | |
| struct Invoc { | |
| instr: String, | |
| args: Vec<(syn::Ident, syn::Expr)>, | |
| } | |
| impl syn::parse::Parse for Invoc { | |
| fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> { | |
| use syn::{Token, ext::IdentExt}; | |
| 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('.'); | |
| 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); | |
| } | |
| } | |
| } | |
| /// Calls `update` on type paths so that type generics can be replaced with the instantiation from | |
| /// the attribute. | |
| fn update_type_path<F>(ty: &mut syn::Type, update: F) | |
| where | |
| F: Fn(&mut syn::TypePath), | |
| { | |
| use syn::Type::*; | |
| match ty { | |
| Array(syn::TypeArray { elem, .. }) | |
| | Group(syn::TypeGroup { elem, .. }) | |
| | Paren(syn::TypeParen { elem, .. }) | |
| | Ptr(syn::TypePtr { elem, .. }) | |
| | Reference(syn::TypeReference { elem, .. }) | |
| | Slice(syn::TypeSlice { elem, .. }) => update_type_path(elem.as_mut(), update), | |
| Path(path @ syn::TypePath { .. }) => update(path), | |
| Tuple(..) => panic!("tuples and generic types together are not yet supported"), | |
| _ => {} | |
| } | |
| } |