1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! This crate provides a `no_std` version of std's [`is_x86_feature_detected!`]
//! macro.
//!
//! This is possible because x86 chips can just use the `cpuid` instruction to
//! detect CPU features, whereas most other architectures require either reading
//! files or querying the OS.
//!
//! # Usage
//!
//! ```
//! # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
//! # fn main() {
//! if core_detect::is_x86_feature_detected!("ssse3") {
//!     println!("SSSE3 is available");
//! }
//! # }
//! # #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
//! # fn main() {}
//! ```
//!
//! Note that like the [equivalent macro in `std`][stddetect], this will error
//! on architectures other than x86/x86_64, so you should put the code behind a
//! `#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]` check.
//!
//! [stddetect]:
//! https://doc.rust-lang.org/nightly/std/macro.is_x86_feature_detected.html
//!
//! (In the future, this crate may provide another macro which returns false in
//! these cases instead, and supports testing multiple features simultaneously).
//!
//! # Caveats
//! The `cpuid` instruction doesn't exist on all x86 machines, it was added
//! around 1994. (It's also not available on SGX, but this doesn't cause any
//! issues since we can check that with `cfg(target_env = "sgx")`).
//!
//! If you run `cpuid` on a machine older than that, it causes an illegal
//! instruction fault (SIGILL). Unfortunately, there's no good stable way to
//! reliably determine if `cpuid` will fault in stable rust: A
//! [`core::arch::x86::has_cpuid`](https://doc.rust-lang.org/nightly/core/arch/x86/fn.has_cpuid.html)
//! function exists, but didn't stabilize with the rest of `core::arch::x86`,
//! and the only way to implement it ourselves is with inline asm, which... is
//! also still unstable.
//!
//! For what it's worth, it's actually pretty uncommon that we'd need to call
//! `has_cpuid` on common rust targets, since we perform the following compile
//! time checks:
//! - We never have cpuid on `target_env = "sgx"` (as mentioned).
//! - We always have cpuid on `target_arch = "x86_64"`.
//! - And we always have cpuid if `target_feature = "sse"` (which covers the
//!   `i686-*` targets).
//!
//! Unfortunately, if none of those applies... we don't know if calling CPUID
//! will crash the process. This library has a few ways it can handle this:
//!
//! 1. Cautiously (the default): In this mode, we conceptually swap `has_cpuid`
//!    out with a function that always returns false. That is: we never call it
//!    unless we're sure it wont crash the process.
//!
//! 2. Recklessly (`feature = "assume_has_cpuid"`): This is essentially the
//!    opposite of the last one — assume `has_cpuid` would have returned true,
//!    and call `cpuid` anyway.
//!
//!     In practice, this should be fine. These machines are rare now (they're
//!     over 30 years old...), and pretty only are common through QEMU, and even
//!     then, usually after a misconfiguration.
//!
//!     If you do happen to run the instruction, the process crashes, but in a
//!     controlled manner — Executing an illegal instruction to tringger a
//!     SIGILL is what `core::intrinsics::abort` does on x86, so it's not
//!     dangerous or anything.
//!
//! 3. Using unstable nightly features (`feature = "unstable_has_cpuid"`): This
//!    approach requires a nightly compiler, but has no other major downsides,
//!    besides the fact that the `has_cpuid` function could vanish at any time.
//!
//! Eventually, inline asm will stabilize and we can solve this problem more
//! cleanly.
#![no_std]
#![allow(dead_code)]
#![cfg_attr(
    all(
        target_arch = "x86",
        not(target_env = "sgx"),
        not(target_feature = "sse"),
        feature = "unstable_has_cpuid",
    ),
    feature(stdsimd)
)]

#[macro_use]
mod macros;

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "arch/x86.rs"]
#[macro_use]
mod arch;

// Unimplemented architecture:
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
mod arch {
    #[doc(hidden)]
    pub(crate) enum Feature {
        Null,
    }

    #[doc(hidden)]
    pub mod __is_feature_detected {}

    impl Feature {
        #[doc(hidden)]
        pub(crate) fn from_str(_s: &str) -> Result<Feature, ()> {
            Err(())
        }
        #[doc(hidden)]
        pub(crate) fn to_str(self) -> &'static str {
            ""
        }
    }
}

pub(crate) use crate::arch::Feature;
#[doc(hidden)]
pub use crate::arch::__is_feature_detected;

/// Performs run-time feature detection.
#[inline]
#[allow(dead_code)]
fn check_for(x: Feature) -> bool {
    cache::test(x as u32)
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "os/x86.rs"]
mod os;

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
mod os {
    #[inline]
    pub(crate) fn detect_features() -> crate::cache::Initializer {
        Default::default()
    }
}

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
#[macro_export]
macro_rules! is_x86_feature_detected {
    ($t: tt) => {
        compile_error!(
            r#"
        is_x86_feature_detected can only be used on x86 and x86_64 targets.
        You can prevent it from being used in other architectures by
        guarding it behind a cfg(target_arch) as follows:

            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
                if is_x86_feature_detected(...) { ... }
            }
        "#
        )
    };
}

mod cache;