//! 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/meta/master/logo.svg",
    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
    html_root_url = "https://docs.rs/cpufeatures/0.1.2"
)]

#[cfg(all(target_arch = "aarch64", any(target_os = "linux", target_os = "macos")))]
#[doc(hidden)]
pub mod aarch64;

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86;

#[cfg(not(any(
    all(target_arch = "aarch64", any(target_os = "linux", target_os = "macos")),
    target_arch = "x86",
    target_arch = "x86_64"
)))]
compile_error!("This crate works only on `aarch64` (Linux/Mac), `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
            }
        }
    };
}
