//! This crate provides macros for runtime CPU feature detection. It's intended //! as a stopgap until Rust [RFC 2725] adding first-class target feature detection //! macros to `libcore` is implemented. //! //! Supported target architectures: //! - `aarch64`: Linux and macOS/M4 only (ARM64 does not support OS-independent feature detection) //! - Target features: `aes`, `sha2`, `sha3` //! - `x86`/`x86_64`: OS independent and `no_std`-friendly //! - Target features: `adx`, `aes`, `avx`, `avx2`, `bmi1`, `bmi2`, `fma`, //! `mmx`, `pclmulqdq`, `popcnt`, `rdrand`, `rdseed`, `sgx`, `sha`, `sse`, //! `sse2`, `sse3`, `sse4.1`, `sse4.2`, `ssse3` //! //! If you would like detection support for a target feature which is not on //! this list, please [open a GitHub issue][gh]. //! //! # Example //! ``` //! # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] //! # { //! // This macro creates `cpuid_aes_sha` module //! cpufeatures::new!(cpuid_aes_sha, "aes", "sha"); //! //! // `token` is a Zero Sized Type (ZST) value, which guarantees //! // that underlying static storage got properly initialized, //! // which allows to omit initialization branch //! let token: cpuid_aes_sha::InitToken = cpuid_aes_sha::init(); //! //! if token.get() { //! println!("CPU supports both SHA and AES extensions"); //! } else { //! println!("SHA and AES extensions are not supported"); //! } //! //! // If stored value needed only once you can get stored value //! // omitting the token //! let val = cpuid_aes_sha::get(); //! assert_eq!(val, token.get()); //! //! // Additionally you can get both token and value //! let (token, val) = cpuid_aes_sha::init_get(); //! assert_eq!(val, token.get()); //! # } //! ``` //! //! Note that if all tested target features are enabled via compiler options //! (e.g. by using `RUSTFLAGS`), the `get` method will always return `true` //! and `init` will not use CPUID instruction. Such behavior allows //! compiler to completely eliminate fallback code. //! //! After first call macro caches result and returns it in subsequent //! calls, thus runtime overhead for them is minimal. //! //! [RFC 2725]: https://github.com/rust-lang/rfcs/pull/2725 //! [gh]: https://github.com/RustCrypto/utils/issues/new?title=cpufeatures:%20requesting%20support%20for%20CHANGEME%20target%20feature #![no_std] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" )] #[cfg(not(miri))] #[cfg(all(target_arch = "aarch64"))] #[doc(hidden)] pub mod aarch64; #[cfg(not(miri))] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86; #[cfg(miri)] mod miri; #[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))] compile_error!("This crate works only on `aarch64`, `x86`, and `x86-64` targets."); /// Create module with CPU feature detection code. #[macro_export] macro_rules! new { ($mod_name:ident, $($tf:tt),+ $(,)?) => { mod $mod_name { use core::sync::atomic::{AtomicU8, Ordering::Relaxed}; const UNINIT: u8 = u8::max_value(); static STORAGE: AtomicU8 = AtomicU8::new(UNINIT); /// Initialization token #[derive(Copy, Clone, Debug)] pub struct InitToken(()); impl InitToken { /// Get initialized value #[inline(always)] pub fn get(&self) -> bool { $crate::__unless_target_features! { $($tf),+ => { STORAGE.load(Relaxed) == 1 } } } } /// Initialize underlying storage if needed and get /// stored value and initialization token. #[inline] pub fn init_get() -> (InitToken, bool) { let res = $crate::__unless_target_features! { $($tf),+ => { // Relaxed ordering is fine, as we only have a single atomic variable. let val = STORAGE.load(Relaxed); if val == UNINIT { let res = $crate::__detect_target_features!($($tf),+); STORAGE.store(res as u8, Relaxed); res } else { val == 1 } } }; (InitToken(()), res) } /// Initialize underlying storage if needed and get /// initialization token. #[inline] pub fn init() -> InitToken { init_get().0 } /// Initialize underlying storage if needed and get /// stored value. #[inline] pub fn get() -> bool { init_get().1 } } }; }