core_detect/
lib.rs

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