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)]
23 pub(crate) struct Features(());
24 
25 #[inline(always)]
features() -> Features26 pub(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 
63 pub(crate) mod arm {
64     #[cfg(all(
65         any(target_os = "android", target_os = "linux"),
66         any(target_arch = "aarch64", target_arch = "arm")
67     ))]
setup()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"))]
setup()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)]
available(&self, _: super::Features) -> bool234         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]
test_armcap_static_matches_armcap_dynamic()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 )]
305 pub(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)]
available(&self, _: super::Features) -> bool314         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]
test_avx_movbe_mask()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 }
379