| //! 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", |
| 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() |
| } |