Skip to main content

ic_core/
cpu.rs

1//! CPU feature detection.
2//!
3//! Lives in `ic-core` so that both the implementation crates and the ontology
4//! can consult it without either depending on the other: `ic-cipher` uses it to
5//! pick a backend, and `ic-ontology` uses it to tell an agent which backend is
6//! live.
7//!
8//! Detection is compile-time when the feature is already enabled for the build
9//! (`-C target-cpu=native`, or an explicit `-C target-feature=+aes`), and
10//! runtime otherwise. Under `no_std` only the compile-time path exists, because
11//! runtime detection needs `std`.
12//!
13//! Every query here depends only on the CPU, never on key material, so none of
14//! it is a side channel.
15
16/// Whether x86 AES-NI is available.
17///
18/// x86 only, deliberately: `ic_cipher::aes::aesni_available` is this, and it
19/// selects the x86 backend. The ARMv8 extension is [`has_armv8_aes`].
20#[inline]
21#[must_use]
22pub fn has_aes() -> bool {
23    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
24    {
25        std::arch::is_x86_feature_detected!("aes")
26    }
27    #[cfg(all(
28        any(target_arch = "x86", target_arch = "x86_64"),
29        not(feature = "std"),
30        target_feature = "aes"
31    ))]
32    {
33        true
34    }
35    #[cfg(not(all(
36        any(target_arch = "x86", target_arch = "x86_64"),
37        any(feature = "std", target_feature = "aes")
38    )))]
39    {
40        false
41    }
42}
43
44/// Whether carry-less multiplication (`PCLMULQDQ`) is available.
45///
46/// The instruction GHASH needs, but not a sufficient condition for GHASH to use
47/// it: whether it will is [`has_ghash_clmul`], which is what to ask before
48/// concluding that AES-GCM is fast.
49#[inline]
50#[must_use]
51pub fn has_pclmulqdq() -> bool {
52    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
53    {
54        std::arch::is_x86_feature_detected!("pclmulqdq")
55    }
56    #[cfg(all(
57        any(target_arch = "x86", target_arch = "x86_64"),
58        not(feature = "std"),
59        target_feature = "pclmulqdq"
60    ))]
61    {
62        true
63    }
64    #[cfg(not(all(
65        any(target_arch = "x86", target_arch = "x86_64"),
66        any(feature = "std", target_feature = "pclmulqdq")
67    )))]
68    {
69        false
70    }
71}
72
73/// Whether GHASH will run on the carry-less multiply in this build.
74///
75/// Not the same question as [`has_pclmulqdq`]. The instruction is necessary but
76/// the backend also needs `ssse3` for its byte-reversal shuffle, and it exists
77/// only on x86-64 with `std`. A 32-bit x86 build on a CPU that has every one of
78/// those instructions still runs the portable GHASH.
79///
80/// This is the predicate `ic_cipher::gcm` dispatches on and the one the
81/// ontology reports from, so the two cannot disagree. They did: the report
82/// used to ask only whether the CPU had the instruction, and told agents on
83/// 32-bit x86 that AES-GCM was fast when it ran at a fortieth of the speed of
84/// ChaCha20-Poly1305.
85#[inline]
86#[must_use]
87pub fn has_ghash_clmul() -> bool {
88    #[cfg(all(target_arch = "x86_64", feature = "std"))]
89    {
90        std::arch::is_x86_feature_detected!("pclmulqdq")
91            && std::arch::is_x86_feature_detected!("ssse3")
92    }
93    #[cfg(not(all(target_arch = "x86_64", feature = "std")))]
94    {
95        false
96    }
97}
98
99/// Whether the ARMv8 AES backend is compiled in and this CPU can run it.
100///
101/// Two conditions. The CPU must have `FEAT_AES`, and the build must have the
102/// `aarch64-crypto` feature, which `ic-cipher`'s feature of the same name turns
103/// on here. The feature exists because the ARM backends have not yet run on
104/// ARM hardware, only under emulation; without it an ARM build keeps the
105/// portable code, and this says so. `ic_cipher::aes::armv8_aes_available` is
106/// this function, so the ontology and the cipher cannot disagree.
107#[inline]
108#[must_use]
109pub fn has_armv8_aes() -> bool {
110    #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
111    {
112        std::arch::is_aarch64_feature_detected!("aes")
113    }
114    #[cfg(not(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")))]
115    {
116        false
117    }
118}
119
120/// Whether GHASH will run on `PMULL` in this build.
121///
122/// The ARM counterpart of [`has_ghash_clmul`], under the same two conditions
123/// as [`has_armv8_aes`]. `ic_cipher::gcm` dispatches on it, and the ontology
124/// reports from it.
125#[inline]
126#[must_use]
127pub fn has_ghash_pmull() -> bool {
128    #[cfg(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std"))]
129    {
130        std::arch::is_aarch64_feature_detected!("neon")
131            && std::arch::is_aarch64_feature_detected!("pmull")
132    }
133    #[cfg(not(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")))]
134    {
135        false
136    }
137}
138
139/// Whether the x86-64 wide-multiply instructions are available: `MULX` (BMI2)
140/// and the two independent carry chains of `ADCX`/`ADOX` (ADX).
141///
142/// What `ic_ec`'s four-limb Curve25519 field needs. x86-64 only: the field is
143/// written for sixteen 64-bit registers, and a 32-bit x86 build has neither
144/// those nor a 64x64 multiply to put in them. Every x86-64 CPU since Intel
145/// Broadwell (2014) and AMD Zen (2017) has both.
146#[inline]
147#[must_use]
148pub fn has_bmi2_adx() -> bool {
149    #[cfg(all(target_arch = "x86_64", feature = "std"))]
150    {
151        std::arch::is_x86_feature_detected!("bmi2") && std::arch::is_x86_feature_detected!("adx")
152    }
153    #[cfg(all(
154        target_arch = "x86_64",
155        not(feature = "std"),
156        target_feature = "bmi2",
157        target_feature = "adx"
158    ))]
159    {
160        true
161    }
162    #[cfg(not(all(
163        target_arch = "x86_64",
164        any(feature = "std", all(target_feature = "bmi2", target_feature = "adx"))
165    )))]
166    {
167        false
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    /// Detection must not panic and must be stable within a process — a
176    /// backend chosen once per key would otherwise be able to disagree with
177    /// itself.
178    #[test]
179    fn detection_is_total_and_stable() {
180        let a = has_aes();
181        let b = has_pclmulqdq();
182        let c = has_bmi2_adx();
183        let d = has_armv8_aes();
184        let e = has_ghash_pmull();
185        for _ in 0..8 {
186            assert_eq!(has_aes(), a);
187            assert_eq!(has_pclmulqdq(), b);
188            assert_eq!(has_bmi2_adx(), c);
189            assert_eq!(has_armv8_aes(), d);
190            assert_eq!(has_ghash_pmull(), e);
191        }
192    }
193
194    /// Off aarch64, or without the feature, the ARM predicates must say no.
195    #[test]
196    #[cfg(not(all(target_arch = "aarch64", feature = "aarch64-crypto", feature = "std")))]
197    fn the_arm_backends_are_absent_without_the_feature() {
198        assert!(!has_armv8_aes());
199        assert!(!has_ghash_pmull());
200    }
201
202    /// On a non-x86 target both must report false rather than guessing.
203    #[test]
204    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
205    fn non_x86_reports_no_acceleration() {
206        assert!(!has_aes());
207        assert!(!has_pclmulqdq());
208    }
209}