| //! Implementation of the `#[simd_test]` macro | |
| //! | |
| //! This macro expands to a `#[test]` function which tests the local machine | |
| //! for the appropriate cfg before calling the inner test function. | |
| #![deny(rust_2018_idioms)] | |
| #[macro_use] | |
| extern crate quote; | |
| use proc_macro2::{Ident, Span, TokenStream, TokenTree}; | |
| use quote::ToTokens; | |
| use std::env; | |
| #[proc_macro_attribute] | |
| pub fn simd_test( | |
| attr: proc_macro::TokenStream, | |
| item: proc_macro::TokenStream, | |
| ) -> proc_macro::TokenStream { | |
| let tokens = TokenStream::from(attr).into_iter().collect::<Vec<_>>(); | |
| let target = env::var("TARGET").expect( | |
| "TARGET environment variable should be set for rustc (e.g. TARGET=x86_64-apple-darwin cargo test)" | |
| ); | |
| let target_arch = target | |
| .split('-') | |
| .next() | |
| .unwrap_or_else(|| panic!("target triple contained no \"-\": {target}")); | |
| let (target_features, target_feature_attr) = match &tokens[..] { | |
| [] => (Vec::new(), TokenStream::new()), | |
| [ | |
| TokenTree::Ident(enable), | |
| TokenTree::Punct(equals), | |
| TokenTree::Literal(literal), | |
| ] if enable == "enable" && equals.as_char() == '=' => { | |
| let mut enable_feature = literal | |
| .to_string() | |
| .trim_start_matches('"') | |
| .trim_end_matches('"') | |
| .to_string(); | |
| let target_features: Vec<_> = enable_feature | |
| .replace('+', "") | |
| .split(',') | |
| .map(String::from) | |
| .collect(); | |
| // Allows using `#[simd_test(enable = "neon")]` on aarch64/armv7 shared tests. | |
| if target_arch == "armv7" && target_features.iter().any(|feat| feat == "neon") { | |
| enable_feature.push_str(",v7"); | |
| } | |
| ( | |
| target_features, | |
| quote! { | |
| #[target_feature(enable = #enable_feature)] | |
| }, | |
| ) | |
| } | |
| _ => panic!("expected #[simd_test(enable = \"feature\")] or #[simd_test]"), | |
| }; | |
| let mut item = syn::parse_macro_input!(item as syn::ItemFn); | |
| let item_attrs = std::mem::take(&mut item.attrs); | |
| let name = &item.sig.ident; | |
| let macro_test = match target_arch { | |
| "i686" | "x86_64" | "i586" => "is_x86_feature_detected", | |
| "arm" | "armv7" | "thumbv7neon" => "is_arm_feature_detected", | |
| "aarch64" | "arm64ec" | "aarch64_be" => "is_aarch64_feature_detected", | |
| maybe_riscv if maybe_riscv.starts_with("riscv") => "is_riscv_feature_detected", | |
| "powerpc" | "powerpcle" => "is_powerpc_feature_detected", | |
| "powerpc64" | "powerpc64le" => "is_powerpc64_feature_detected", | |
| "loongarch32" | "loongarch64" => "is_loongarch_feature_detected", | |
| "s390x" => "is_s390x_feature_detected", | |
| "mips64" | "mips64el" => "is_mips64_feature_detected", | |
| t => panic!("unknown target: {t}"), | |
| }; | |
| let macro_test = Ident::new(macro_test, Span::call_site()); | |
| let skipped_functions = env::var("STDARCH_TEST_SKIP_FUNCTION").unwrap_or_default(); | |
| let skipped_features = env::var("STDARCH_TEST_SKIP_FEATURE").unwrap_or_default(); | |
| let mut name_str = &*name.to_string(); | |
| if name_str.starts_with("test_") { | |
| name_str = &name_str[5..]; | |
| } | |
| let skip_this = skipped_functions | |
| .split(',') | |
| .map(str::trim) | |
| .any(|s| s == name_str) | |
| || skipped_features | |
| .split(',') | |
| .map(str::trim) | |
| .any(|s| target_features.iter().any(|feature| s == feature)); | |
| let mut detect_missing_features = TokenStream::new(); | |
| for feature in target_features { | |
| let q = if target_arch == "armv7" && feature == "fp16" { | |
| // "fp16" cannot be checked at runtime | |
| quote_spanned! { | |
| proc_macro2::Span::call_site() => | |
| if !cfg!(target_feature = #feature) { | |
| missing_features.push(#feature); | |
| } | |
| } | |
| } else { | |
| quote_spanned! { | |
| proc_macro2::Span::call_site() => | |
| if !::std::arch::#macro_test!(#feature) { | |
| missing_features.push(#feature); | |
| } | |
| } | |
| }; | |
| q.to_tokens(&mut detect_missing_features); | |
| } | |
| let maybe_ignore = if skip_this { | |
| quote! { #[ignore] } | |
| } else { | |
| TokenStream::new() | |
| }; | |
| let (const_test, const_stability) = if item.sig.constness.is_some() { | |
| ( | |
| quote! { | |
| const _: () = unsafe { #name() }; | |
| }, | |
| quote! { | |
| #[rustc_const_unstable(feature = "stdarch_const_helpers", issue = "none")] | |
| }, | |
| ) | |
| } else { | |
| (TokenStream::new(), TokenStream::new()) | |
| }; | |
| let ret: TokenStream = quote_spanned! { | |
| proc_macro2::Span::call_site() => | |
| #[allow(non_snake_case)] | |
| #[test] | |
| #maybe_ignore | |
| #(#item_attrs)* | |
| fn #name() { | |
| #const_test | |
| let mut missing_features = ::std::vec::Vec::new(); | |
| #detect_missing_features | |
| if missing_features.is_empty() { | |
| let v = unsafe { #name() }; | |
| return v; | |
| } else { | |
| ::stdarch_test::assert_skip_test_ok(stringify!(#name), &missing_features); | |
| } | |
| #target_feature_attr | |
| #const_stability | |
| #item | |
| } | |
| }; | |
| ret.into() | |
| } |