diff --git a/cpufeatures/src/aarch64.rs b/cpufeatures/src/aarch64.rs index 24386621..f4d772c6 100644 --- a/cpufeatures/src/aarch64.rs +++ b/cpufeatures/src/aarch64.rs @@ -56,7 +56,7 @@ macro_rules! __expand_check_macro { macro_rules! check { $( ($hwcaps:expr, $name) => { - (($hwcaps & $crate::aarch64::hwcaps::$hwcap) != 0) + (($hwcaps & $crate::aarch64::hwcaps::$hwcap) == $crate::aarch64::hwcaps::$hwcap) }; )* } diff --git a/cpufeatures/src/x86.rs b/cpufeatures/src/x86.rs index bf857729..a32f2bc3 100644 --- a/cpufeatures/src/x86.rs +++ b/cpufeatures/src/x86.rs @@ -94,7 +94,12 @@ macro_rules! __expand_check_macro { "zmm" => $crate::__xgetbv!($cr, 0b1110_0110), _ => true, }; - reg_cap + reg_cap & $crate::check!(@cpuid $cr, $name) + }}; + + // CPU feature bits are checked separately so tests can supply synthetic capabilities. + (@cpuid $cr:expr, $name) => {{ + true $( & ($cr[$i].$reg & (1 << $offset) != 0) )* @@ -104,6 +109,8 @@ macro_rules! __expand_check_macro { }; } +// Rust enables FMA and F16C with every AVX-512 target feature, but AVX-512F +// does not guarantee either extension. Each AVX-512 entry requires both bits. __expand_check_macro! { ("sse3", "", 0, ecx, 0), ("pclmulqdq", "", 0, ecx, 1), @@ -124,24 +131,24 @@ __expand_check_macro! { ("bmi1", "", 1, ebx, 3), ("bmi2", "", 1, ebx, 8), ("avx2", "ymm", 1, ebx, 5, 0, ecx, 28), - ("avx512f", "zmm", 1, ebx, 16), - ("avx512dq", "zmm", 1, ebx, 17), + ("avx512f", "zmm", 1, ebx, 16, 0, ecx, 12, 0, ecx, 29), + ("avx512dq", "zmm", 1, ebx, 17, 0, ecx, 12, 0, ecx, 29), ("rdseed", "", 1, ebx, 18), ("adx", "", 1, ebx, 19), - ("avx512ifma", "zmm", 1, ebx, 21), - ("avx512pf", "zmm", 1, ebx, 26), - ("avx512er", "zmm", 1, ebx, 27), - ("avx512cd", "zmm", 1, ebx, 28), + ("avx512ifma", "zmm", 1, ebx, 21, 0, ecx, 12, 0, ecx, 29), + ("avx512pf", "zmm", 1, ebx, 26, 0, ecx, 12, 0, ecx, 29), + ("avx512er", "zmm", 1, ebx, 27, 0, ecx, 12, 0, ecx, 29), + ("avx512cd", "zmm", 1, ebx, 28, 0, ecx, 12, 0, ecx, 29), ("sha", "", 1, ebx, 29), - ("avx512bw", "zmm", 1, ebx, 30), - ("avx512vl", "zmm", 1, ebx, 31), - ("avx512vbmi", "zmm", 1, ecx, 1), - ("avx512vbmi2", "zmm", 1, ecx, 6), + ("avx512bw", "zmm", 1, ebx, 30, 0, ecx, 12, 0, ecx, 29), + ("avx512vl", "zmm", 1, ebx, 31, 0, ecx, 12, 0, ecx, 29), + ("avx512vbmi", "zmm", 1, ecx, 1, 0, ecx, 12, 0, ecx, 29), + ("avx512vbmi2", "zmm", 1, ecx, 6, 0, ecx, 12, 0, ecx, 29), ("gfni", "zmm", 1, ecx, 8), ("vaes", "zmm", 1, ecx, 9), ("vpclmulqdq", "zmm", 1, ecx, 10), - ("avx512bitalg", "zmm", 1, ecx, 12), - ("avx512vpopcntdq", "zmm", 1, ecx, 14), + ("avx512bitalg", "zmm", 1, ecx, 12, 0, ecx, 12, 0, ecx, 29), + ("avx512vpopcntdq", "zmm", 1, ecx, 14, 0, ecx, 12, 0, ecx, 29), ("sha512", "ymm", 2, eax, 0), ("sm3", "xmm", 2, eax, 1), diff --git a/cpufeatures/tests/aarch64.rs b/cpufeatures/tests/aarch64.rs index 41a61233..9f4d97ba 100644 --- a/cpufeatures/tests/aarch64.rs +++ b/cpufeatures/tests/aarch64.rs @@ -15,3 +15,42 @@ fn init_get() { let (token, val) = armcaps::init_get(); assert_eq!(val, token.get()); } + +#[cfg(all(not(miri), any(target_os = "linux", target_os = "android")))] +mod capability_masks { + fn requires_both(detect: impl Fn(u64) -> bool, first: u64, second: u64) { + for extra in [0, libc::HWCAP_FP | libc::HWCAP_ASIMD] { + assert!(!detect(extra)); + assert!(!detect(extra | first)); + assert!(!detect(extra | second)); + assert!(detect(extra | first | second)); + } + } + + #[test] + fn aes_requires_aes_and_pmull() { + requires_both( + |caps| cpufeatures::check!(caps, "aes"), + libc::HWCAP_AES, + libc::HWCAP_PMULL, + ); + } + + #[test] + fn sha3_requires_sha3_and_sha512() { + requires_both( + |caps| cpufeatures::check!(caps, "sha3"), + libc::HWCAP_SHA3, + libc::HWCAP_SHA512, + ); + } + + #[test] + fn sm4_requires_sm3_and_sm4() { + requires_both( + |caps| cpufeatures::check!(caps, "sm4"), + libc::HWCAP_SM3, + libc::HWCAP_SM4, + ); + } +} diff --git a/cpufeatures/tests/x86.rs b/cpufeatures/tests/x86.rs index 8b1692b2..2951d877 100644 --- a/cpufeatures/tests/x86.rs +++ b/cpufeatures/tests/x86.rs @@ -15,3 +15,79 @@ fn init_get() { let (token, val) = cpuid::init_get(); assert_eq!(val, token.get()); } + +#[cfg(not(miri))] +mod capability_masks { + #[cfg(target_arch = "x86")] + use core::arch::x86::CpuidResult; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::CpuidResult; + + const FMA: u32 = 1 << 12; + const F16C: u32 = 1 << 29; + const ALL_BITS: [CpuidResult; 3] = [CpuidResult { + eax: u32::MAX, + ebx: u32::MAX, + ecx: u32::MAX, + edx: u32::MAX, + }; 3]; + + macro_rules! avx512_prerequisites { + ($(($name:ident, $feature:tt)),+ $(,)?) => { + $( + #[test] + fn $name() { + for flags in [0, FMA, F16C, FMA | F16C] { + let mut registers = ALL_BITS; + registers[0].ecx = (registers[0].ecx & !(FMA | F16C)) | flags; + assert_eq!( + cpufeatures::check!(@cpuid registers, $feature), + flags == FMA | F16C, + ); + } + } + )+ + }; + } + + avx512_prerequisites! { + (avx512f, "avx512f"), + (avx512dq, "avx512dq"), + (avx512ifma, "avx512ifma"), + (avx512pf, "avx512pf"), + (avx512er, "avx512er"), + (avx512cd, "avx512cd"), + (avx512bw, "avx512bw"), + (avx512vl, "avx512vl"), + (avx512vbmi, "avx512vbmi"), + (avx512vbmi2, "avx512vbmi2"), + (avx512bitalg, "avx512bitalg"), + (avx512vpopcntdq, "avx512vpopcntdq"), + } + + #[test] + fn other_zmm_features_do_not_require_fma_or_f16c() { + let mut registers = ALL_BITS; + registers[0].ecx &= !(FMA | F16C); + assert!(cpufeatures::check!(@cpuid registers, "gfni")); + assert!(cpufeatures::check!(@cpuid registers, "vaes")); + assert!(cpufeatures::check!(@cpuid registers, "vpclmulqdq")); + } + + #[test] + fn check_requires_cpu_bits() { + let mut registers = ALL_BITS; + assert!(cpufeatures::check!(registers, "sha")); + registers[1].ebx &= !(1 << 29); + assert!(!cpufeatures::check!(registers, "sha")); + } + + #[test] + fn avx512_requires_xsave_and_osxsave() { + for bit in [26, 27] { + let mut registers = ALL_BITS; + registers[0].ecx &= !(1 << bit); + assert!(!cpufeatures::check!(registers, "avx512f")); + } + } +}