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. 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/// The instruction GHASH needs, but not a sufficient condition for GHASH to use
51/// it: whether it will is [`has_ghash_clmul`], which is what to ask before
52/// concluding that AES-GCM is fast.
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/// Whether GHASH will run on the carry-less multiply in this build.
78///
79/// Not the same question as [`has_pclmulqdq`]. The instruction is necessary but
80/// the backend also needs `ssse3` for its byte-reversal shuffle, and it exists
81/// only on x86-64 with `std`. A 32-bit x86 build on a CPU that has every one of
82/// those instructions still runs the portable GHASH.
83///
84/// This is the predicate `ic_cipher::gcm` dispatches on and the one the
85/// ontology reports from, so the two cannot disagree. They did: the report
86/// used to ask only whether the CPU had the instruction, and told agents on
87/// 32-bit x86 that AES-GCM was fast when it ran at a fortieth of the speed of
88/// ChaCha20-Poly1305.
89#[inline]
90#[must_use]
91pub fn has_ghash_clmul() -> bool {
92    #[cfg(all(target_arch = "x86_64", feature = "std"))]
93    {
94        std::arch::is_x86_feature_detected!("pclmulqdq")
95            && std::arch::is_x86_feature_detected!("ssse3")
96    }
97    #[cfg(not(all(target_arch = "x86_64", feature = "std")))]
98    {
99        false
100    }
101}
102
103/// Whether the x86-64 wide-multiply instructions are available: `MULX` (BMI2)
104/// and the two independent carry chains of `ADCX`/`ADOX` (ADX).
105///
106/// What `ic_ec`'s four-limb Curve25519 field needs. x86-64 only: the field is
107/// written for sixteen 64-bit registers, and a 32-bit x86 build has neither
108/// those nor a 64x64 multiply to put in them. Every x86-64 CPU since Intel
109/// Broadwell (2014) and AMD Zen (2017) has both.
110#[inline]
111#[must_use]
112pub fn has_bmi2_adx() -> bool {
113    #[cfg(all(target_arch = "x86_64", feature = "std"))]
114    {
115        std::arch::is_x86_feature_detected!("bmi2") && std::arch::is_x86_feature_detected!("adx")
116    }
117    #[cfg(all(
118        target_arch = "x86_64",
119        not(feature = "std"),
120        target_feature = "bmi2",
121        target_feature = "adx"
122    ))]
123    {
124        true
125    }
126    #[cfg(not(all(
127        target_arch = "x86_64",
128        any(feature = "std", all(target_feature = "bmi2", target_feature = "adx"))
129    )))]
130    {
131        false
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    /// Detection must not panic and must be stable within a process — a
140    /// backend chosen once per key would otherwise be able to disagree with
141    /// itself.
142    #[test]
143    fn detection_is_total_and_stable() {
144        let a = has_aes();
145        let b = has_pclmulqdq();
146        let c = has_bmi2_adx();
147        for _ in 0..8 {
148            assert_eq!(has_aes(), a);
149            assert_eq!(has_pclmulqdq(), b);
150            assert_eq!(has_bmi2_adx(), c);
151        }
152    }
153
154    /// On a non-x86 target both must report false rather than guessing.
155    #[test]
156    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
157    fn non_x86_reports_no_acceleration() {
158        assert!(!has_aes());
159        assert!(!has_pclmulqdq());
160    }
161}