cpufeatures/
lib.rs

1//! This crate provides macros for runtime CPU feature detection. It's intended
2//! as a stopgap until Rust [RFC 2725] adding first-class target feature detection
3//! macros to `libcore` is implemented.
4//!
5//! Supported target architectures:
6//! - `aarch64`: Linux and macOS/M4 only (ARM64 does not support OS-independent feature detection)
7//!   - Target features: `aes`, `sha2`, `sha3`
8//! - `x86`/`x86_64`: OS independent and `no_std`-friendly
9//!   - Target features: `adx`, `aes`, `avx`, `avx2`, `bmi1`, `bmi2`, `fma`,
10//!     `mmx`, `pclmulqdq`, `popcnt`, `rdrand`, `rdseed`, `sgx`, `sha`, `sse`,
11//!     `sse2`, `sse3`, `sse4.1`, `sse4.2`, `ssse3`
12//!
13//! If you would like detection support for a target feature which is not on
14//! this list, please [open a GitHub issue][gh].
15//!
16//! # Example
17//! ```
18//! # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
19//! # {
20//! // This macro creates `cpuid_aes_sha` module
21//! cpufeatures::new!(cpuid_aes_sha, "aes", "sha");
22//!
23//! // `token` is a Zero Sized Type (ZST) value, which guarantees
24//! // that underlying static storage got properly initialized,
25//! // which allows to omit initialization branch
26//! let token: cpuid_aes_sha::InitToken = cpuid_aes_sha::init();
27//!
28//! if token.get() {
29//!     println!("CPU supports both SHA and AES extensions");
30//! } else {
31//!     println!("SHA and AES extensions are not supported");
32//! }
33//!
34//! // If stored value needed only once you can get stored value
35//! // omitting the token
36//! let val = cpuid_aes_sha::get();
37//! assert_eq!(val, token.get());
38//!
39//! // Additionally you can get both token and value
40//! let (token, val) = cpuid_aes_sha::init_get();
41//! assert_eq!(val, token.get());
42//! # }
43//! ```
44//!
45//! Note that if all tested target features are enabled via compiler options
46//! (e.g. by using `RUSTFLAGS`), the `get` method will always return `true`
47//! and `init` will not use CPUID instruction. Such behavior allows
48//! compiler to completely eliminate fallback code.
49//!
50//! After first call macro caches result and returns it in subsequent
51//! calls, thus runtime overhead for them is minimal.
52//!
53//! [RFC 2725]: https://github.com/rust-lang/rfcs/pull/2725
54//! [gh]: https://github.com/RustCrypto/utils/issues/new?title=cpufeatures:%20requesting%20support%20for%20CHANGEME%20target%20feature
55
56#![no_std]
57#![doc(
58    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
59    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
60)]
61
62#[cfg(not(miri))]
63#[cfg(all(target_arch = "aarch64"))]
64#[doc(hidden)]
65pub mod aarch64;
66
67#[cfg(not(miri))]
68#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
69mod x86;
70
71#[cfg(miri)]
72mod miri;
73
74#[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))]
75compile_error!("This crate works only on `aarch64`, `x86`, and `x86-64` targets.");
76
77/// Create module with CPU feature detection code.
78#[macro_export]
79macro_rules! new {
80    ($mod_name:ident, $($tf:tt),+ $(,)?) => {
81        mod $mod_name {
82            use core::sync::atomic::{AtomicU8, Ordering::Relaxed};
83
84            const UNINIT: u8 = u8::max_value();
85            static STORAGE: AtomicU8 = AtomicU8::new(UNINIT);
86
87            /// Initialization token
88            #[derive(Copy, Clone, Debug)]
89            pub struct InitToken(());
90
91            impl InitToken {
92                /// Get initialized value
93                #[inline(always)]
94                pub fn get(&self) -> bool {
95                    $crate::__unless_target_features! {
96                        $($tf),+ => {
97                            STORAGE.load(Relaxed) == 1
98                        }
99                    }
100                }
101            }
102
103            /// Initialize underlying storage if needed and get
104            /// stored value and initialization token.
105            #[inline]
106            pub fn init_get() -> (InitToken, bool) {
107                let res = $crate::__unless_target_features! {
108                    $($tf),+ => {
109                        // Relaxed ordering is fine, as we only have a single atomic variable.
110                        let val = STORAGE.load(Relaxed);
111
112                        if val == UNINIT {
113                            let res = $crate::__detect_target_features!($($tf),+);
114                            STORAGE.store(res as u8, Relaxed);
115                            res
116                        } else {
117                            val == 1
118                        }
119                    }
120                };
121
122                (InitToken(()), res)
123            }
124
125            /// Initialize underlying storage if needed and get
126            /// initialization token.
127            #[inline]
128            pub fn init() -> InitToken {
129                init_get().0
130            }
131
132            /// Initialize underlying storage if needed and get
133            /// stored value.
134            #[inline]
135            pub fn get() -> bool {
136                init_get().1
137            }
138        }
139    };
140}