• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2 
3 #[derive(Clone, Copy)]
4 pub(crate) struct CpuInfo(u32);
5 
6 impl CpuInfo {
7     const INIT: u32 = 0;
8 
9     #[inline]
set(&mut self, bit: u32)10     fn set(&mut self, bit: u32) {
11         self.0 = set(self.0, bit);
12     }
13     #[inline]
test(self, bit: u32) -> bool14     fn test(self, bit: u32) -> bool {
15         test(self.0, bit)
16     }
17 }
18 
19 #[inline]
set(x: u32, bit: u32) -> u3220 fn set(x: u32, bit: u32) -> u32 {
21     x | 1 << bit
22 }
23 #[inline]
test(x: u32, bit: u32) -> bool24 fn test(x: u32, bit: u32) -> bool {
25     x & (1 << bit) != 0
26 }
27 
28 #[inline]
detect() -> CpuInfo29 pub(crate) fn detect() -> CpuInfo {
30     use core::sync::atomic::{AtomicU32, Ordering};
31 
32     static CACHE: AtomicU32 = AtomicU32::new(0);
33     let mut info = CpuInfo(CACHE.load(Ordering::Relaxed));
34     if info.0 != 0 {
35         return info;
36     }
37     info.set(CpuInfo::INIT);
38     // Note: detect_false cfg is intended to make it easy for portable-atomic developers to
39     // test cases such as has_cmpxchg16b == false, has_lse == false,
40     // __kuser_helper_version < 5, etc., and is not a public API.
41     if !cfg!(portable_atomic_test_outline_atomics_detect_false) {
42         _detect(&mut info);
43     }
44     CACHE.store(info.0, Ordering::Relaxed);
45     info
46 }
47 
48 macro_rules! flags {
49     ($(
50         $(#[$attr:meta])*
51         $flag:ident ($shift:literal, $func:ident, $name:literal, $cfg:meta),
52     )*) => {
53         impl CpuInfo {
54             $(
55                 $(#[$attr])*
56                 const $flag: u32 = $shift;
57                 $(#[$attr])*
58                 #[cfg(any(test, not($cfg)))]
59                 #[inline]
60                 pub(crate) fn $func(self) -> bool {
61                     self.test(Self::$flag)
62                 }
63             )*
64             #[cfg(test)] // for test
65             const ALL_FLAGS: &'static [(&'static str, u32, bool)] = &[$(
66                 ($name, Self::$flag, cfg!($cfg)),
67             )*];
68         }
69     };
70 }
71 
72 #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
73 flags! {
74     // FEAT_LSE, Large System Extensions
75     // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv8-1-architecture-extension
76     // > This feature is supported in AArch64 state only.
77     // > FEAT_LSE is OPTIONAL from Armv8.0.
78     // > FEAT_LSE is mandatory from Armv8.1.
79     HAS_LSE(1, has_lse, "lse", any(target_feature = "lse", portable_atomic_target_feature = "lse")),
80     // FEAT_LSE2, Large System Extensions version 2
81     // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv8-4-architecture-extension
82     // > This feature is supported in AArch64 state only.
83     // > FEAT_LSE2 is OPTIONAL from Armv8.2.
84     // > FEAT_LSE2 is mandatory from Armv8.4.
85     #[cfg_attr(not(test), allow(dead_code))]
86     HAS_LSE2(2, has_lse2, "lse2", any(target_feature = "lse2", portable_atomic_target_feature = "lse2")),
87     // FEAT_LRCPC3, Load-Acquire RCpc instructions version 3
88     // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv8-9-architecture-extension
89     // > This feature is supported in AArch64 state only.
90     // > FEAT_LRCPC3 is OPTIONAL from Armv8.2.
91     // > If FEAT_LRCPC3 is implemented, then FEAT_LRCPC2 is implemented.
92     #[cfg_attr(not(test), allow(dead_code))]
93     HAS_RCPC3(3, has_rcpc3, "rcpc3", any(target_feature = "rcpc3", portable_atomic_target_feature = "rcpc3")),
94     // FEAT_LSE128, 128-bit Atomics
95     // https://developer.arm.com/documentation/109697/0100/Feature-descriptions/The-Armv9-4-architecture-extension
96     // > This feature is supported in AArch64 state only.
97     // > FEAT_LSE128 is OPTIONAL from Armv9.3.
98     // > If FEAT_LSE128 is implemented, then FEAT_LSE is implemented.
99     #[cfg_attr(not(test), allow(dead_code))]
100     HAS_LSE128(4, has_lse128, "lse128", any(target_feature = "lse128", portable_atomic_target_feature = "lse128")),
101 }
102 
103 #[cfg(target_arch = "powerpc64")]
104 flags! {
105     // lqarx and stqcx.
106     HAS_QUADWORD_ATOMICS(1, has_quadword_atomics, "quadword-atomics", any(target_feature = "quadword-atomics", portable_atomic_target_feature = "quadword-atomics")),
107 }
108 
109 #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
110 flags! {
111     // amocas.{w,d,q}
112     HAS_ZACAS(1, has_zacas, "zacas", any(target_feature = "experimental-zacas", portable_atomic_target_feature = "experimental-zacas")),
113 }
114 
115 #[cfg(target_arch = "x86_64")]
116 flags! {
117     // cmpxchg16b
118     HAS_CMPXCHG16B(1, has_cmpxchg16b, "cmpxchg16b", any(target_feature = "cmpxchg16b", portable_atomic_target_feature = "cmpxchg16b")),
119     // atomic vmovdqa
120     #[cfg(target_feature = "sse")]
121     HAS_VMOVDQA_ATOMIC(2, has_vmovdqa_atomic, "vmovdqa-atomic", any(/* always false */)),
122 }
123 
124 // core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47
125 #[cfg(not(target_arch = "x86_64"))]
126 #[cfg(not(windows))]
127 #[allow(dead_code, non_camel_case_types)]
128 mod c_types {
129     pub(crate) type c_void = core::ffi::c_void;
130     // c_{,u}int is {i,u}32 on non-16-bit architectures
131     // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L147
132     // (16-bit architectures currently don't use this module)
133     pub(crate) type c_int = i32;
134     pub(crate) type c_uint = u32;
135     // c_{,u}long is {i,u}64 on non-Windows 64-bit targets, otherwise is {i,u}32
136     // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L159
137     // (Windows currently doesn't use this module - this module is cfg(not(windows)))
138     #[cfg(target_pointer_width = "64")]
139     pub(crate) type c_long = i64;
140     #[cfg(not(target_pointer_width = "64"))]
141     pub(crate) type c_long = i32;
142     #[cfg(target_pointer_width = "64")]
143     pub(crate) type c_ulong = u64;
144     #[cfg(not(target_pointer_width = "64"))]
145     pub(crate) type c_ulong = u32;
146     // c_size_t is currently always usize
147     // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L67
148     pub(crate) type c_size_t = usize;
149     // c_char is u8 by default on most non-Apple/non-Windows Arm/PowerPC/RISC-V/s390x/Hexagon targets
150     // (Linux/Android/FreeBSD/NetBSD/OpenBSD/VxWorks/Fuchsia/QNX Neutrino/Horizon/AIX/z/OS)
151     // https://github.com/rust-lang/rust/blob/1.80.0/library/core/src/ffi/mod.rs#L83
152     // https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/lldb/source/Utility/ArchSpec.cpp#L712
153     // RISC-V https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/draft-20240829-13bfa9f54634cb60d86b9b333e109f077805b4b3/riscv-cc.adoc#cc-type-representations
154     // Hexagon https://lists.llvm.org/pipermail/llvm-dev/attachments/20190916/21516a52/attachment-0001.pdf
155     // AIX https://www.ibm.com/docs/en/xl-c-aix/13.1.3?topic=specifiers-character-types
156     // z/OS https://www.ibm.com/docs/en/zos/3.1.0?topic=specifiers-character-types
157     // (Windows currently doesn't use this module)
158     #[cfg(not(target_vendor = "apple"))]
159     pub(crate) type c_char = u8;
160     // c_char is i8 on all Apple targets
161     #[cfg(target_vendor = "apple")]
162     pub(crate) type c_char = i8;
163 
164     // Static assertions for C type definitions.
165     #[cfg(test)]
166     const _: fn() = || {
167         use test_helper::{libc, sys};
168         let _: c_int = 0 as std::os::raw::c_int;
169         let _: c_uint = 0 as std::os::raw::c_uint;
170         let _: c_long = 0 as std::os::raw::c_long;
171         let _: c_ulong = 0 as std::os::raw::c_ulong;
172         let _: c_size_t = 0 as libc::size_t; // std::os::raw::c_size_t is unstable
173         #[cfg(not(any(
174             all(target_arch = "aarch64", target_os = "illumos"), // TODO: https://github.com/rust-lang/rust/issues/129945
175             all(target_arch = "riscv64", target_os = "android"), // TODO: https://github.com/rust-lang/rust/issues/129945
176         )))]
177         let _: c_char = 0 as std::os::raw::c_char;
178         let _: c_char = 0 as sys::c_char;
179     };
180 }
181 
182 #[allow(
183     clippy::alloc_instead_of_core,
184     clippy::std_instead_of_alloc,
185     clippy::std_instead_of_core,
186     clippy::undocumented_unsafe_blocks,
187     clippy::wildcard_imports
188 )]
189 #[cfg(test)]
190 mod tests_common {
191     use std::{collections::BTreeSet, vec};
192 
193     use super::*;
194 
195     #[test]
test_bit_flags()196     fn test_bit_flags() {
197         let mut flags = vec![("init", CpuInfo::INIT)];
198         flags.extend(CpuInfo::ALL_FLAGS.iter().map(|&(name, flag, _)| (name, flag)));
199         let flag_set = flags.iter().map(|(_, flag)| flag).collect::<BTreeSet<_>>();
200         let name_set = flags.iter().map(|(_, flag)| flag).collect::<BTreeSet<_>>();
201         if flag_set.len() != flags.len() {
202             panic!("CpuInfo flag values must be unique")
203         }
204         if name_set.len() != flags.len() {
205             panic!("CpuInfo flag names must be unique")
206         }
207 
208         let mut x = CpuInfo(0);
209         for &(_, f) in &flags {
210             assert!(!x.test(f));
211         }
212         for i in 0..flags.len() {
213             x.set(flags[i].1);
214             for &(_, f) in &flags[..i + 1] {
215                 assert!(x.test(f));
216             }
217             for &(_, f) in &flags[i + 1..] {
218                 assert!(!x.test(f));
219             }
220         }
221         for &(_, f) in &flags {
222             assert!(x.test(f));
223         }
224     }
225 
226     #[test]
print_features()227     fn print_features() {
228         use std::{
229             fmt::Write as _,
230             io::{self, Write},
231             string::String,
232         };
233 
234         let mut features = String::new();
235         features.push_str("\nfeatures:\n");
236         for &(name, flag, compile_time) in CpuInfo::ALL_FLAGS {
237             let run_time = detect().test(flag);
238             if run_time == compile_time {
239                 let _ = writeln!(features, "  {}: {}", name, run_time);
240             } else {
241                 let _ = writeln!(
242                     features,
243                     "  {}: {} (compile-time), {} (run-time)",
244                     name, compile_time, run_time
245                 );
246             }
247         }
248         let stdout = io::stderr();
249         let mut stdout = stdout.lock();
250         let _ = stdout.write_all(features.as_bytes());
251     }
252 
253     #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
254     #[test]
255     #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)]
test_detect()256     fn test_detect() {
257         let proc_cpuinfo = test_helper::cpuinfo::ProcCpuinfo::new();
258         if detect().has_lse() {
259             assert!(detect().test(CpuInfo::HAS_LSE));
260             if let Ok(proc_cpuinfo) = proc_cpuinfo {
261                 assert!(proc_cpuinfo.lse);
262             }
263         } else {
264             assert!(!detect().test(CpuInfo::HAS_LSE));
265             if let Ok(proc_cpuinfo) = proc_cpuinfo {
266                 assert!(!proc_cpuinfo.lse);
267             }
268         }
269         if detect().has_lse2() {
270             assert!(detect().test(CpuInfo::HAS_LSE));
271             assert!(detect().test(CpuInfo::HAS_LSE2));
272             if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse2: Some(lse2), .. }) = proc_cpuinfo {
273                 assert!(lse2);
274             }
275         } else {
276             assert!(!detect().test(CpuInfo::HAS_LSE2));
277             if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse2: Some(lse2), .. }) = proc_cpuinfo {
278                 assert!(!lse2);
279             }
280         }
281         if detect().has_lse128() {
282             assert!(detect().test(CpuInfo::HAS_LSE));
283             assert!(detect().test(CpuInfo::HAS_LSE2));
284             assert!(detect().test(CpuInfo::HAS_LSE128));
285             if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse128: Some(lse128), .. }) = proc_cpuinfo
286             {
287                 assert!(lse128);
288             }
289         } else {
290             assert!(!detect().test(CpuInfo::HAS_LSE128));
291             if let Ok(test_helper::cpuinfo::ProcCpuinfo { lse128: Some(lse128), .. }) = proc_cpuinfo
292             {
293                 assert!(!lse128);
294             }
295         }
296         if detect().has_rcpc3() {
297             assert!(detect().test(CpuInfo::HAS_RCPC3));
298             if let Ok(test_helper::cpuinfo::ProcCpuinfo { rcpc3: Some(rcpc3), .. }) = proc_cpuinfo {
299                 assert!(rcpc3);
300             }
301         } else {
302             assert!(!detect().test(CpuInfo::HAS_RCPC3));
303             if let Ok(test_helper::cpuinfo::ProcCpuinfo { rcpc3: Some(rcpc3), .. }) = proc_cpuinfo {
304                 assert!(!rcpc3);
305             }
306         }
307     }
308     #[cfg(target_arch = "powerpc64")]
309     #[test]
310     #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)]
test_detect()311     fn test_detect() {
312         let proc_cpuinfo = test_helper::cpuinfo::ProcCpuinfo::new();
313         if detect().has_quadword_atomics() {
314             assert!(detect().test(CpuInfo::HAS_QUADWORD_ATOMICS));
315             if let Ok(proc_cpuinfo) = proc_cpuinfo {
316                 assert!(proc_cpuinfo.power8);
317             }
318         } else {
319             assert!(!detect().test(CpuInfo::HAS_QUADWORD_ATOMICS));
320             if let Ok(proc_cpuinfo) = proc_cpuinfo {
321                 assert!(!proc_cpuinfo.power8);
322             }
323         }
324     }
325     #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
326     #[test]
327     #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)]
test_detect()328     fn test_detect() {
329         if detect().has_zacas() {
330             assert!(detect().test(CpuInfo::HAS_ZACAS));
331         } else {
332             assert!(!detect().test(CpuInfo::HAS_ZACAS));
333         }
334     }
335     #[cfg(target_arch = "x86_64")]
336     #[test]
337     #[cfg_attr(portable_atomic_test_outline_atomics_detect_false, ignore)]
test_detect()338     fn test_detect() {
339         if detect().has_cmpxchg16b() {
340             assert!(detect().test(CpuInfo::HAS_CMPXCHG16B));
341         } else {
342             assert!(!detect().test(CpuInfo::HAS_CMPXCHG16B));
343         }
344         if detect().has_vmovdqa_atomic() {
345             assert!(detect().test(CpuInfo::HAS_VMOVDQA_ATOMIC));
346         } else {
347             assert!(!detect().test(CpuInfo::HAS_VMOVDQA_ATOMIC));
348         }
349     }
350 }
351