| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| #[derive(Clone, Copy)] |
| pub(crate) struct CpuInfo(u32); |
| |
| impl CpuInfo { |
| const INIT: u32 = 0; |
| |
| #[inline] |
| fn set(&mut self, bit: u32) { |
| self.0 = set(self.0, bit); |
| } |
| #[inline] |
| fn test(self, bit: u32) -> bool { |
| test(self.0, bit) |
| } |
| } |
| |
| #[inline] |
| fn set(x: u32, bit: u32) -> u32 { |
| x | 1 << bit |
| } |
| #[inline] |
| fn test(x: u32, bit: u32) -> bool { |
| x & (1 << bit) != 0 |
| } |
| |
| #[inline] |
| pub(crate) fn detect() -> CpuInfo { |
| use core::sync::atomic::{AtomicU32, Ordering}; |
| |
| static CACHE: AtomicU32 = AtomicU32::new(0); |
| let mut info = CpuInfo(CACHE.load(Ordering::Relaxed)); |
| if info.0 != 0 { |
| return info; |
| } |
| info.set(CpuInfo::INIT); |
| // Note: detect_false cfg is intended to make it easy for portable-atomic developers to |
| // test cases such as has_cmpxchg16b == false, has_lse == false, |
| // __kuser_helper_version < 5, etc., and is not a public API. |
| if !cfg!(portable_atomic_test_outline_atomics_detect_false) { |
| _detect(&mut info); |
| } |
| CACHE.store(info.0, Ordering::Relaxed); |
| info |
| } |
| |
| macro_rules! flags { |
| ($( |
| $(#[$attr:meta])* |
| $flag:ident ($shift:literal, $func:ident, $name:literal, $cfg:meta), |
| )*) => { |
| impl CpuInfo { |
| $( |
| $(#[$attr])* |
| const $flag: u32 = $shift; |
| $(#[$attr])* |
| #[cfg(any(test, not($cfg)))] |
| #[inline] |
| pub(crate) fn $func(self) -> bool { |
| self.test(Self::$flag) |
| } |
| )* |
| #[cfg(test)] // for test |
| const ALL_FLAGS: &'static [(&'static str, u32, bool)] = &[$( |
| ($name, Self::$flag, cfg!($cfg)), |
| )*]; |
| } |
| }; |
| } |
| |
| #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))] |
| flags! { |
| // FEAT_LSE, Large System Extensions |
| // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv8-1-architecture-extension |
| // > This feature is supported in AArch64 state only. |
| // > FEAT_LSE is OPTIONAL from Armv8.0. |
| // > FEAT_LSE is mandatory from Armv8.1. |
| HAS_LSE(1, has_lse, "lse", any(target_feature = "lse", portable_atomic_target_feature = "lse")), |
| // FEAT_LSE2, Large System Extensions version 2 |
| // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv8-4-architecture-extension |
| // > This feature is supported in AArch64 state only. |
| // > FEAT_LSE2 is OPTIONAL from Armv8.2. |
| // > FEAT_LSE2 is mandatory from Armv8.4. |
| #[cfg_attr(not(test), allow(dead_code))] |
| HAS_LSE2(2, has_lse2, "lse2", any(target_feature = "lse2", portable_atomic_target_feature = "lse2")), |
| // FEAT_LRCPC3, Load-Acquire RCpc instructions version 3 |
| // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv8-9-architecture-extension |
| // > This feature is supported in AArch64 state only. |
| // > FEAT_LRCPC3 is OPTIONAL from Armv8.2. |
| // > If FEAT_LRCPC3 is implemented, then FEAT_LRCPC2 is implemented. |
| #[cfg_attr(not(test), allow(dead_code))] |
| HAS_RCPC3(3, has_rcpc3, "rcpc3", any(target_feature = "rcpc3", portable_atomic_target_feature = "rcpc3")), |
| // FEAT_LSE128, 128-bit Atomics |
| // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv9-4-architecture-extension |
| // > This feature is supported in AArch64 state only. |
| // > FEAT_LSE128 is OPTIONAL from Armv9.3. |
| // > If FEAT_LSE128 is implemented, then FEAT_LSE is implemented. |
| #[cfg_attr(not(test), allow(dead_code))] |
| HAS_LSE128(4, has_lse128, "lse128", any(target_feature = "lse128", portable_atomic_target_feature = "lse128")), |
| } |
| |
| #[cfg(target_arch = "powerpc64")] |
| flags! { |
| // lqarx and stqcx. |
| HAS_QUADWORD_ATOMICS(1, has_quadword_atomics, "quadword-atomics", any(target_feature = "quadword-atomics", portable_atomic_target_feature = "quadword-atomics")), |
| } |
| |
| #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] |
| flags! { |
| // amocas.{w,d,q} |
| HAS_ZACAS(1, has_zacas, "zacas", any(target_feature = "experimental-zacas", portable_atomic_target_feature = "experimental-zacas")), |
| } |
| |
| #[cfg(target_arch = "x86_64")] |
| flags! { |
| // cmpxchg16b |
| HAS_CMPXCHG16B(1, has_cmpxchg16b, "cmpxchg16b", any(target_feature = "cmpxchg16b", portable_atomic_target_feature = "cmpxchg16b")), |
| // atomic vmovdqa |
| #[cfg(target_feature = "sse")] |
| HAS_VMOVDQA_ATOMIC(2, has_vmovdqa_atomic, "vmovdqa-atomic", any(/* always false */)), |
| } |
| |
| // core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47 |
| #[cfg(not(target_arch = "x86_64"))] |
| #[cfg(not(windows))] |
| #[allow(dead_code, non_camel_case_types)] |
| mod c_types { |
| pub(crate) type c_void = core::ffi::c_void; |
| // c_{,u}int is {i,u}32 on non-16-bit architectures |
| // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L147 |
| // (16-bit architectures currently don't use this module) |
| pub(crate) type c_int = i32; |
| pub(crate) type c_uint = u32; |
| // c_{,u}long is {i,u}64 on non-Windows 64-bit targets, otherwise is {i,u}32 |
| // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L159 |
| // (Windows currently doesn't use this module - this module is cfg(not(windows))) |
| #[cfg(target_pointer_width = "64")] |
| pub(crate) type c_long = i64; |
| #[cfg(not(target_pointer_width = "64"))] |
| pub(crate) type c_long = i32; |
| #[cfg(target_pointer_width = "64")] |
| pub(crate) type c_ulong = u64; |
| #[cfg(not(target_pointer_width = "64"))] |
| pub(crate) type c_ulong = u32; |
| // c_size_t is currently always usize |
| // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L67 |
| pub(crate) type c_size_t = usize; |
| // c_char is u8 by default on most non-Apple/non-Windows Arm/PowerPC/RISC-V/s390x/Hexagon targets |
| // (Linux/Android/FreeBSD/NetBSD/OpenBSD/VxWorks/Fuchsia/QNX Neutrino/Horizon/AIX/z/OS) |
| // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L83 |
| // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/lldb/source/Utility/ArchSpec.cpp#L712 |
| // RISC-V https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/draft-20240829-13bfa9f54634cb60d86b9b333e109f077805b4b3/riscv-cc.adoc#cc-type-representations |
| // Hexagon https://lists.llvm.org/pipermail/llvm-dev/attachments/20190916/21516a52/attachment-0001.pdf |
| // AIX https://www.ibm.com/docs/en/xl-c-aix/13.1.3?topic=specifiers-character-types |
| // z/OS https://www.ibm.com/docs/en/zos/3.1.0?topic=specifiers-character-types |
| // (Windows currently doesn't use this module) |
| #[cfg(not(target_vendor = "apple"))] |
| pub(crate) type c_char = u8; |
| // c_char is i8 on all Apple targets |
| #[cfg(target_vendor = "apple")] |
| pub(crate) type c_char = i8; |
| |
| // Static assertions for C type definitions. |
| #[cfg(test)] |
| const _: fn() = || { |
| use test_helper::{libc, sys}; |
| let _: c_int = 0 as std::os::raw::c_int; |
| let _: c_uint = 0 as std::os::raw::c_uint; |
| let _: c_long = 0 as std::os::raw::c_long; |
| let _: c_ulong = 0 as std::os::raw::c_ulong; |
| let _: c_size_t = 0 as libc::size_t; // std::os::raw::c_size_t is unstable |
| #[cfg(not(any( |
| all(target_arch = "aarch64", target_os = "illumos"), // TODO: https://github.com/rust-lang/rust/issues/129945 |
| all(target_arch = "riscv64", target_os = "android"), // TODO: https://github.com/rust-lang/rust/issues/129945 |
| )))] |
| let _: c_char = 0 as std::os::raw::c_char; |
| let _: c_char = 0 as sys::c_char; |
| }; |
| } |
| |
| #[allow( |
| clippy::alloc_instead_of_core, |
| clippy::std_instead_of_alloc, |
| clippy::std_instead_of_core, |
| clippy::undocumented_unsafe_blocks, |
| clippy::wildcard_imports |
| )] |
| #[cfg(test)] |
| mod tests_common { |
| use std::{collections::BTreeSet, vec}; |
| |
| use super::*; |
| |
| #[test] |
| fn test_bit_flags() { |
| let mut flags = vec![("init", CpuInfo::INIT)]; |
| flags.extend(CpuInfo::ALL_FLAGS.iter().map(|&(name, flag, _)| (name, flag))); |
| let flag_set = flags.iter().map(|(_, flag)| flag).collect::<BTreeSet<_>>(); |
| let name_set = flags.iter().map(|(_, flag)| flag).collect::<BTreeSet<_>>(); |
| if flag_set.len() != flags.len() { |
| panic!("CpuInfo flag values must be unique") |
| } |
| if name_set.len() != flags.len() { |
| panic!("CpuInfo flag names must be unique") |
| } |
| |
| let mut x = CpuInfo(0); |
| for &(_, f) in &flags { |
| assert!(!x.test(f)); |
| } |
| for i in 0..flags.len() { |
| x.set(flags[i].1); |
| for &(_, f) in &flags[..i + 1] { |
| assert!(x.test(f)); |
| } |
| for &(_, f) in &flags[i + 1..] { |
| assert!(!x.test(f)); |
| } |
| } |
| for &(_, f) in &flags { |
| assert!(x.test(f)); |
| } |
| } |
| |
| #[test] |
| fn print_features() { |
| use std::{ |
| fmt::Write as _, |
| io::{self, Write}, |
| string::String, |
| }; |
| |
| let mut features = String::new(); |
| features.push_str("\nfeatures:\n"); |
| for &(name, flag, compile_time) in CpuInfo::ALL_FLAGS { |
| let run_time = detect().test(flag); |
| if run_time == compile_time { |
| let _ = writeln!(features, " {}: {}", name, run_time); |
| } else { |
| let _ = writeln!( |
| features, |
| " {}: {} (compile-time), {} (run-time)", |
| name, compile_time, run_time |
| ); |
| } |
| } |
| let stdout = io::stderr(); |
| let mut stdout = stdout.lock(); |
| let _ = stdout.write_all(features.as_bytes()); |
| } |
| |
| #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))] |
| #[test] |
| #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)] |
| fn test_detect() { |
| let proc_cpuinfo = test_helper::cpuinfo::ProcCpuinfo::new(); |
| if detect().has_lse() { |
| assert!(detect().test(CpuInfo::HAS_LSE)); |
| if let Ok(proc_cpuinfo) = proc_cpuinfo { |
| assert!(proc_cpuinfo.lse); |
| } |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_LSE)); |
| if let Ok(proc_cpuinfo) = proc_cpuinfo { |
| assert!(!proc_cpuinfo.lse); |
| } |
| } |
| if detect().has_lse2() { |
| assert!(detect().test(CpuInfo::HAS_LSE)); |
| assert!(detect().test(CpuInfo::HAS_LSE2)); |
| if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse2: Some(lse2), .. }) = proc_cpuinfo { |
| assert!(lse2); |
| } |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_LSE2)); |
| if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse2: Some(lse2), .. }) = proc_cpuinfo { |
| assert!(!lse2); |
| } |
| } |
| if detect().has_lse128() { |
| assert!(detect().test(CpuInfo::HAS_LSE)); |
| assert!(detect().test(CpuInfo::HAS_LSE2)); |
| assert!(detect().test(CpuInfo::HAS_LSE128)); |
| if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse128: Some(lse128), .. }) = proc_cpuinfo |
| { |
| assert!(lse128); |
| } |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_LSE128)); |
| if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse128: Some(lse128), .. }) = proc_cpuinfo |
| { |
| assert!(!lse128); |
| } |
| } |
| if detect().has_rcpc3() { |
| assert!(detect().test(CpuInfo::HAS_RCPC3)); |
| if let Ok(test_helper::cpuinfo::ProcCpuinfo { rcpc3: Some(rcpc3), .. }) = proc_cpuinfo { |
| assert!(rcpc3); |
| } |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_RCPC3)); |
| if let Ok(test_helper::cpuinfo::ProcCpuinfo { rcpc3: Some(rcpc3), .. }) = proc_cpuinfo { |
| assert!(!rcpc3); |
| } |
| } |
| } |
| #[cfg(target_arch = "powerpc64")] |
| #[test] |
| #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)] |
| fn test_detect() { |
| let proc_cpuinfo = test_helper::cpuinfo::ProcCpuinfo::new(); |
| if detect().has_quadword_atomics() { |
| assert!(detect().test(CpuInfo::HAS_QUADWORD_ATOMICS)); |
| if let Ok(proc_cpuinfo) = proc_cpuinfo { |
| assert!(proc_cpuinfo.power8); |
| } |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_QUADWORD_ATOMICS)); |
| if let Ok(proc_cpuinfo) = proc_cpuinfo { |
| assert!(!proc_cpuinfo.power8); |
| } |
| } |
| } |
| #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] |
| #[test] |
| #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)] |
| fn test_detect() { |
| if detect().has_zacas() { |
| assert!(detect().test(CpuInfo::HAS_ZACAS)); |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_ZACAS)); |
| } |
| } |
| #[cfg(target_arch = "x86_64")] |
| #[test] |
| #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)] |
| fn test_detect() { |
| if detect().has_cmpxchg16b() { |
| assert!(detect().test(CpuInfo::HAS_CMPXCHG16B)); |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_CMPXCHG16B)); |
| } |
| if detect().has_vmovdqa_atomic() { |
| assert!(detect().test(CpuInfo::HAS_VMOVDQA_ATOMIC)); |
| } else { |
| assert!(!detect().test(CpuInfo::HAS_VMOVDQA_ATOMIC)); |
| } |
| } |
| } |