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. The ARMv8 cryptographic extension is detected in
19/// `ic_cipher::aes::armv8_aes_available`, because selecting it also depends on
20/// a cargo feature that this crate has no business knowing about. Reporting it
21/// here would additionally make the ontology call an ARM build
22/// `hardware-accelerated`, which would be an overclaim: that label means the
23/// cipher and the carry-less multiply, and there is no `PMULL` GHASH backend.
24#[inline]
25#[must_use]
26pub fn has_aes() -> bool {
27 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
28 {
29 std::arch::is_x86_feature_detected!("aes")
30 }
31 #[cfg(all(
32 any(target_arch = "x86", target_arch = "x86_64"),
33 not(feature = "std"),
34 target_feature = "aes"
35 ))]
36 {
37 true
38 }
39 #[cfg(not(all(
40 any(target_arch = "x86", target_arch = "x86_64"),
41 any(feature = "std", target_feature = "aes")
42 )))]
43 {
44 false
45 }
46}
47
48/// Whether carry-less multiplication (`PCLMULQDQ`) is available.
49///
50/// This is what GHASH needs; AES-GCM is only fast when *both* this and
51/// [`has_aes`] are true, because the portable GHASH costs far more per block
52/// than the portable AES does.
53#[inline]
54#[must_use]
55pub fn has_pclmulqdq() -> bool {
56 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
57 {
58 std::arch::is_x86_feature_detected!("pclmulqdq")
59 }
60 #[cfg(all(
61 any(target_arch = "x86", target_arch = "x86_64"),
62 not(feature = "std"),
63 target_feature = "pclmulqdq"
64 ))]
65 {
66 true
67 }
68 #[cfg(not(all(
69 any(target_arch = "x86", target_arch = "x86_64"),
70 any(feature = "std", target_feature = "pclmulqdq")
71 )))]
72 {
73 false
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 /// Detection must not panic and must be stable within a process — a
82 /// backend chosen once per key would otherwise be able to disagree with
83 /// itself.
84 #[test]
85 fn detection_is_total_and_stable() {
86 let a = has_aes();
87 let b = has_pclmulqdq();
88 for _ in 0..8 {
89 assert_eq!(has_aes(), a);
90 assert_eq!(has_pclmulqdq(), b);
91 }
92 }
93
94 /// On a non-x86 target both must report false rather than guessing.
95 #[test]
96 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
97 fn non_x86_reports_no_acceleration() {
98 assert!(!has_aes());
99 assert!(!has_pclmulqdq());
100 }
101}