ring/
cpu.rs

1// Copyright 2016 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15/// A witness indicating that CPU features have been detected and cached.
16///
17/// TODO: Eventually all feature detection logic should be done through
18/// functions that accept a `Features` parameter, to guarantee that nothing
19/// tries to read the cached values before they are written.
20///
21/// This is a zero-sized type so that it can be "stored" wherever convenient.
22#[derive(Copy, Clone)]
23pub(crate) struct Features(());
24
25#[inline(always)]
26pub(crate) fn features() -> Features {
27    // We don't do runtime feature detection on aarch64-apple-* as all AAarch64
28    // features we use are available on every device since the first devices.
29    #[cfg(any(
30        target_arch = "x86",
31        target_arch = "x86_64",
32        all(
33            any(target_arch = "aarch64", target_arch = "arm"),
34            any(target_os = "android", target_os = "fuchsia", target_os = "linux")
35        )
36    ))]
37    {
38        static INIT: spin::Once<()> = spin::Once::new();
39        let () = INIT.call_once(|| {
40            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
41            {
42                extern "C" {
43                    fn GFp_cpuid_setup();
44                }
45                unsafe {
46                    GFp_cpuid_setup();
47                }
48            }
49
50            #[cfg(all(
51                any(target_arch = "aarch64", target_arch = "arm"),
52                any(target_os = "android", target_os = "fuchsia", target_os = "linux")
53            ))]
54            {
55                arm::setup();
56            }
57        });
58    }
59
60    Features(())
61}
62
63pub(crate) mod arm {
64    #[cfg(all(
65        any(target_os = "android", target_os = "linux"),
66        any(target_arch = "aarch64", target_arch = "arm")
67    ))]
68    pub fn setup() {
69        use libc::c_ulong;
70
71        // XXX: The `libc` crate doesn't provide `libc::getauxval` consistently
72        // across all Android/Linux targets, e.g. musl.
73        extern "C" {
74            fn getauxval(type_: c_ulong) -> c_ulong;
75        }
76
77        const AT_HWCAP: c_ulong = 16;
78
79        #[cfg(target_arch = "aarch64")]
80        const HWCAP_NEON: c_ulong = 1 << 1;
81
82        #[cfg(target_arch = "arm")]
83        const HWCAP_NEON: c_ulong = 1 << 12;
84
85        let caps = unsafe { getauxval(AT_HWCAP) };
86
87        // We assume NEON is available on AARCH64 because it is a required
88        // feature.
89        #[cfg(target_arch = "aarch64")]
90        debug_assert!(caps & HWCAP_NEON == HWCAP_NEON);
91
92        // OpenSSL and BoringSSL don't enable any other features if NEON isn't
93        // available.
94        if caps & HWCAP_NEON == HWCAP_NEON {
95            let mut features = NEON.mask;
96
97            #[cfg(target_arch = "aarch64")]
98            const OFFSET: c_ulong = 3;
99
100            #[cfg(target_arch = "arm")]
101            const OFFSET: c_ulong = 0;
102
103            #[cfg(target_arch = "arm")]
104            let caps = {
105                const AT_HWCAP2: c_ulong = 26;
106                unsafe { getauxval(AT_HWCAP2) }
107            };
108
109            const HWCAP_AES: c_ulong = 1 << 0 + OFFSET;
110            const HWCAP_PMULL: c_ulong = 1 << 1 + OFFSET;
111            const HWCAP_SHA2: c_ulong = 1 << 3 + OFFSET;
112
113            if caps & HWCAP_AES == HWCAP_AES {
114                features |= AES.mask;
115            }
116            if caps & HWCAP_PMULL == HWCAP_PMULL {
117                features |= PMULL.mask;
118            }
119            if caps & HWCAP_SHA2 == HWCAP_SHA2 {
120                features |= SHA256.mask;
121            }
122
123            unsafe { GFp_armcap_P = features };
124        }
125    }
126
127    #[cfg(all(target_os = "fuchsia", target_arch = "aarch64"))]
128    pub fn setup() {
129        type zx_status_t = i32;
130
131        #[link(name = "zircon")]
132        extern "C" {
133            fn zx_system_get_features(kind: u32, features: *mut u32) -> zx_status_t;
134        }
135
136        const ZX_OK: i32 = 0;
137        const ZX_FEATURE_KIND_CPU: u32 = 0;
138        const ZX_ARM64_FEATURE_ISA_ASIMD: u32 = 1 << 2;
139        const ZX_ARM64_FEATURE_ISA_AES: u32 = 1 << 3;
140        const ZX_ARM64_FEATURE_ISA_PMULL: u32 = 1 << 4;
141        const ZX_ARM64_FEATURE_ISA_SHA2: u32 = 1 << 6;
142
143        let mut caps = 0;
144        let rc = unsafe { zx_system_get_features(ZX_FEATURE_KIND_CPU, &mut caps) };
145
146        // OpenSSL and BoringSSL don't enable any other features if NEON isn't
147        // available.
148        if rc == ZX_OK && (caps & ZX_ARM64_FEATURE_ISA_ASIMD == ZX_ARM64_FEATURE_ISA_ASIMD) {
149            let mut features = NEON.mask;
150
151            if caps & ZX_ARM64_FEATURE_ISA_AES == ZX_ARM64_FEATURE_ISA_AES {
152                features |= AES.mask;
153            }
154            if caps & ZX_ARM64_FEATURE_ISA_PMULL == ZX_ARM64_FEATURE_ISA_PMULL {
155                features |= PMULL.mask;
156            }
157            if caps & ZX_ARM64_FEATURE_ISA_SHA2 == ZX_ARM64_FEATURE_ISA_SHA2 {
158                features |= 1 << 4;
159            }
160
161            unsafe { GFp_armcap_P = features };
162        }
163    }
164
165    macro_rules! features {
166        {
167            $(
168                $name:ident {
169                    mask: $mask:expr,
170
171                    /// Should we assume that the feature is always available
172                    /// for aarch64-apple-* targets? The first AArch64 iOS
173                    /// device used the Apple A7 chip.
174                    // TODO: When we can use `if` in const expressions:
175                    // ```
176                    // aarch64_apple: $aarch64_apple,
177                    // ```
178                    aarch64_apple: true,
179                }
180            ),+
181            , // trailing comma is required.
182        } => {
183            $(
184                #[allow(dead_code)]
185                pub(crate) const $name: Feature = Feature {
186                    mask: $mask,
187                };
188            )+
189
190            // TODO: When we can use `if` in const expressions, do this:
191            // ```
192            // const ARMCAP_STATIC: u32 = 0
193            //    $(
194            //        | ( if $aarch64_apple &&
195            //               cfg!(all(target_arch = "aarch64",
196            //                        target_vendor = "apple")) {
197            //                $name.mask
198            //            } else {
199            //                0
200            //            }
201            //          )
202            //    )+;
203            // ```
204            //
205            // TODO: Add static feature detection to other targets.
206            // TODO: Combine static feature detection with runtime feature
207            //       detection.
208            #[cfg(all(target_arch = "aarch64", target_vendor = "apple"))]
209            const ARMCAP_STATIC: u32 = 0
210                $(  | $name.mask
211                )+;
212            #[cfg(not(all(target_arch = "aarch64", target_vendor = "apple")))]
213            const ARMCAP_STATIC: u32 = 0;
214
215            #[cfg(all(target_arch = "aarch64", target_vendor = "apple"))]
216            #[test]
217            fn test_armcap_static_available() {
218                let features = crate::cpu::features();
219                $(
220                    assert!($name.available(features));
221                )+
222            }
223        }
224    }
225
226    #[allow(dead_code)]
227    pub(crate) struct Feature {
228        mask: u32,
229    }
230
231    impl Feature {
232        #[allow(dead_code)]
233        #[inline(always)]
234        pub fn available(&self, _: super::Features) -> bool {
235            if self.mask == self.mask & ARMCAP_STATIC {
236                return true;
237            }
238
239            #[cfg(all(
240                any(target_os = "android", target_os = "fuchsia", target_os = "linux"),
241                any(target_arch = "arm", target_arch = "aarch64")
242            ))]
243            {
244                if self.mask == self.mask & unsafe { GFp_armcap_P } {
245                    return true;
246                }
247            }
248
249            false
250        }
251    }
252
253    features! {
254        // Keep in sync with `ARMV7_NEON`.
255        NEON {
256            mask: 1 << 0,
257            aarch64_apple: true,
258        },
259
260        // Keep in sync with `ARMV8_AES`.
261        AES {
262            mask: 1 << 2,
263            aarch64_apple: true,
264        },
265
266        // Keep in sync with `ARMV8_SHA256`.
267        SHA256 {
268            mask: 1 << 4,
269            aarch64_apple: true,
270        },
271
272        // Keep in sync with `ARMV8_PMULL`.
273        PMULL {
274            mask: 1 << 5,
275            aarch64_apple: true,
276        },
277    }
278
279    // Some non-Rust code still checks this even when it is statically known
280    // the given feature is available, so we have to ensure that this is
281    // initialized properly. Keep this in sync with the initialization in
282    // BoringSSL's crypto.c.
283    //
284    // TODO: This should have "hidden" visibility but we don't have a way of
285    // controlling that yet: https://github.com/rust-lang/rust/issues/73958.
286    #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
287    #[no_mangle]
288    static mut GFp_armcap_P: u32 = ARMCAP_STATIC;
289
290    #[cfg(all(
291        any(target_arch = "arm", target_arch = "aarch64"),
292        target_vendor = "apple"
293    ))]
294    #[test]
295    fn test_armcap_static_matches_armcap_dynamic() {
296        assert_eq!(ARMCAP_STATIC, 1 | 4 | 16 | 32);
297        assert_eq!(ARMCAP_STATIC, unsafe { GFp_armcap_P });
298    }
299}
300
301#[cfg_attr(
302    not(any(target_arch = "x86", target_arch = "x86_64")),
303    allow(dead_code)
304)]
305pub(crate) mod intel {
306    pub(crate) struct Feature {
307        word: usize,
308        mask: u32,
309    }
310
311    impl Feature {
312        #[allow(clippy::needless_return)]
313        #[inline(always)]
314        pub fn available(&self, _: super::Features) -> bool {
315            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
316            {
317                extern "C" {
318                    static mut GFp_ia32cap_P: [u32; 4];
319                }
320                return self.mask == self.mask & unsafe { GFp_ia32cap_P[self.word] };
321            }
322
323            #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
324            {
325                return false;
326            }
327        }
328    }
329
330    pub(crate) const FXSR: Feature = Feature {
331        word: 0,
332        mask: 1 << 24,
333    };
334
335    pub(crate) const PCLMULQDQ: Feature = Feature {
336        word: 1,
337        mask: 1 << 1,
338    };
339
340    pub(crate) const SSSE3: Feature = Feature {
341        word: 1,
342        mask: 1 << 9,
343    };
344
345    #[cfg(target_arch = "x86_64")]
346    pub(crate) const SSE41: Feature = Feature {
347        word: 1,
348        mask: 1 << 19,
349    };
350
351    #[cfg(target_arch = "x86_64")]
352    pub(crate) const MOVBE: Feature = Feature {
353        word: 1,
354        mask: 1 << 22,
355    };
356
357    pub(crate) const AES: Feature = Feature {
358        word: 1,
359        mask: 1 << 25,
360    };
361
362    #[cfg(target_arch = "x86_64")]
363    pub(crate) const AVX: Feature = Feature {
364        word: 1,
365        mask: 1 << 28,
366    };
367
368    #[cfg(all(target_arch = "x86_64", test))]
369    mod x86_64_tests {
370        use super::*;
371
372        #[test]
373        fn test_avx_movbe_mask() {
374            // This is the OpenSSL style of testing these bits.
375            assert_eq!((AVX.mask | MOVBE.mask) >> 22, 0x41);
376        }
377    }
378}