• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2 
3 /*
4 Run-time CPU feature detection on AArch64 Linux/Android/FreeBSD/NetBSD/OpenBSD by parsing system registers.
5 
6 As of nightly-2024-09-07, is_aarch64_feature_detected doesn't support run-time detection on NetBSD.
7 https://github.com/rust-lang/stdarch/blob/d9466edb4c53cece8686ee6e17b028436ddf4151/crates/std_detect/src/detect/mod.rs
8 Run-time detection on OpenBSD by is_aarch64_feature_detected is supported on Rust 1.70+.
9 https://github.com/rust-lang/stdarch/pull/1374
10 
11 Refs:
12 - https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers
13 - https://github.com/torvalds/linux/blob/v6.11/Documentation/arch/arm64/cpu-feature-registers.rst
14 - https://github.com/rust-lang/stdarch/blob/a0c30f3e3c75adcd6ee7efc94014ebcead61c507/crates/std_detect/src/detect/os/aarch64.rs
15 
16 Supported platforms:
17 - Linux 4.11+ (emulate mrs instruction)
18   https://github.com/torvalds/linux/commit/77c97b4ee21290f5f083173d957843b615abbff2
19 - FreeBSD 12.0+ (emulate mrs instruction)
20   https://github.com/freebsd/freebsd-src/commit/398810619cb32abf349f8de23f29510b2ee0839b
21 - NetBSD 9.0+ (through sysctl/sysctlbyname)
22   https://github.com/NetBSD/src/commit/0e9d25528729f7fea53e78275d1bc5039dfe8ffb
23 - OpenBSD 7.1+ (through sysctl)
24   https://github.com/openbsd/src/commit/d335af936b9d7dd9cf655cae1ce19560c45de6c8
25 
26 For now, this module is only used on NetBSD/OpenBSD.
27 
28 On Linux/Android/FreeBSD, we use auxv.rs and this module is test-only because:
29 - On Linux/Android, this approach requires a higher kernel version than Rust supports,
30   and also does not work with qemu-user (as of 7.2) and Valgrind (as of 3.19).
31   (Looking into HWCAP_CPUID in auxvec, it appears that Valgrind is setting it
32   to false correctly, but qemu-user is setting it to true.)
33 - On FreeBSD, this approach does not work on FreeBSD 12 on QEMU (confirmed on
34   FreeBSD 12.{2,3,4}), and we got SIGILL (worked on FreeBSD 13 and 14).
35 */
36 
37 include!("common.rs");
38 
39 #[cfg_attr(test, derive(Debug, PartialEq))]
40 struct AA64Reg {
41     aa64isar0: u64,
42     aa64isar1: u64,
43     aa64mmfr2: u64,
44 }
45 
46 #[cold]
_detect(info: &mut CpuInfo)47 fn _detect(info: &mut CpuInfo) {
48     let AA64Reg { aa64isar0, aa64isar1, aa64mmfr2 } = imp::aa64reg();
49 
50     // ID_AA64ISAR0_EL1, AArch64 Instruction Set Attribute Register 0
51     // https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/ID-AA64ISAR0-EL1--AArch64-Instruction-Set-Attribute-Register-0
52     let atomic = extract(aa64isar0, 23, 20);
53     if atomic >= 0b0010 {
54         info.set(CpuInfo::HAS_LSE);
55         if atomic >= 0b0011 {
56             info.set(CpuInfo::HAS_LSE128);
57         }
58     }
59     // ID_AA64ISAR1_EL1, AArch64 Instruction Set Attribute Register 1
60     // https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/ID-AA64ISAR1-EL1--AArch64-Instruction-Set-Attribute-Register-1
61     if extract(aa64isar1, 23, 20) >= 0b0011 {
62         info.set(CpuInfo::HAS_RCPC3);
63     }
64     // ID_AA64MMFR2_EL1, AArch64 Memory Model Feature Register 2
65     // https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/ID-AA64MMFR2-EL1--AArch64-Memory-Model-Feature-Register-2
66     if extract(aa64mmfr2, 35, 32) >= 0b0001 {
67         info.set(CpuInfo::HAS_LSE2);
68     }
69 }
70 
extract(x: u64, high: usize, low: usize) -> u6471 fn extract(x: u64, high: usize, low: usize) -> u64 {
72     (x >> low) & ((1 << (high - low + 1)) - 1)
73 }
74 
75 #[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))]
76 mod imp {
77     // This module is test-only. See parent module docs for details.
78 
79     #[cfg(not(portable_atomic_no_asm))]
80     use core::arch::asm;
81 
82     use super::AA64Reg;
83 
aa64reg() -> AA64Reg84     pub(super) fn aa64reg() -> AA64Reg {
85         // SAFETY: This is safe on FreeBSD 12.0+. FreeBSD 11 was EoL on 2021-09-30.
86         // Note that stdarch has been doing the same thing since before FreeBSD 11 was EoL.
87         // https://github.com/rust-lang/stdarch/pull/611
88         unsafe {
89             let aa64isar0: u64;
90             asm!(
91                 "mrs {0}, ID_AA64ISAR0_EL1",
92                 out(reg) aa64isar0,
93                 options(pure, nomem, nostack, preserves_flags),
94             );
95             let aa64isar1: u64;
96             asm!(
97                 "mrs {0}, ID_AA64ISAR1_EL1",
98                 out(reg) aa64isar1,
99                 options(pure, nomem, nostack, preserves_flags),
100             );
101             let aa64mmfr2: u64;
102             asm!(
103                 "mrs {0}, ID_AA64MMFR2_EL1",
104                 out(reg) aa64mmfr2,
105                 options(pure, nomem, nostack, preserves_flags),
106             );
107             AA64Reg { aa64isar0, aa64isar1, aa64mmfr2 }
108         }
109     }
110 }
111 #[cfg(target_os = "netbsd")]
112 mod imp {
113     // NetBSD doesn't trap the mrs instruction, but exposes the system registers through sysctl.
114     // https://github.com/NetBSD/src/commit/0e9d25528729f7fea53e78275d1bc5039dfe8ffb
115     // https://github.com/golang/sys/commit/ef9fd89ba245e184bdd308f7f2b4f3c551fa5b0f
116 
117     use core::ptr;
118 
119     use super::AA64Reg;
120 
121     // core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47
122     #[allow(non_camel_case_types)]
123     pub(super) mod ffi {
124         pub(crate) use super::super::c_types::{c_char, c_int, c_size_t, c_void};
125 
126         // Defined in aarch64/armreg.h.
127         // https://github.com/NetBSD/src/blob/432a1357026b10c184d8a0ddb683008a23cc7cd9/sys/arch/aarch64/include/armreg.h#L1863
128         #[derive(Clone, Copy)]
129         #[repr(C)]
130         pub(crate) struct aarch64_sysctl_cpu_id {
131             // NetBSD 9.0+
132             // https://github.com/NetBSD/src/commit/0e9d25528729f7fea53e78275d1bc5039dfe8ffb
133             pub(crate) midr: u64,
134             pub(crate) revidr: u64,
135             pub(crate) mpidr: u64,
136             pub(crate) aa64dfr0: u64,
137             pub(crate) aa64dfr1: u64,
138             pub(crate) aa64isar0: u64,
139             pub(crate) aa64isar1: u64,
140             pub(crate) aa64mmfr0: u64,
141             pub(crate) aa64mmfr1: u64,
142             pub(crate) aa64mmfr2: u64,
143             pub(crate) aa64pfr0: u64,
144             pub(crate) aa64pfr1: u64,
145             pub(crate) aa64zfr0: u64,
146             pub(crate) mvfr0: u32,
147             pub(crate) mvfr1: u32,
148             pub(crate) mvfr2: u32,
149             // NetBSD 10.0+
150             // https://github.com/NetBSD/src/commit/0c7bdc13f0e332cccec56e307f023b4888638973
151             pub(crate) pad: u32,
152             pub(crate) clidr: u64,
153             pub(crate) ctr: u64,
154         }
155 
156         extern "C" {
157             // Defined in sys/sysctl.h.
158             // https://man.netbsd.org/sysctl.3
159             // https://github.com/NetBSD/src/blob/432a1357026b10c184d8a0ddb683008a23cc7cd9/sys/sys/sysctl.h
sysctlbyname( name: *const c_char, old_p: *mut c_void, old_len_p: *mut c_size_t, new_p: *const c_void, new_len: c_size_t, ) -> c_int160             pub(crate) fn sysctlbyname(
161                 name: *const c_char,
162                 old_p: *mut c_void,
163                 old_len_p: *mut c_size_t,
164                 new_p: *const c_void,
165                 new_len: c_size_t,
166             ) -> c_int;
167         }
168     }
169 
sysctl_cpu_id(name: &[u8]) -> Option<AA64Reg>170     pub(super) unsafe fn sysctl_cpu_id(name: &[u8]) -> Option<AA64Reg> {
171         const OUT_LEN: ffi::c_size_t =
172             core::mem::size_of::<ffi::aarch64_sysctl_cpu_id>() as ffi::c_size_t;
173 
174         debug_assert_eq!(name.last(), Some(&0), "{:?}", name);
175         debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name);
176 
177         // SAFETY: all fields of aarch64_sysctl_cpu_id are zero-able and we use
178         // the result when machdep.cpuN.cpu_id sysctl was successful.
179         let mut buf: ffi::aarch64_sysctl_cpu_id = unsafe { core::mem::zeroed() };
180         let mut out_len = OUT_LEN;
181         // SAFETY:
182         // - the caller must guarantee that `name` is ` machdep.cpuN.cpu_id` in a C string.
183         // - `out_len` does not exceed the size of the value at `buf`.
184         // - `sysctlbyname` is thread-safe.
185         let res = unsafe {
186             ffi::sysctlbyname(
187                 name.as_ptr().cast::<ffi::c_char>(),
188                 (&mut buf as *mut ffi::aarch64_sysctl_cpu_id).cast::<ffi::c_void>(),
189                 &mut out_len,
190                 ptr::null_mut(),
191                 0,
192             )
193         };
194         if res != 0 {
195             return None;
196         }
197         Some(AA64Reg {
198             aa64isar0: buf.aa64isar0,
199             aa64isar1: buf.aa64isar1,
200             aa64mmfr2: buf.aa64mmfr2,
201         })
202     }
203 
aa64reg() -> AA64Reg204     pub(super) fn aa64reg() -> AA64Reg {
205         // Get system registers for cpu0.
206         // If failed, returns default because machdep.cpuN.cpu_id sysctl is not available.
207         // machdep.cpuN.cpu_id sysctl was added in NetBSD 9.0 so it is not available on older versions.
208         // SAFETY: we passed a valid name in a C string.
209         // It is ok to check only cpu0, even if there are more CPUs.
210         // https://github.com/NetBSD/src/commit/bd9707e06ea7d21b5c24df6dfc14cb37c2819416
211         // https://github.com/golang/sys/commit/ef9fd89ba245e184bdd308f7f2b4f3c551fa5b0f
212         match unsafe { sysctl_cpu_id(b"machdep.cpu0.cpu_id\0") } {
213             Some(cpu_id) => cpu_id,
214             None => AA64Reg { aa64isar0: 0, aa64isar1: 0, aa64mmfr2: 0 },
215         }
216     }
217 }
218 #[cfg(target_os = "openbsd")]
219 mod imp {
220     // OpenBSD doesn't trap the mrs instruction, but exposes the system registers through sysctl.
221     // https://github.com/openbsd/src/commit/d335af936b9d7dd9cf655cae1ce19560c45de6c8
222     // https://github.com/golang/go/commit/cd54ef1f61945459486e9eea2f016d99ef1da925
223 
224     use core::ptr;
225 
226     use super::AA64Reg;
227 
228     // core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47
229     #[allow(non_camel_case_types)]
230     pub(super) mod ffi {
231         pub(crate) use super::super::c_types::{c_int, c_size_t, c_uint, c_void};
232 
233         // Defined in sys/sysctl.h.
234         // https://github.com/openbsd/src/blob/ed8f5e8d82ace15e4cefca2c82941b15cb1a7830/sys/sys/sysctl.h#L82
235         pub(crate) const CTL_MACHDEP: c_int = 7;
236 
237         // Defined in machine/cpu.h.
238         // https://github.com/openbsd/src/blob/ed8f5e8d82ace15e4cefca2c82941b15cb1a7830/sys/arch/arm64/include/cpu.h#L25-L40
239         // OpenBSD 7.1+
240         // https://github.com/openbsd/src/commit/d335af936b9d7dd9cf655cae1ce19560c45de6c8
241         pub(crate) const CPU_ID_AA64ISAR0: c_int = 2;
242         pub(crate) const CPU_ID_AA64ISAR1: c_int = 3;
243         // OpenBSD 7.3+
244         // https://github.com/openbsd/src/commit/c7654cd65262d532212f65123ee3905ba200365c
245         // However, on OpenBSD 7.3-7.5, querying CPU_ID_AA64MMFR2 always returns 0.
246         // https://github.com/openbsd/src/commit/e8331b74e5c20302d4bd948c9db722af688ccfc1
247         pub(crate) const CPU_ID_AA64MMFR2: c_int = 7;
248 
249         extern "C" {
250             // Defined in sys/sysctl.h.
251             // https://man.openbsd.org/sysctl.2
252             // https://github.com/openbsd/src/blob/ed8f5e8d82ace15e4cefca2c82941b15cb1a7830/sys/sys/sysctl.h
sysctl( name: *const c_int, name_len: c_uint, old_p: *mut c_void, old_len_p: *mut c_size_t, new_p: *mut c_void, new_len: c_size_t, ) -> c_int253             pub(crate) fn sysctl(
254                 name: *const c_int,
255                 name_len: c_uint,
256                 old_p: *mut c_void,
257                 old_len_p: *mut c_size_t,
258                 new_p: *mut c_void,
259                 new_len: c_size_t,
260             ) -> c_int;
261         }
262     }
263 
264     // sysctl returns an unsupported error if operation is not supported,
265     // so we can safely use this function on older versions of OpenBSD.
aa64reg() -> AA64Reg266     pub(super) fn aa64reg() -> AA64Reg {
267         let aa64isar0 = sysctl64(&[ffi::CTL_MACHDEP, ffi::CPU_ID_AA64ISAR0]).unwrap_or(0);
268         let aa64isar1 = sysctl64(&[ffi::CTL_MACHDEP, ffi::CPU_ID_AA64ISAR1]).unwrap_or(0);
269         let aa64mmfr2 = sysctl64(&[ffi::CTL_MACHDEP, ffi::CPU_ID_AA64MMFR2]).unwrap_or(0);
270         AA64Reg { aa64isar0, aa64isar1, aa64mmfr2 }
271     }
272 
sysctl64(mib: &[ffi::c_int]) -> Option<u64>273     fn sysctl64(mib: &[ffi::c_int]) -> Option<u64> {
274         const OUT_LEN: ffi::c_size_t = core::mem::size_of::<u64>() as ffi::c_size_t;
275         let mut out = 0_u64;
276         let mut out_len = OUT_LEN;
277         #[allow(clippy::cast_possible_truncation)]
278         let mib_len = mib.len() as ffi::c_uint;
279         // SAFETY:
280         // - `mib.len()` does not exceed the size of `mib`.
281         // - `out_len` does not exceed the size of `out`.
282         // - `sysctl` is thread-safe.
283         let res = unsafe {
284             ffi::sysctl(
285                 mib.as_ptr(),
286                 mib_len,
287                 (&mut out as *mut u64).cast::<ffi::c_void>(),
288                 &mut out_len,
289                 ptr::null_mut(),
290                 0,
291             )
292         };
293         if res == -1 {
294             return None;
295         }
296         debug_assert_eq!(out_len, OUT_LEN);
297         Some(out)
298     }
299 }
300 
301 #[allow(
302     clippy::alloc_instead_of_core,
303     clippy::std_instead_of_alloc,
304     clippy::std_instead_of_core,
305     clippy::undocumented_unsafe_blocks,
306     clippy::wildcard_imports
307 )]
308 #[cfg(test)]
309 mod tests {
310     use std::{
311         process::Command,
312         string::{String, ToString},
313     };
314 
315     use super::*;
316 
317     #[test]
test_aa64reg()318     fn test_aa64reg() {
319         let AA64Reg { aa64isar0, aa64isar1, aa64mmfr2 } = imp::aa64reg();
320         std::eprintln!("aa64isar0={}", aa64isar0);
321         std::eprintln!("aa64isar1={}", aa64isar1);
322         std::eprintln!("aa64mmfr2={}", aa64mmfr2);
323         if cfg!(target_os = "openbsd") {
324             let output = Command::new("sysctl").arg("machdep").output().unwrap();
325             assert!(output.status.success());
326             let stdout = String::from_utf8(output.stdout).unwrap();
327             // OpenBSD 7.1+
328             assert_eq!(
329                 stdout.lines().find_map(|s| s.strip_prefix("machdep.id_aa64isar0=")).unwrap_or("0"),
330                 aa64isar0.to_string(),
331             );
332             assert_eq!(
333                 stdout.lines().find_map(|s| s.strip_prefix("machdep.id_aa64isar1=")).unwrap_or("0"),
334                 aa64isar1.to_string(),
335             );
336             // OpenBSD 7.3+
337             assert_eq!(
338                 stdout.lines().find_map(|s| s.strip_prefix("machdep.id_aa64mmfr2=")).unwrap_or("0"),
339                 aa64mmfr2.to_string(),
340             );
341         }
342         if detect().test(CpuInfo::HAS_LSE) {
343             let atomic = extract(aa64isar0, 23, 20);
344             if detect().test(CpuInfo::HAS_LSE128) {
345                 assert_eq!(atomic, 3);
346             } else {
347                 assert_eq!(atomic, 2);
348             }
349         }
350         if detect().test(CpuInfo::HAS_LSE2) {
351             assert_eq!(extract(aa64mmfr2, 35, 32), 1);
352         }
353         if detect().test(CpuInfo::HAS_RCPC3) {
354             assert_eq!(extract(aa64isar1, 23, 20), 3);
355         }
356     }
357 
358     #[allow(clippy::cast_possible_wrap)]
359     #[cfg(target_os = "netbsd")]
360     #[test]
test_netbsd()361     fn test_netbsd() {
362         use c_types::*;
363         use imp::ffi;
364         #[cfg(not(portable_atomic_no_asm))]
365         use std::arch::asm;
366         use std::{mem, ptr, vec, vec::Vec};
367         use test_helper::sys;
368 
369         // Call syscall using asm instead of libc.
370         // Note that NetBSD does not guarantee the stability of raw syscall as
371         // much as Linux does (It may actually be stable enough, though: https://lists.llvm.org/pipermail/llvm-dev/2019-June/133393.html).
372         //
373         // This is currently used only for testing.
374         unsafe fn sysctl_cpu_id_asm_syscall(name: &[&[u8]]) -> Result<AA64Reg, c_int> {
375             // https://github.com/golang/go/blob/4badad8d477ffd7a6b762c35bc69aed82faface7/src/syscall/asm_netbsd_arm64.s
376             #[inline]
377             unsafe fn sysctl(
378                 name: *const c_int,
379                 name_len: c_uint,
380                 old_p: *mut c_void,
381                 old_len_p: *mut c_size_t,
382                 new_p: *const c_void,
383                 new_len: c_size_t,
384             ) -> Result<c_int, c_int> {
385                 // SAFETY: the caller must uphold the safety contract.
386                 unsafe {
387                     let mut n = sys::SYS___sysctl as u64;
388                     let r: i64;
389                     asm!(
390                         "svc 0",
391                         "b.cc 2f",
392                         "mov x17, x0",
393                         "mov x0, #-1",
394                         "2:",
395                         inout("x17") n,
396                         inout("x0") ptr_reg!(name) => r,
397                         inout("x1") name_len as u64 => _,
398                         in("x2") ptr_reg!(old_p),
399                         in("x3") ptr_reg!(old_len_p),
400                         in("x4") ptr_reg!(new_p),
401                         in("x5") new_len as u64,
402                         options(nostack),
403                     );
404                     #[allow(clippy::cast_possible_truncation)]
405                     if r as c_int == -1 {
406                         Err(n as c_int)
407                     } else {
408                         Ok(r as c_int)
409                     }
410                 }
411             }
412 
413             // https://github.com/golang/sys/blob/4badad8d477ffd7a6b762c35bc69aed82faface7/cpu/cpu_netbsd_arm64.go.
414             fn sysctl_nodes(mib: &mut Vec<i32>) -> Result<Vec<sys::sysctlnode>, i32> {
415                 mib.push(sys::CTL_QUERY);
416                 let mut q_node = sys::sysctlnode {
417                     sysctl_flags: sys::SYSCTL_VERS_1,
418                     ..unsafe { mem::zeroed() }
419                 };
420                 let qp = (&mut q_node as *mut sys::sysctlnode).cast::<ffi::c_void>();
421                 let sz = mem::size_of::<sys::sysctlnode>();
422                 let mut olen = 0;
423                 #[allow(clippy::cast_possible_truncation)]
424                 let mib_len = mib.len() as c_uint;
425                 unsafe {
426                     sysctl(mib.as_ptr(), mib_len, ptr::null_mut(), &mut olen, qp, sz)?;
427                 }
428 
429                 let mut nodes = Vec::<sys::sysctlnode>::with_capacity(olen / sz);
430                 let np = nodes.as_mut_ptr().cast::<ffi::c_void>();
431                 unsafe {
432                     sysctl(mib.as_ptr(), mib_len, np, &mut olen, qp, sz)?;
433                     nodes.set_len(olen / sz);
434                 }
435 
436                 mib.pop(); // pop CTL_QUERY
437                 Ok(nodes)
438             }
439             fn name_to_mib(parts: &[&[u8]]) -> Result<Vec<i32>, i32> {
440                 let mut mib = vec![];
441                 for (part_no, &part) in parts.iter().enumerate() {
442                     let nodes = sysctl_nodes(&mut mib)?;
443                     for node in nodes {
444                         let mut n = vec![];
445                         for b in node.sysctl_name {
446                             if b != 0 {
447                                 n.push(b);
448                             }
449                         }
450                         if n == part {
451                             mib.push(node.sysctl_num);
452                             break;
453                         }
454                     }
455                     if mib.len() != part_no + 1 {
456                         return Err(0);
457                     }
458                 }
459 
460                 Ok(mib)
461             }
462 
463             const OUT_LEN: ffi::c_size_t =
464                 core::mem::size_of::<ffi::aarch64_sysctl_cpu_id>() as ffi::c_size_t;
465 
466             let mib = name_to_mib(name)?;
467 
468             let mut buf: ffi::aarch64_sysctl_cpu_id = unsafe { core::mem::zeroed() };
469             let mut out_len = OUT_LEN;
470             #[allow(clippy::cast_possible_truncation)]
471             let mib_len = mib.len() as c_uint;
472             unsafe {
473                 sysctl(
474                     mib.as_ptr(),
475                     mib_len,
476                     (&mut buf as *mut ffi::aarch64_sysctl_cpu_id).cast::<ffi::c_void>(),
477                     &mut out_len,
478                     ptr::null_mut(),
479                     0,
480                 )?;
481             }
482             Ok(AA64Reg {
483                 aa64isar0: buf.aa64isar0,
484                 aa64isar1: buf.aa64isar1,
485                 aa64mmfr2: buf.aa64mmfr2,
486             })
487         }
488 
489         unsafe {
490             assert_eq!(
491                 imp::sysctl_cpu_id(b"machdep.cpu0.cpu_id\0").unwrap(),
492                 sysctl_cpu_id_asm_syscall(&[b"machdep", b"cpu0", b"cpu_id"]).unwrap()
493             );
494         }
495     }
496 
497     // Static assertions for FFI bindings.
498     // This checks that FFI bindings defined in this crate, FFI bindings defined
499     // in libc, and FFI bindings generated for the platform's latest header file
500     // using bindgen have compatible signatures (or the same values if constants).
501     // Since this is static assertion, we can detect problems with
502     // `cargo check --tests --target <target>` run in CI (via TESTS=1 build.sh)
503     // without actually running tests on these platforms.
504     // See also tools/codegen/src/ffi.rs.
505     // TODO(codegen): auto-generate this test
506     #[cfg(target_os = "netbsd")]
507     #[allow(
508         clippy::cast_possible_wrap,
509         clippy::cast_sign_loss,
510         clippy::no_effect_underscore_binding,
511         clippy::used_underscore_binding
512     )]
513     const _: fn() = || {
514         use imp::ffi;
515         use std::mem;
516         use test_helper::{libc, sys};
517         let mut _sysctlbyname: unsafe extern "C" fn(
518             *const ffi::c_char,
519             *mut ffi::c_void,
520             *mut ffi::c_size_t,
521             *const ffi::c_void,
522             ffi::c_size_t,
523         ) -> ffi::c_int = ffi::sysctlbyname;
524         _sysctlbyname = libc::sysctlbyname;
525         _sysctlbyname = sys::sysctlbyname;
526         // libc doesn't have this
527         // static_assert!(
528         //     mem::size_of::<ffi::aarch64_sysctl_cpu_id>()
529         //         == mem::size_of::<libc::aarch64_sysctl_cpu_id>()
530         // );
531         static_assert!(
532             mem::size_of::<ffi::aarch64_sysctl_cpu_id>()
533                 == mem::size_of::<sys::aarch64_sysctl_cpu_id>()
534         );
535         let ffi: ffi::aarch64_sysctl_cpu_id = unsafe { mem::zeroed() };
536         let _ = sys::aarch64_sysctl_cpu_id {
537             ac_midr: ffi.midr,
538             ac_revidr: ffi.revidr,
539             ac_mpidr: ffi.mpidr,
540             ac_aa64dfr0: ffi.aa64dfr0,
541             ac_aa64dfr1: ffi.aa64dfr1,
542             ac_aa64isar0: ffi.aa64isar0,
543             ac_aa64isar1: ffi.aa64isar1,
544             ac_aa64mmfr0: ffi.aa64mmfr0,
545             ac_aa64mmfr1: ffi.aa64mmfr1,
546             ac_aa64mmfr2: ffi.aa64mmfr2,
547             ac_aa64pfr0: ffi.aa64pfr0,
548             ac_aa64pfr1: ffi.aa64pfr1,
549             ac_aa64zfr0: ffi.aa64zfr0,
550             ac_mvfr0: ffi.mvfr0,
551             ac_mvfr1: ffi.mvfr1,
552             ac_mvfr2: ffi.mvfr2,
553             ac_pad: ffi.pad,
554             ac_clidr: ffi.clidr,
555             ac_ctr: ffi.ctr,
556         };
557     };
558     #[cfg(target_os = "openbsd")]
559     #[allow(
560         clippy::cast_possible_wrap,
561         clippy::cast_sign_loss,
562         clippy::no_effect_underscore_binding
563     )]
564     const _: fn() = || {
565         use imp::ffi;
566         use test_helper::{libc, sys};
567         let mut _sysctl: unsafe extern "C" fn(
568             *const ffi::c_int,
569             ffi::c_uint,
570             *mut ffi::c_void,
571             *mut ffi::c_size_t,
572             *mut ffi::c_void,
573             ffi::c_size_t,
574         ) -> ffi::c_int = ffi::sysctl;
575         _sysctl = libc::sysctl;
576         _sysctl = sys::sysctl;
577         static_assert!(ffi::CTL_MACHDEP == libc::CTL_MACHDEP);
578         static_assert!(ffi::CTL_MACHDEP == sys::CTL_MACHDEP as ffi::c_int);
579         // static_assert!(ffi::CPU_ID_AA64ISAR0 == libc::CPU_ID_AA64ISAR0); // libc doesn't have this
580         static_assert!(ffi::CPU_ID_AA64ISAR0 == sys::CPU_ID_AA64ISAR0 as ffi::c_int);
581         // static_assert!(ffi::CPU_ID_AA64ISAR1 == libc::CPU_ID_AA64ISAR1); // libc doesn't have this
582         static_assert!(ffi::CPU_ID_AA64ISAR1 == sys::CPU_ID_AA64ISAR1 as ffi::c_int);
583         // static_assert!(ffi::CPU_ID_AA64MMFR2 == libc::CPU_ID_AA64MMFR2); // libc doesn't have this
584         static_assert!(ffi::CPU_ID_AA64MMFR2 == sys::CPU_ID_AA64MMFR2 as ffi::c_int);
585     };
586 }
587