2017-09-26 12:37:39 -07:00
|
|
|
//! Implementation of the `#[simd_test]` macro
|
|
|
|
|
//!
|
2017-10-27 17:55:29 +02:00
|
|
|
//! This macro expands to a `#[test]` function which tests the local machine
|
|
|
|
|
//! for the appropriate cfg before calling the inner test function.
|
2017-09-26 12:37:39 -07:00
|
|
|
|
2017-10-27 17:55:29 +02:00
|
|
|
extern crate proc_macro;
|
2018-02-02 16:08:27 +01:00
|
|
|
extern crate proc_macro2;
|
2017-09-26 12:37:39 -07:00
|
|
|
#[macro_use]
|
|
|
|
|
extern crate quote;
|
|
|
|
|
|
2020-05-28 02:41:59 +01:00
|
|
|
use proc_macro2::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree};
|
2018-11-10 15:45:16 +01:00
|
|
|
use quote::ToTokens;
|
|
|
|
|
use std::env;
|
2017-09-26 12:37:39 -07:00
|
|
|
|
|
|
|
|
fn string(s: &str) -> TokenTree {
|
2018-04-03 07:34:02 -07:00
|
|
|
Literal::string(s).into()
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[proc_macro_attribute]
|
2017-10-27 17:55:29 +02:00
|
|
|
pub fn simd_test(
|
2018-12-13 17:26:22 -06:00
|
|
|
attr: proc_macro::TokenStream,
|
|
|
|
|
item: proc_macro::TokenStream,
|
2017-10-27 17:55:29 +02:00
|
|
|
) -> proc_macro::TokenStream {
|
2018-06-06 00:17:14 +02:00
|
|
|
let tokens = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
|
2018-04-27 04:54:15 +02:00
|
|
|
if tokens.len() != 3 {
|
|
|
|
|
panic!("expected #[simd_test(enable = \"feature\")]");
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|
2018-04-03 07:34:02 -07:00
|
|
|
match &tokens[0] {
|
2018-11-10 15:45:16 +01:00
|
|
|
TokenTree::Ident(tt) if *tt == "enable" => {}
|
2018-04-27 04:54:15 +02:00
|
|
|
_ => panic!("expected #[simd_test(enable = \"feature\")]"),
|
|
|
|
|
}
|
|
|
|
|
match &tokens[1] {
|
2018-05-21 20:37:41 +02:00
|
|
|
TokenTree::Punct(tt) if tt.as_char() == '=' => {}
|
2018-04-27 04:54:15 +02:00
|
|
|
_ => panic!("expected #[simd_test(enable = \"feature\")]"),
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|
2018-04-27 04:54:15 +02:00
|
|
|
let enable_feature = match &tokens[2] {
|
2018-04-03 07:34:02 -07:00
|
|
|
TokenTree::Literal(tt) => tt.to_string(),
|
2018-04-27 04:54:15 +02:00
|
|
|
_ => panic!("expected #[simd_test(enable = \"feature\")]"),
|
2017-11-13 17:48:40 +01:00
|
|
|
};
|
2018-12-14 12:44:51 -08:00
|
|
|
let enable_feature = enable_feature.trim_start_matches('"').trim_end_matches('"');
|
2018-04-27 04:54:15 +02:00
|
|
|
let target_features: Vec<String> = enable_feature
|
2017-11-13 17:48:40 +01:00
|
|
|
.replace('+', "")
|
|
|
|
|
.split(',')
|
2018-11-10 15:45:16 +01:00
|
|
|
.map(String::from)
|
2017-11-13 17:48:40 +01:00
|
|
|
.collect();
|
|
|
|
|
|
2019-01-22 19:20:18 +01:00
|
|
|
let mmx = target_features.iter().any(|s| s.starts_with("mmx"));
|
|
|
|
|
|
2018-01-17 09:45:02 -06:00
|
|
|
let enable_feature = string(enable_feature);
|
2017-09-26 12:37:39 -07:00
|
|
|
let item = TokenStream::from(item);
|
|
|
|
|
let name = find_name(item.clone());
|
|
|
|
|
|
2018-12-13 17:26:22 -06:00
|
|
|
let name: TokenStream = name
|
|
|
|
|
.to_string()
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap_or_else(|_| panic!("failed to parse name: {}", name.to_string()));
|
2017-09-26 12:37:39 -07:00
|
|
|
|
2019-04-19 12:23:30 +02:00
|
|
|
let target = env::var("TARGET").expect(
|
|
|
|
|
"TARGET environment variable should be set for rustc (e.g. TARGET=x86_64-apple-darwin cargo test)"
|
|
|
|
|
);
|
2018-03-10 19:22:54 +01:00
|
|
|
let mut force_test = false;
|
2018-12-13 17:26:22 -06:00
|
|
|
let macro_test = match target
|
|
|
|
|
.split('-')
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap_or_else(|| panic!("target triple contained no \"-\": {}", target))
|
|
|
|
|
{
|
2018-03-07 09:46:16 -06:00
|
|
|
"i686" | "x86_64" | "i586" => "is_x86_feature_detected",
|
2018-03-20 15:11:50 +01:00
|
|
|
"arm" | "armv7" => "is_arm_feature_detected",
|
2018-03-07 09:46:16 -06:00
|
|
|
"aarch64" => "is_aarch64_feature_detected",
|
2018-05-16 22:19:13 +02:00
|
|
|
"powerpc" | "powerpcle" => "is_powerpc_feature_detected",
|
2018-05-16 20:59:28 +02:00
|
|
|
"powerpc64" | "powerpc64le" => "is_powerpc64_feature_detected",
|
2019-04-04 08:59:43 +00:00
|
|
|
"mips" | "mipsel" | "mipsisa32r6" | "mipsisa32r6el" => {
|
2018-03-10 19:22:54 +01:00
|
|
|
// 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"
|
|
|
|
|
}
|
2019-04-04 08:59:43 +00:00
|
|
|
"mips64" | "mips64el" | "mipsisa64r6" | "mipsisa64r6el" => {
|
2018-03-10 19:22:54 +01:00
|
|
|
// FIXME: see above
|
|
|
|
|
force_test = true;
|
|
|
|
|
"is_mips64_feature_detected"
|
|
|
|
|
}
|
2018-03-07 09:46:16 -06:00
|
|
|
t => panic!("unknown target: {}", t),
|
|
|
|
|
};
|
2018-05-21 20:37:41 +02:00
|
|
|
let macro_test = Ident::new(macro_test, Span::call_site());
|
2018-03-07 09:46:16 -06:00
|
|
|
|
2018-06-02 16:01:35 +02:00
|
|
|
let mut cfg_target_features = TokenStream::new();
|
2017-11-13 17:48:40 +01:00
|
|
|
for feature in target_features {
|
2017-11-21 12:54:06 -06:00
|
|
|
let q = quote_spanned! {
|
2018-01-08 10:10:52 -08:00
|
|
|
proc_macro2::Span::call_site() =>
|
2018-03-07 09:46:16 -06:00
|
|
|
#macro_test!(#feature) &&
|
2017-11-13 17:48:40 +01:00
|
|
|
};
|
|
|
|
|
q.to_tokens(&mut cfg_target_features);
|
|
|
|
|
}
|
2018-11-22 12:52:19 +01:00
|
|
|
let q = quote! { true };
|
2017-11-13 17:48:40 +01:00
|
|
|
q.to_tokens(&mut cfg_target_features);
|
|
|
|
|
|
2018-05-22 15:33:57 +00:00
|
|
|
let test_norun = std::env::var("STDSIMD_TEST_NORUN").is_ok();
|
2018-11-10 15:45:16 +01:00
|
|
|
let maybe_ignore = if test_norun {
|
|
|
|
|
quote! { #[ignore] }
|
2018-05-22 15:33:57 +00:00
|
|
|
} else {
|
2018-11-10 15:45:16 +01:00
|
|
|
TokenStream::new()
|
2018-05-22 15:33:57 +00:00
|
|
|
};
|
|
|
|
|
|
2019-01-22 19:20:18 +01:00
|
|
|
let emms = if mmx {
|
|
|
|
|
// note: if the test requires MMX we need to clear the FPU
|
|
|
|
|
// registers once the test finishes before interfacing with
|
|
|
|
|
// other x87 code:
|
|
|
|
|
quote! { unsafe { super::_mm_empty() }; }
|
|
|
|
|
} else {
|
|
|
|
|
TokenStream::new()
|
|
|
|
|
};
|
|
|
|
|
|
2017-11-21 12:54:06 -06:00
|
|
|
let ret: TokenStream = quote_spanned! {
|
2018-01-08 10:10:52 -08:00
|
|
|
proc_macro2::Span::call_site() =>
|
2017-10-11 17:28:44 +02:00
|
|
|
#[allow(non_snake_case)]
|
2017-09-26 12:37:39 -07:00
|
|
|
#[test]
|
2018-05-22 15:33:57 +00:00
|
|
|
#maybe_ignore
|
2017-09-26 12:37:39 -07:00
|
|
|
fn #name() {
|
2018-03-10 19:22:54 +01:00
|
|
|
if #force_test | (#cfg_target_features) {
|
2019-01-22 19:20:18 +01:00
|
|
|
let v = unsafe { #name() };
|
|
|
|
|
#emms
|
|
|
|
|
return v;
|
2017-10-18 11:35:11 -04:00
|
|
|
} else {
|
2019-07-08 23:21:37 +02:00
|
|
|
::stdarch_test::assert_skip_test_ok(stringify!(#name));
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|
|
|
|
|
|
2018-01-17 09:45:02 -06:00
|
|
|
#[target_feature(enable = #enable_feature)]
|
2017-09-26 12:37:39 -07:00
|
|
|
#item
|
|
|
|
|
}
|
2018-11-10 15:45:16 +01:00
|
|
|
};
|
2017-09-26 12:37:39 -07:00
|
|
|
ret.into()
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-21 20:37:41 +02:00
|
|
|
fn find_name(item: TokenStream) -> Ident {
|
2017-09-26 12:37:39 -07:00
|
|
|
let mut tokens = item.into_iter();
|
|
|
|
|
while let Some(tok) = tokens.next() {
|
2018-05-21 20:37:41 +02:00
|
|
|
if let TokenTree::Ident(word) = tok {
|
|
|
|
|
if word == "fn" {
|
2017-10-27 17:55:29 +02:00
|
|
|
break;
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-28 02:41:59 +01:00
|
|
|
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,
|
|
|
|
|
}
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|
2020-05-28 02:41:59 +01:00
|
|
|
|
|
|
|
|
tokens.next().and_then(get_ident).expect("failed to find function name")
|
2017-09-26 12:37:39 -07:00
|
|
|
}
|