blob: d6bf9d002bc2c1acd05f551c74827b30a09936cc [file] [log] [blame]
// 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<u32> {
const OUT_LEN: ffi::c_size_t = core::mem::size_of::<u32>() 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::<ffi::c_char>(),
(&mut out as *mut u32).cast::<ffi::c_void>(),
&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 <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;
};
}