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 MOVBE: Feature = Feature {
347 word: 1,
348 mask: 1 << 22,
349 };
350
351 pub(crate) const AES: Feature = Feature {
352 word: 1,
353 mask: 1 << 25,
354 };
355
356 #[cfg(target_arch = "x86_64")]
357 pub(crate) const AVX: Feature = Feature {
358 word: 1,
359 mask: 1 << 28,
360 };
361
362 #[cfg(all(target_arch = "x86_64", test))]
363 mod x86_64_tests {
364 use super::*;
365
366 #[test]
test_avx_movbe_mask()367 fn test_avx_movbe_mask() {
368 // This is the OpenSSL style of testing these bits.
369 assert_eq!((AVX.mask | MOVBE.mask) >> 22, 0x41);
370 }
371 }
372 }
373