// SPDX-License-Identifier: Apache-2.0 OR MIT // Run-time feature detection on aarch64 macOS by using sysctl. // // This module is currently only enabled on tests because aarch64 macOS always supports FEAT_LSE and FEAT_LSE2. // https://github.com/llvm/llvm-project/blob/llvmorg-17.0.0-rc2/llvm/include/llvm/TargetParser/AArch64TargetParser.h#L494 // // If macOS supporting Armv9.4-a becomes popular in the future, this module will // be used to support outline-atomics for FEAT_LSE128/FEAT_LRCPC3. // // Refs: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics // // Note that iOS doesn't support sysctl: // - https://developer.apple.com/forums/thread/9440 // - https://nabla-c0d3.github.io/blog/2015/06/16/ios9-security-privacy include!("common.rs"); use core::ptr; // core::ffi::c_* (except c_void) requires Rust 1.64, libc will soon require Rust 1.47 #[allow(non_camel_case_types)] mod ffi { pub(crate) use super::c_types::{c_char, c_int, c_size_t, c_void}; extern "C" { // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname // https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/bsd/sys/sysctl.h // https://github.com/rust-lang/libc/blob/0.2.139/src/unix/bsd/apple/mod.rs#L5167-L5173 pub(crate) fn sysctlbyname( name: *const c_char, old_p: *mut c_void, old_len_p: *mut c_size_t, new_p: *mut c_void, new_len: c_size_t, ) -> c_int; } } unsafe fn sysctlbyname32(name: &[u8]) -> Option { const OUT_LEN: ffi::c_size_t = core::mem::size_of::() as ffi::c_size_t; debug_assert_eq!(name.last(), Some(&0), "{:?}", name); debug_assert_eq!(name.iter().filter(|&&v| v == 0).count(), 1, "{:?}", name); let mut out = 0_u32; let mut out_len = OUT_LEN; // SAFETY: // - the caller must guarantee that `name` a valid C string. // - `out_len` does not exceed the size of `out`. // - `sysctlbyname` is thread-safe. let res = unsafe { ffi::sysctlbyname( name.as_ptr().cast::(), (&mut out as *mut u32).cast::(), &mut out_len, ptr::null_mut(), 0, ) }; if res != 0 { return None; } debug_assert_eq!(out_len, OUT_LEN); Some(out) } #[cold] fn _detect(info: &mut CpuInfo) { // hw.optional.armv8_1_atomics is available on macOS 11+ (note: aarch64 support was added on macOS 11), // hw.optional.arm.FEAT_* are only available on macOS 12+. // Query both names in case future versions of macOS remove the old name. // https://github.com/golang/go/commit/c15593197453b8bf90fc3a9080ba2afeaf7934ea // https://github.com/google/boringssl/commit/91e0b11eba517d83b910b20fe3740eeb39ecb37e // SAFETY: we passed a valid C string. if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE\0").unwrap_or(0) != 0 || sysctlbyname32(b"hw.optional.armv8_1_atomics\0").unwrap_or(0) != 0 } { info.set(CpuInfo::HAS_LSE); } // SAFETY: we passed a valid C string. if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE2\0").unwrap_or(0) != 0 } { info.set(CpuInfo::HAS_LSE2); } // we currently only use FEAT_LSE and FEAT_LSE2 in outline-atomics. #[cfg(test)] { // SAFETY: we passed a valid C string. if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LSE128\0").unwrap_or(0) != 0 } { info.set(CpuInfo::HAS_LSE128); } // SAFETY: we passed a valid C string. if unsafe { sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC3\0").unwrap_or(0) != 0 } { info.set(CpuInfo::HAS_RCPC3); } } } #[allow( clippy::alloc_instead_of_core, clippy::std_instead_of_alloc, clippy::std_instead_of_core, clippy::undocumented_unsafe_blocks, clippy::wildcard_imports )] #[cfg(test)] mod tests { use super::*; #[test] fn test_macos() { unsafe { assert_eq!(sysctlbyname32(b"hw.optional.armv8_1_atomics\0"), Some(1)); assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE\0"), Some(1)); assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE2\0"), Some(1)); assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LSE128\0"), None); assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC\0"), Some(1)); assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC2\0"), Some(1)); assert_eq!(sysctlbyname32(b"hw.optional.arm.FEAT_LRCPC3\0"), None); assert_eq!(std::io::Error::last_os_error().kind(), std::io::ErrorKind::NotFound); } } // Static assertions for FFI bindings. // This checks that FFI bindings defined in this crate, FFI bindings defined // in libc, and FFI bindings generated for the platform's latest header file // using bindgen have compatible signatures (or the same values if constants). // Since this is static assertion, we can detect problems with // `cargo check --tests --target ` run in CI (via TESTS=1 build.sh) // without actually running tests on these platforms. // See also tools/codegen/src/ffi.rs. // TODO(codegen): auto-generate this test #[allow( clippy::cast_possible_wrap, clippy::cast_sign_loss, clippy::no_effect_underscore_binding )] const _: fn() = || { use test_helper::{libc, sys}; let mut _sysctlbyname: unsafe extern "C" fn( *const ffi::c_char, *mut ffi::c_void, *mut ffi::c_size_t, *mut ffi::c_void, ffi::c_size_t, ) -> ffi::c_int = ffi::sysctlbyname; _sysctlbyname = libc::sysctlbyname; _sysctlbyname = sys::sysctlbyname; }; }