]> git.proxmox.com Git - rustc.git/blob - src/stdarch/crates/simd-test-macro/src/lib.rs
New upstream version 1.42.0+dfsg0+pve1
[rustc.git] / src / stdarch / crates / simd-test-macro / src / lib.rs
1 //! Implementation of the `#[simd_test]` macro
2 //!
3 //! This macro expands to a `#[test]` function which tests the local machine
4 //! for the appropriate cfg before calling the inner test function.
5
6 extern crate proc_macro;
7 extern crate proc_macro2;
8 #[macro_use]
9 extern crate quote;
10
11 use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree};
12 use quote::ToTokens;
13 use std::env;
14
15 fn string(s: &str) -> TokenTree {
16 Literal::string(s).into()
17 }
18
19 #[proc_macro_attribute]
20 pub fn simd_test(
21 attr: proc_macro::TokenStream,
22 item: proc_macro::TokenStream,
23 ) -> proc_macro::TokenStream {
24 let tokens = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
25 if tokens.len() != 3 {
26 panic!("expected #[simd_test(enable = \"feature\")]");
27 }
28 match &tokens[0] {
29 TokenTree::Ident(tt) if *tt == "enable" => {}
30 _ => panic!("expected #[simd_test(enable = \"feature\")]"),
31 }
32 match &tokens[1] {
33 TokenTree::Punct(tt) if tt.as_char() == '=' => {}
34 _ => panic!("expected #[simd_test(enable = \"feature\")]"),
35 }
36 let enable_feature = match &tokens[2] {
37 TokenTree::Literal(tt) => tt.to_string(),
38 _ => panic!("expected #[simd_test(enable = \"feature\")]"),
39 };
40 let enable_feature = enable_feature.trim_start_matches('"').trim_end_matches('"');
41 let target_features: Vec<String> = enable_feature
42 .replace('+', "")
43 .split(',')
44 .map(String::from)
45 .collect();
46
47 let mmx = target_features.iter().any(|s| s.starts_with("mmx"));
48
49 let enable_feature = string(enable_feature);
50 let item = TokenStream::from(item);
51 let name = find_name(item.clone());
52
53 let name: TokenStream = name
54 .to_string()
55 .parse()
56 .unwrap_or_else(|_| panic!("failed to parse name: {}", name.to_string()));
57
58 let target = env::var("TARGET").expect(
59 "TARGET environment variable should be set for rustc (e.g. TARGET=x86_64-apple-darwin cargo test)"
60 );
61 let mut force_test = false;
62 let macro_test = match target
63 .split('-')
64 .next()
65 .unwrap_or_else(|| panic!("target triple contained no \"-\": {}", target))
66 {
67 "i686" | "x86_64" | "i586" => "is_x86_feature_detected",
68 "arm" | "armv7" => "is_arm_feature_detected",
69 "aarch64" => "is_aarch64_feature_detected",
70 "powerpc" | "powerpcle" => "is_powerpc_feature_detected",
71 "powerpc64" | "powerpc64le" => "is_powerpc64_feature_detected",
72 "mips" | "mipsel" | "mipsisa32r6" | "mipsisa32r6el" => {
73 // FIXME:
74 // On MIPS CI run-time feature detection always returns false due
75 // to this qemu bug: https://bugs.launchpad.net/qemu/+bug/1754372
76 //
77 // This is a workaround to force the MIPS tests to always run on
78 // CI.
79 force_test = true;
80 "is_mips_feature_detected"
81 }
82 "mips64" | "mips64el" | "mipsisa64r6" | "mipsisa64r6el" => {
83 // FIXME: see above
84 force_test = true;
85 "is_mips64_feature_detected"
86 }
87 t => panic!("unknown target: {}", t),
88 };
89 let macro_test = Ident::new(macro_test, Span::call_site());
90
91 let mut cfg_target_features = TokenStream::new();
92 for feature in target_features {
93 let q = quote_spanned! {
94 proc_macro2::Span::call_site() =>
95 #macro_test!(#feature) &&
96 };
97 q.to_tokens(&mut cfg_target_features);
98 }
99 let q = quote! { true };
100 q.to_tokens(&mut cfg_target_features);
101
102 let test_norun = std::env::var("STDSIMD_TEST_NORUN").is_ok();
103 let maybe_ignore = if test_norun {
104 quote! { #[ignore] }
105 } else {
106 TokenStream::new()
107 };
108
109 let emms = if mmx {
110 // note: if the test requires MMX we need to clear the FPU
111 // registers once the test finishes before interfacing with
112 // other x87 code:
113 quote! { unsafe { super::_mm_empty() }; }
114 } else {
115 TokenStream::new()
116 };
117
118 let ret: TokenStream = quote_spanned! {
119 proc_macro2::Span::call_site() =>
120 #[allow(non_snake_case)]
121 #[test]
122 #maybe_ignore
123 fn #name() {
124 if #force_test | (#cfg_target_features) {
125 let v = unsafe { #name() };
126 #emms
127 return v;
128 } else {
129 ::stdarch_test::assert_skip_test_ok(stringify!(#name));
130 }
131
132 #[target_feature(enable = #enable_feature)]
133 #item
134 }
135 };
136 ret.into()
137 }
138
139 fn find_name(item: TokenStream) -> Ident {
140 let mut tokens = item.into_iter();
141 while let Some(tok) = tokens.next() {
142 if let TokenTree::Ident(word) = tok {
143 if word == "fn" {
144 break;
145 }
146 }
147 }
148
149 match tokens.next() {
150 Some(TokenTree::Ident(word)) => word,
151 _ => panic!("failed to find function name"),
152 }
153 }