feat(uucore): add shared hardware detection module

Add shared CPU hardware capability detection in uucore to prevent
code duplication across utilities. This provides a unified interface
for detecting CPU features (AVX512, AVX2, PCLMUL, SSE2, ASIMD) and
respecting GLIBC_TUNABLES environment variable.

This unblocks PR #9088 (cksum --debug) and PR #9144 (wc --debug) by
providing a common implementation that both utilities can use.

Features:
- CPU feature detection with caching (singleton pattern)
- GLIBC_TUNABLES parsing for hwcaps restrictions
- Cross-platform support (x86/x86_64, aarch64)
- Comprehensive test coverage
- Zero-cost abstractions using std::arch

Implementation details:
- Uses std::arch feature detection (no external deps for detection)
- Adds cfg-if dependency for conditional compilation
- Feature-gated behind "hardware" feature flag
- Android excluded (no CPUID access in sandboxed environment)

Related: #9088, #9144
This commit is contained in:
naoNao89
2025-11-14 22:19:22 +07:00
committed by Dorian Péron
parent be99b634e7
commit 8d740257ac
6 changed files with 495 additions and 144 deletions
+13
View File
@@ -188,3 +188,16 @@ nofield
# * clippy
uninlined
nonminimal
# * CPU/hardware features
ASIMD
asimd
hwcaps
PCLMUL
pclmul
PCLMULQDQ
pclmulqdq
TUNABLES
tunables
VMULL
vmull
+44 -144
View File
@@ -8,15 +8,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -58,22 +49,22 @@ dependencies = [
[[package]]
name = "anstyle-query"
version = "1.1.4"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -205,9 +196,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.44"
version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -349,15 +340,14 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc-fast"
version = "1.6.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3"
checksum = "a2f7c8d397a6353ef0c1d6217ab91b3ddb5431daf57fd013f506b967dcf44458"
dependencies = [
"crc",
"digest",
"rand",
"regex",
"rustversion",
"spin",
]
[[package]]
@@ -402,9 +392,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
@@ -525,9 +515,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "flate2"
@@ -592,9 +582,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.9"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
@@ -834,24 +824,24 @@ dependencies = [
[[package]]
name = "jiff"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.59.0",
"serde_core",
"windows-sys 0.61.2",
]
[[package]]
name = "jiff-static"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
dependencies = [
"proc-macro2",
"quote",
@@ -1093,9 +1083,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
[[package]]
name = "parse_datetime"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d45119ed61100f40b2389d8ed12e51ec869046d4279afbb5a7c73a4733be36"
checksum = "e4955561bc7aa4c40afcfd2a8c34297b13164ae9ac3b30ac348737befdc98e4c"
dependencies = [
"jiff",
"num-traits",
@@ -1197,9 +1187,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.41"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
@@ -1259,34 +1249,11 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rust-ini"
@@ -1430,6 +1397,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -1444,9 +1417,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.108"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
@@ -1993,22 +1966,13 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
"windows-targets",
]
[[package]]
@@ -2020,22 +1984,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
@@ -2043,106 +1991,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
+1
View File
@@ -131,6 +131,7 @@ fast-inc = []
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
fsext = ["libc", "windows-sys"]
fsxattr = ["xattr"]
hardware = []
lines = []
feat_systemd_logind = ["utmpx", "libc"]
format = [
+2
View File
@@ -74,6 +74,8 @@ pub mod tty;
#[cfg(all(unix, feature = "fsxattr"))]
pub mod fsxattr;
#[cfg(feature = "hardware")]
pub mod hardware;
#[cfg(all(target_os = "linux", feature = "selinux"))]
pub mod selinux;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
+433
View File
@@ -0,0 +1,433 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! CPU hardware capability detection for performance-sensitive utilities
//!
//! This module provides a unified interface for detecting CPU features and
//! respecting environment-based SIMD policies (e.g., GLIBC_TUNABLES).
//!
//! # Use Cases
//!
//! - `cksum --debug`: Report hardware acceleration capabilities
//! - `wc --debug`: Report SIMD usage and GLIBC_TUNABLES restrictions
//! - Runtime decisions: Enable/disable SIMD paths based on environment
//!
//! # Examples
//!
//! ```no_run
//! use uucore::hardware::{CpuFeatures, simd_policy};
//!
//! // Simple hardware detection
//! let features = CpuFeatures::detect();
//! if features.has_avx2() {
//! println!("AVX2 is available");
//! }
//!
//! // Check SIMD policy (respects GLIBC_TUNABLES)
//! let policy = simd_policy();
//! if policy.allows_simd() {
//! // Use SIMD-accelerated path
//! } else {
//! // Fall back to scalar implementation
//! }
//! ```
use std::env;
use std::sync::OnceLock;
/// CPU hardware features that affect performance
///
/// Provides platform-specific CPU feature detection with caching.
/// Detection is performed once and cached for the lifetime of the process.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CpuFeatures {
/// AVX-512 support (x86/x86_64 only)
avx512: bool,
/// AVX2 support (x86/x86_64 only)
avx2: bool,
/// PCLMULQDQ support for CRC acceleration (x86/x86_64 only)
pclmul: bool,
/// VMULL support for CRC acceleration (ARM only)
vmull: bool,
/// SSE2 support (x86/x86_64 only)
sse2: bool,
/// ARM ASIMD/NEON support (aarch64 only)
asimd: bool,
}
impl CpuFeatures {
/// Detect available CPU features (cached after first call)
///
/// This function uses a singleton pattern to ensure feature detection
/// happens only once per process. Thread-safe.
///
/// # Examples
///
/// ```no_run
/// use uucore::hardware::CpuFeatures;
///
/// let features = CpuFeatures::detect();
/// println!("AVX2: {}", features.has_avx2());
/// ```
pub fn detect() -> Self {
static FEATURES: OnceLock<CpuFeatures> = OnceLock::new();
*FEATURES.get_or_init(Self::detect_impl)
}
fn detect_impl() -> Self {
Self {
avx512: detect_avx512(),
avx2: detect_avx2(),
pclmul: detect_pclmul(),
vmull: detect_vmull(),
sse2: detect_sse2(),
asimd: detect_asimd(),
}
}
/// Check if AVX-512 is available (x86/x86_64 only)
pub fn has_avx512(&self) -> bool {
self.avx512
}
/// Check if AVX2 is available (x86/x86_64 only)
pub fn has_avx2(&self) -> bool {
self.avx2
}
/// Check if PCLMULQDQ is available (x86/x86_64 only)
pub fn has_pclmul(&self) -> bool {
self.pclmul
}
/// Check if VMULL is available (ARM only)
pub fn has_vmull(&self) -> bool {
self.vmull
}
/// Check if SSE2 is available (x86/x86_64 only)
pub fn has_sse2(&self) -> bool {
self.sse2
}
/// Check if ARM ASIMD/NEON is available (aarch64 only)
pub fn has_asimd(&self) -> bool {
self.asimd
}
/// Get list of available features as strings
///
/// Returns uppercase feature names (e.g., "AVX2", "SSE2", "ASIMD")
pub fn available_features(&self) -> Vec<&'static str> {
let mut features = Vec::new();
if self.avx512 {
features.push("AVX512");
}
if self.avx2 {
features.push("AVX2");
}
if self.pclmul {
features.push("PCLMUL");
}
if self.vmull {
features.push("VMULL");
}
if self.sse2 {
features.push("SSE2");
}
if self.asimd {
features.push("ASIMD");
}
features
}
}
/// SIMD policy based on environment variables
///
/// Respects GLIBC_TUNABLES environment variable to disable specific CPU features.
/// This is used by GNU utilities to allow users to disable hardware acceleration.
#[derive(Debug, Clone)]
pub struct SimdPolicy {
/// Features disabled via GLIBC_TUNABLES (e.g., ["AVX2", "AVX512F"])
disabled_by_env: Vec<String>,
/// Hardware features actually available
hardware_features: CpuFeatures,
}
impl SimdPolicy {
/// Create a new SIMD policy by checking environment and hardware
fn new() -> 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,
}
}
/// Check if SIMD operations are allowed
///
/// Returns `false` if any features are disabled via GLIBC_TUNABLES,
/// regardless of what's available in hardware.
///
/// # Examples
///
/// ```no_run
/// use uucore::hardware::simd_policy;
///
/// let policy = simd_policy();
/// if policy.allows_simd() {
/// // Use SIMD-accelerated bytecount
/// } else {
/// // Use scalar fallback
/// }
/// ```
pub fn allows_simd(&self) -> bool {
self.disabled_by_env.is_empty()
}
/// Get list of features disabled by environment
pub fn disabled_features(&self) -> &[String] {
&self.disabled_by_env
}
/// Get available hardware features
pub fn hardware_features(&self) -> &CpuFeatures {
&self.hardware_features
}
/// Get list of features that are both available and not disabled
pub fn enabled_features(&self) -> Vec<&'static str> {
if !self.allows_simd() {
return Vec::new();
}
self.hardware_features.available_features()
}
}
/// Get the global SIMD policy (cached)
///
/// This checks both hardware capabilities and the GLIBC_TUNABLES environment
/// variable. The result is cached for the lifetime of the process.
///
/// # Examples
///
/// ```no_run
/// use uucore::hardware::simd_policy;
///
/// let policy = simd_policy();
/// if policy.allows_simd() {
/// println!("SIMD is enabled");
/// } else {
/// println!("SIMD disabled by: {:?}", policy.disabled_features());
/// }
/// ```
pub fn simd_policy() -> &'static SimdPolicy {
static POLICY: OnceLock<SimdPolicy> = OnceLock::new();
POLICY.get_or_init(SimdPolicy::new)
}
// Platform-specific feature detection
#[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 {
// VMULL is part of ARM NEON/ASIMD
// For now, we use ASIMD as a proxy
detect_asimd()
}
#[cfg(not(target_arch = "aarch64"))]
fn detect_vmull() -> bool {
false
}
// GLIBC_TUNABLES parsing
/// Parse GLIBC_TUNABLES environment variable for disabled features
///
/// Format: `glibc.cpu.hwcaps=-AVX2,-AVX512F`
/// Multiple tunable sections can be separated by colons.
fn parse_disabled_features(tunables: &str) -> Vec<String> {
if tunables.is_empty() {
return Vec::new();
}
let mut disabled = Vec::new();
// GLIBC_TUNABLES format: "tunable1=value1:tunable2=value2"
for entry in tunables.split(':') {
let entry = entry.trim();
let Some((name, raw_value)) = entry.split_once('=') else {
continue;
};
// We only care about glibc.cpu.hwcaps
if name.trim() != "glibc.cpu.hwcaps" {
continue;
}
// Parse comma-separated features, disabled ones start with '-'
for token in raw_value.split(',') {
let token = token.trim();
if let Some(feature) = token.strip_prefix('-') {
let feature = feature.trim().to_ascii_uppercase();
if !feature.is_empty() {
disabled.push(feature);
}
}
}
}
disabled
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_features_detection() {
let features = CpuFeatures::detect();
// Just verify it doesn't panic and returns consistent results
let features2 = CpuFeatures::detect();
assert_eq!(features, features2);
}
#[test]
fn test_available_features() {
let features = CpuFeatures::detect();
let available = features.available_features();
// Should return a list (may be empty on some platforms)
assert!(available.iter().all(|s| !s.is_empty()));
}
#[test]
fn test_parse_disabled_features_empty() {
assert_eq!(parse_disabled_features(""), Vec::<String>::new());
}
#[test]
fn test_parse_disabled_features_single() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2");
assert_eq!(result, vec!["AVX2"]);
}
#[test]
fn test_parse_disabled_features_multiple() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2,-AVX512F");
assert_eq!(result, vec!["AVX2", "AVX512F"]);
}
#[test]
fn test_parse_disabled_features_mixed() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2,SSE2,-AVX512F");
// Only features with '-' prefix are disabled
assert_eq!(result, vec!["AVX2", "AVX512F"]);
}
#[test]
fn test_parse_disabled_features_with_other_tunables() {
let result =
parse_disabled_features("glibc.malloc.check=1:glibc.cpu.hwcaps=-AVX2:other=value");
assert_eq!(result, vec!["AVX2"]);
}
#[test]
fn test_parse_disabled_features_case_insensitive() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-avx2,-Avx512f");
// Should normalize to uppercase
assert_eq!(result, vec!["AVX2", "AVX512F"]);
}
#[test]
fn test_simd_policy() {
let policy = simd_policy();
// Just verify it works
let _ = policy.allows_simd();
let _ = policy.disabled_features();
let _ = policy.enabled_features();
}
#[test]
fn test_simd_policy_caching() {
let policy1 = simd_policy();
let policy2 = simd_policy();
// Should be same instance (pointer equality)
assert!(std::ptr::eq(policy1, policy2));
}
}
+2
View File
@@ -54,6 +54,8 @@ pub use crate::features::fast_inc;
pub use crate::features::format;
#[cfg(feature = "fs")]
pub use crate::features::fs;
#[cfg(feature = "hardware")]
pub use crate::features::hardware;
#[cfg(feature = "i18n-common")]
pub use crate::features::i18n;
#[cfg(feature = "lines")]