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}