use std::collections::BTreeSet;
use std::env;
use std::sync::OnceLock;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum HardwareFeature {
Avx512,
Avx2,
PclMul,
Vmull,
Sse2,
Asimd,
}
pub struct InvalidHardwareFeature;
impl TryFrom<&str> for HardwareFeature {
type Error = InvalidHardwareFeature;
fn try_from(value: &str) -> Result<Self, Self::Error> {
use HardwareFeature::*;
match value {
"AVX512" | "AVX512F" => Ok(Avx512),
"AVX2" => Ok(Avx2),
"PCLMUL" | "PMULL" => Ok(PclMul),
"VMULL" => Ok(Vmull),
"SSE2" => Ok(Sse2),
"ASIMD" => Ok(Asimd),
_ => Err(InvalidHardwareFeature),
}
}
}
pub trait HasHardwareFeatures {
fn has_feature(&self, feat: HardwareFeature) -> bool;
fn iter_features(&self) -> impl Iterator<Item = HardwareFeature>;
#[inline]
fn has_avx512(&self) -> bool {
self.has_feature(HardwareFeature::Avx512)
}
#[inline]
fn has_avx2(&self) -> bool {
self.has_feature(HardwareFeature::Avx2)
}
#[inline]
fn has_pclmul(&self) -> bool {
self.has_feature(HardwareFeature::PclMul)
}
#[inline]
fn has_vmull(&self) -> bool {
self.has_feature(HardwareFeature::Vmull)
}
#[inline]
fn has_sse2(&self) -> bool {
self.has_feature(HardwareFeature::Sse2)
}
#[inline]
fn has_asimd(&self) -> bool {
self.has_feature(HardwareFeature::Asimd)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CpuFeatures {
set: BTreeSet<HardwareFeature>,
}
impl CpuFeatures {
pub fn detect() -> &'static Self {
static FEATURES: OnceLock<CpuFeatures> = OnceLock::new();
FEATURES.get_or_init(Self::detect_impl)
}
fn detect_impl() -> Self {
let set = [
(HardwareFeature::Avx512, detect_avx512 as fn() -> bool),
(HardwareFeature::Avx2, detect_avx2),
(HardwareFeature::PclMul, detect_pclmul),
(HardwareFeature::Vmull, detect_vmull),
(HardwareFeature::Sse2, detect_sse2),
(HardwareFeature::Asimd, detect_asimd),
]
.into_iter()
.filter_map(|(feat, detect)| detect().then_some(feat))
.collect();
Self { set }
}
}
impl HasHardwareFeatures for CpuFeatures {
fn has_feature(&self, feat: HardwareFeature) -> bool {
self.set.contains(&feat)
}
fn iter_features(&self) -> impl Iterator<Item = HardwareFeature> {
self.set.iter().copied()
}
}
#[derive(Debug, Clone)]
pub struct SimdPolicy {
disabled_by_env: BTreeSet<HardwareFeature>,
hardware_features: &'static CpuFeatures,
}
impl SimdPolicy {
pub fn detect() -> &'static Self {
static POLICY: OnceLock<SimdPolicy> = OnceLock::new();
POLICY.get_or_init(Self::detect_impl)
}
fn detect_impl() -> Self {
let tunables = env::var("GLIBC_TUNABLES").unwrap_or_default();
let disabled_by_env = parse_disabled_features(&tunables);
let hardware_features = CpuFeatures::detect();
Self {
disabled_by_env,
hardware_features,
}
}
pub fn allows_simd(&self) -> bool {
self.disabled_by_env.is_empty()
}
pub fn disabled_features(&self) -> Vec<HardwareFeature> {
self.disabled_by_env.iter().copied().collect()
}
}
impl HasHardwareFeatures for SimdPolicy {
fn has_feature(&self, feat: HardwareFeature) -> bool {
self.hardware_features.has_feature(feat) && !self.disabled_by_env.contains(&feat)
}
fn iter_features(&self) -> impl Iterator<Item = HardwareFeature> {
self.hardware_features
.set
.difference(&self.disabled_by_env)
.copied()
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx512() -> bool {
if cfg!(target_os = "android") {
false
} else {
std::arch::is_x86_feature_detected!("avx512f")
&& std::arch::is_x86_feature_detected!("avx512bw")
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx512() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx2() -> bool {
if cfg!(target_os = "android") {
false
} else {
std::arch::is_x86_feature_detected!("avx2")
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx2() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_pclmul() -> bool {
if cfg!(target_os = "android") {
false
} else {
std::arch::is_x86_feature_detected!("pclmulqdq")
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_pclmul() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_sse2() -> bool {
if cfg!(target_os = "android") {
false
} else {
std::arch::is_x86_feature_detected!("sse2")
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_sse2() -> bool {
false
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
fn detect_asimd() -> bool {
if cfg!(target_os = "android") {
false
} else {
std::arch::is_aarch64_feature_detected!("asimd")
}
}
#[cfg(not(all(target_arch = "aarch64", target_endian = "little")))]
fn detect_asimd() -> bool {
false
}
#[cfg(target_arch = "aarch64")]
fn detect_vmull() -> bool {
detect_asimd()
}
#[cfg(not(target_arch = "aarch64"))]
fn detect_vmull() -> bool {
false
}
fn parse_disabled_features(tunables: &str) -> BTreeSet<HardwareFeature> {
if tunables.is_empty() {
return BTreeSet::new();
}
let mut disabled = BTreeSet::new();
for entry in tunables.split(':') {
let entry = entry.trim();
let Some((name, raw_value)) = entry.split_once('=') else {
continue;
};
if name.trim() != "glibc.cpu.hwcaps" {
continue;
}
for token in raw_value.split(',') {
let token = token.trim();
if let Some(feature) = token.strip_prefix('-') {
let feature =
HardwareFeature::try_from(feature.trim().to_ascii_uppercase().as_str());
if let Ok(feature) = feature {
disabled.insert(feature);
}
}
}
}
disabled
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_features_detection() {
let features = CpuFeatures::detect();
let features2 = CpuFeatures::detect();
assert_eq!(features, features2);
}
#[test]
fn test_parse_disabled_features_empty() {
assert_eq!(parse_disabled_features(""), BTreeSet::new());
}
#[test]
fn test_parse_disabled_features_single() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2");
let mut expected = BTreeSet::new();
expected.insert(HardwareFeature::Avx2);
assert_eq!(result, expected);
}
#[test]
fn test_parse_disabled_features_multiple() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2,-AVX512F");
let mut expected = BTreeSet::new();
expected.insert(HardwareFeature::Avx2);
expected.insert(HardwareFeature::Avx512);
assert_eq!(result, expected);
}
#[test]
fn test_parse_disabled_features_mixed() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2,SSE2,-AVX512F");
let mut expected = BTreeSet::new();
expected.insert(HardwareFeature::Avx2);
expected.insert(HardwareFeature::Avx512);
assert_eq!(result, expected);
}
#[test]
fn test_parse_disabled_features_with_other_tunables() {
let result =
parse_disabled_features("glibc.malloc.check=1:glibc.cpu.hwcaps=-AVX2:other=value");
let mut expected = BTreeSet::new();
expected.insert(HardwareFeature::Avx2);
assert_eq!(result, expected);
}
#[test]
fn test_parse_disabled_features_case_insensitive() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-avx2,-Avx512f");
let mut expected = BTreeSet::new();
expected.insert(HardwareFeature::Avx2);
expected.insert(HardwareFeature::Avx512);
assert_eq!(result, expected);
}
#[test]
fn test_simd_policy() {
let policy = SimdPolicy::detect();
let _ = policy.allows_simd();
}
#[test]
fn test_simd_policy_caching() {
let policy1 = SimdPolicy::detect();
let policy2 = SimdPolicy::detect();
assert!(std::ptr::eq(policy1, policy2));
}
}