Skip to content

Commit ec916db

Browse files
authored
Build all tests for better cross-compilation (#126)
This was discussed a bit in #122. We were previously omitting architecture-specific tests based on the *host* architecture, which is incorrect and prevents cross-compiling tests to run under something like qemu. We can't access `CARGO_CFG_TARGET_ARCH` at the time that proc macros are evaluated, so we just have to build every architecture-specific test and use `#[cfg]` to disable ones that aren't applicable to the current architecture. Even more annoyingly, there's no way to conditionally ignore a test at runtime. This means that if the CPU we're running the tests on doesn't support a target feature, we have to either pass or fail that test. I've chosen to fail, to prevent faulty code from appearing to pass tests. I've also updated the architecture-specific `exclude` functions to add `#[ignore]` attributes rather than omitting the tests entirely. This makes it more clear that the tests exist, but are not being run because they don't yet pass.
1 parent ae86071 commit ec916db

File tree

2 files changed

+102
-71
lines changed
  • fearless_simd_dev_macros/src
  • fearless_simd_tests/tests

2 files changed

+102
-71
lines changed

fearless_simd_dev_macros/src/lib.rs

Lines changed: 58 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -23,91 +23,86 @@ pub fn simd_test(_: TokenStream, item: TokenStream) -> TokenStream {
2323
let avx2_name = get_ident("avx2");
2424
let wasm_name = get_ident("wasm");
2525

26-
let include_fallback = !exclude_fallback(&input_fn_name.to_string());
27-
#[cfg(target_arch = "aarch64")]
28-
let include_neon = std::arch::is_aarch64_feature_detected!("neon")
29-
&& !exclude_neon(&input_fn_name.to_string());
30-
#[cfg(not(target_arch = "aarch64"))]
31-
let include_neon = false;
32-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
33-
let include_sse4 =
34-
std::arch::is_x86_feature_detected!("sse4.2") && !exclude_sse4(&input_fn_name.to_string());
35-
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
36-
let include_sse4 = false;
37-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
38-
let include_avx2 = std::arch::is_x86_feature_detected!("avx2")
39-
&& std::arch::is_x86_feature_detected!("fma")
40-
&& !exclude_avx2(&input_fn_name.to_string());
41-
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
42-
let include_avx2 = false;
43-
// Note that we cannot feature-gate this with `target_arch`. If we run
44-
// `cargo test --target wasm32-wasip1`, then the `target_arch` will still be set to
45-
// the operating system you are running on. Because of this, we instead add the `target_arch`
46-
// feature gate to the actual test.
47-
let include_wasm = !exclude_wasm(&input_fn_name.to_string());
48-
49-
let fallback_snippet = if include_fallback {
50-
quote! {
51-
#[test]
52-
fn #fallback_name() {
53-
let fallback = fearless_simd::Fallback::new();
54-
#input_fn_name(fallback);
55-
}
26+
let ignore_attr = |f: fn(&str) -> bool| {
27+
let should_ignore = f(&input_fn_name.to_string());
28+
if should_ignore {
29+
quote! { #[ignore] }
30+
} else {
31+
quote! {}
5632
}
57-
} else {
58-
quote! {}
5933
};
6034

61-
let neon_snippet = if include_neon {
62-
quote! {
63-
#[cfg(target_arch = "aarch64")]
64-
#[test]
65-
fn #neon_name() {
35+
let ignore_fallback = ignore_attr(exclude_fallback);
36+
let ignore_neon = ignore_attr(exclude_neon);
37+
let ignore_sse4 = ignore_attr(exclude_sse4);
38+
let ignore_avx2 = ignore_attr(exclude_avx2);
39+
let ignore_wasm = ignore_attr(exclude_wasm);
40+
41+
let fallback_snippet = quote! {
42+
#[test]
43+
#ignore_fallback
44+
fn #fallback_name() {
45+
let fallback = fearless_simd::Fallback::new();
46+
#input_fn_name(fallback);
47+
}
48+
};
49+
50+
// All of the architecture-specific tests need to be included every time, and #[cfg]'d out depending on the target
51+
// architecture. We can't use `CARGO_CFG_TARGET_ARCH` to conditionally omit them because it's not available when
52+
// proc macros are evaluated.
53+
54+
// There is currently no way to conditionally ignore a test at runtime (see
55+
// https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611). Instead, we'll just pass the tests if the
56+
// target features aren't supported. This is not ideal, since it may mislead you into thinking tests have passed
57+
// when they haven't even been run, but some CI runners don't support all target features and we don't want failures
58+
// as a result of that.
59+
60+
let neon_snippet = quote! {
61+
#[cfg(target_arch = "aarch64")]
62+
#[test]
63+
#ignore_neon
64+
fn #neon_name() {
65+
if std::arch::is_aarch64_feature_detected!("neon") {
6666
let neon = unsafe { fearless_simd::aarch64::Neon::new_unchecked() };
6767
#input_fn_name(neon);
6868
}
6969
}
70-
} else {
71-
quote! {}
7270
};
7371

74-
let sse4_snippet = if include_sse4 {
75-
quote! {
76-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
77-
#[test]
78-
fn #sse4_name() {
72+
let sse4_snippet = quote! {
73+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
74+
#[test]
75+
#ignore_sse4
76+
fn #sse4_name() {
77+
if std::arch::is_x86_feature_detected!("sse4.2") {
7978
let sse4 = unsafe { fearless_simd::x86::Sse4_2::new_unchecked() };
8079
#input_fn_name(sse4);
8180
}
8281
}
83-
} else {
84-
quote! {}
8582
};
8683

87-
let avx2_snippet = if include_avx2 {
88-
quote! {
89-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
90-
#[test]
91-
fn #avx2_name() {
84+
let avx2_snippet = quote! {
85+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
86+
#[test]
87+
#ignore_avx2
88+
fn #avx2_name() {
89+
if std::arch::is_x86_feature_detected!("avx2")
90+
&& std::arch::is_x86_feature_detected!("fma")
91+
{
9292
let avx2 = unsafe { fearless_simd::x86::Avx2::new_unchecked() };
9393
#input_fn_name(avx2);
9494
}
9595
}
96-
} else {
97-
quote! {}
9896
};
9997

100-
let wasm_snippet = if include_wasm {
101-
quote! {
102-
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
103-
#[test]
104-
fn #wasm_name() {
105-
let wasm = unsafe { fearless_simd::wasm32::WasmSimd128::new_unchecked() };
106-
#input_fn_name(wasm);
107-
}
98+
let wasm_snippet = quote! {
99+
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
100+
#[test]
101+
#ignore_wasm
102+
fn #wasm_name() {
103+
let wasm = unsafe { fearless_simd::wasm32::WasmSimd128::new_unchecked() };
104+
#input_fn_name(wasm);
108105
}
109-
} else {
110-
quote! {}
111106
};
112107

113108
quote! {
@@ -125,8 +120,6 @@ pub fn simd_test(_: TokenStream, item: TokenStream) -> TokenStream {
125120
// You can update below functions if you want to exclude certain tests from different architectures
126121
// (for example because they haven't been implemented yet).
127122

128-
#[allow(clippy::allow_attributes, reason = "Lints only apply in some cfgs.")]
129-
#[allow(dead_code, reason = "Used only on aarch64, but always type-checked.")]
130123
fn exclude_neon(_test_name: &str) -> bool {
131124
false
132125
}
@@ -135,8 +128,6 @@ fn exclude_fallback(_test_name: &str) -> bool {
135128
false
136129
}
137130

138-
#[allow(clippy::allow_attributes, reason = "Lints only apply in some cfgs.")]
139-
#[allow(dead_code, reason = "Used only on x86(-64), but always type-checked.")]
140131
fn exclude_sse4(test_name: &str) -> bool {
141132
matches!(
142133
test_name,
@@ -145,8 +136,6 @@ fn exclude_sse4(test_name: &str) -> bool {
145136
) || test_name.contains("precise")
146137
}
147138

148-
#[allow(clippy::allow_attributes, reason = "Lints only apply in some cfgs.")]
149-
#[allow(dead_code, reason = "Used only on x86(-64), but always type-checked.")]
150139
fn exclude_avx2(test_name: &str) -> bool {
151140
matches!(
152141
test_name,
@@ -155,8 +144,6 @@ fn exclude_avx2(test_name: &str) -> bool {
155144
) || test_name.contains("precise")
156145
}
157146

158-
#[allow(clippy::allow_attributes, reason = "Lints only apply in some cfgs.")]
159-
#[allow(dead_code, reason = "Used only on wasm32, but always type-checked.")]
160147
fn exclude_wasm(test_name: &str) -> bool {
161148
matches!(
162149
test_name,

fearless_simd_tests/tests/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,47 @@ fn saturate_float_to_int<S: Simd>(simd: S) {
3737
fn generic_cast<S: Simd>(x: S::f32s) -> S::u32s {
3838
x.to_int()
3939
}
40+
41+
#[allow(clippy::allow_attributes, reason = "Only needed in some cfgs.")]
42+
#[allow(
43+
unused_variables,
44+
reason = "The constructed `Level` is only used in some cfgs."
45+
)]
46+
#[allow(
47+
dead_code,
48+
reason = "The `UNSUPPORTED_LEVEL_MESSAGE` is only used in some cfgs."
49+
)]
50+
#[test]
51+
fn supports_highest_level() {
52+
const UNSUPPORTED_LEVEL_MESSAGE: &str = "This means that some of the other tests in this run may be false positives, that is, they have been marked as succeeding even though they would actually fail if they could run.\n\
53+
When these tests are run on CI, any false positives should be caught.\n\
54+
However, please open a thread in the #simd channel on the Linebender Zulip if you see this message.\n\
55+
That would allow us to know whether it's worth us setting up the tests to run on an emulated system (such as using QEMU).";
56+
57+
let level = Level::new();
58+
59+
// When running tests locally, ensure that every SIMD level to be tested is actually supported. The tests themselves
60+
// will return early and pass if run with an unsupported SIMD level.
61+
//
62+
// We skip this on CI because some runners may not support all SIMD levels--in particular, the macOS x86_64 runner
63+
// doesn't support AVX2.
64+
if std::env::var_os("CI").is_none() {
65+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
66+
assert!(
67+
level.as_avx2().is_some(),
68+
"This machine does not support every `Level` supported by Fearless SIMD (currently AVX2 and below).\n{UNSUPPORTED_LEVEL_MESSAGE}",
69+
);
70+
71+
#[cfg(target_arch = "aarch64")]
72+
assert!(
73+
level.as_neon().is_some(),
74+
"This machine does not support every `Level` supported by Fearless SIMD (currently NEON and below).\n{UNSUPPORTED_LEVEL_MESSAGE}",
75+
);
76+
}
77+
78+
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
79+
assert!(
80+
level.as_wasm_simd128().is_some(),
81+
"This environment does not support WASM SIMD128. This should never happen, since it should always be supported if the `simd128` feature is enabled."
82+
);
83+
}

0 commit comments

Comments
 (0)