summaryrefslogtreecommitdiffstats
path: root/library/stdarch/crates/simd-test-macro
diff options
context:
space:
mode:
Diffstat (limited to 'library/stdarch/crates/simd-test-macro')
-rw-r--r--library/stdarch/crates/simd-test-macro/Cargo.toml13
-rw-r--r--library/stdarch/crates/simd-test-macro/src/lib.rs151
2 files changed, 164 insertions, 0 deletions
diff --git a/library/stdarch/crates/simd-test-macro/Cargo.toml b/library/stdarch/crates/simd-test-macro/Cargo.toml
new file mode 100644
index 000000000..c3ecf981e
--- /dev/null
+++ b/library/stdarch/crates/simd-test-macro/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "simd-test-macro"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+test = false
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
diff --git a/library/stdarch/crates/simd-test-macro/src/lib.rs b/library/stdarch/crates/simd-test-macro/src/lib.rs
new file mode 100644
index 000000000..9d81a4c5e
--- /dev/null
+++ b/library/stdarch/crates/simd-test-macro/src/lib.rs
@@ -0,0 +1,151 @@
+//! 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::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree};
+use quote::ToTokens;
+use std::env;
+
+fn string(s: &str) -> TokenTree {
+ Literal::string(s).into()
+}
+
+#[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<_>>();
+ if tokens.len() != 3 {
+ panic!("expected #[simd_test(enable = \"feature\")]");
+ }
+ match &tokens[0] {
+ TokenTree::Ident(tt) if *tt == "enable" => {}
+ _ => panic!("expected #[simd_test(enable = \"feature\")]"),
+ }
+ match &tokens[1] {
+ TokenTree::Punct(tt) if tt.as_char() == '=' => {}
+ _ => panic!("expected #[simd_test(enable = \"feature\")]"),
+ }
+ let enable_feature = match &tokens[2] {
+ TokenTree::Literal(tt) => tt.to_string(),
+ _ => panic!("expected #[simd_test(enable = \"feature\")]"),
+ };
+ let enable_feature = enable_feature.trim_start_matches('"').trim_end_matches('"');
+ let target_features: Vec<String> = enable_feature
+ .replace('+', "")
+ .split(',')
+ .map(String::from)
+ .collect();
+
+ let enable_feature = string(enable_feature);
+ let item = TokenStream::from(item);
+ let name = find_name(item.clone());
+
+ let name: TokenStream = name
+ .to_string()
+ .parse()
+ .unwrap_or_else(|_| panic!("failed to parse name: {}", name.to_string()));
+
+ let target = env::var("TARGET").expect(
+ "TARGET environment variable should be set for rustc (e.g. TARGET=x86_64-apple-darwin cargo test)"
+ );
+ let mut force_test = false;
+ let macro_test = match target
+ .split('-')
+ .next()
+ .unwrap_or_else(|| panic!("target triple contained no \"-\": {}", target))
+ {
+ "i686" | "x86_64" | "i586" => "is_x86_feature_detected",
+ "arm" | "armv7" => "is_arm_feature_detected",
+ "aarch64" => "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",
+ "mips" | "mipsel" | "mipsisa32r6" | "mipsisa32r6el" => {
+ // FIXME:
+ // On MIPS CI run-time feature detection always returns false due
+ // to this qemu bug: https://bugs.launchpad.net/qemu/+bug/1754372
+ //
+ // This is a workaround to force the MIPS tests to always run on
+ // CI.
+ force_test = true;
+ "is_mips_feature_detected"
+ }
+ "mips64" | "mips64el" | "mipsisa64r6" | "mipsisa64r6el" => {
+ // FIXME: see above
+ force_test = true;
+ "is_mips64_feature_detected"
+ }
+ t => panic!("unknown target: {}", t),
+ };
+ let macro_test = Ident::new(macro_test, Span::call_site());
+
+ let mut cfg_target_features = TokenStream::new();
+ for feature in target_features {
+ let q = quote_spanned! {
+ proc_macro2::Span::call_site() =>
+ #macro_test!(#feature) &&
+ };
+ q.to_tokens(&mut cfg_target_features);
+ }
+ let q = quote! { true };
+ q.to_tokens(&mut cfg_target_features);
+
+ let test_norun = std::env::var("STDSIMD_TEST_NORUN").is_ok();
+ let maybe_ignore = if test_norun {
+ quote! { #[ignore] }
+ } else {
+ TokenStream::new()
+ };
+
+ let ret: TokenStream = quote_spanned! {
+ proc_macro2::Span::call_site() =>
+ #[allow(non_snake_case)]
+ #[test]
+ #maybe_ignore
+ fn #name() {
+ if #force_test | (#cfg_target_features) {
+ let v = unsafe { #name() };
+ return v;
+ } else {
+ ::stdarch_test::assert_skip_test_ok(stringify!(#name));
+ }
+
+ #[target_feature(enable = #enable_feature)]
+ #item
+ }
+ };
+ ret.into()
+}
+
+fn find_name(item: TokenStream) -> Ident {
+ let mut tokens = item.into_iter();
+ while let Some(tok) = tokens.next() {
+ if let TokenTree::Ident(word) = tok {
+ if word == "fn" {
+ break;
+ }
+ }
+ }
+
+ fn get_ident(tt: TokenTree) -> Option<Ident> {
+ match tt {
+ TokenTree::Ident(i) => Some(i),
+ TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
+ get_ident(g.stream().into_iter().next()?)
+ }
+ _ => None,
+ }
+ }
+
+ tokens
+ .next()
+ .and_then(get_ident)
+ .expect("failed to find function name")
+}