Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 56 additions & 40 deletions src/uucore/src/lib/features/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,43 +324,25 @@ fn detect_vmull() -> bool {

// 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.
/// Parse the `glibc.cpu.hwcaps` setting of `GLIBC_TUNABLES` into the set of
/// disabled features. Only the last setting counts and names are
/// case-sensitive.
fn parse_disabled_features(tunables: &str) -> BTreeSet<HardwareFeature> {
if tunables.is_empty() {
let Some(value) = tunables
.split(':')
.filter_map(|setting| setting.split_once('='))
.filter(|(name, _)| name.trim_start() == "glibc.cpu.hwcaps")
.map(|(_, value)| value)
.next_back()
else {
return BTreeSet::new();
}

let mut disabled = BTreeSet::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 =
HardwareFeature::try_from(feature.trim().to_ascii_uppercase().as_str());
if let Ok(feature) = feature {
disabled.insert(feature);
}
}
}
}

disabled
value
.split(',')
.filter_map(|token| token.trim_start().strip_prefix('-'))
Comment thread
ChihweiLHBird marked this conversation as resolved.
.filter_map(|name| HardwareFeature::try_from(name).ok())
.collect()
}

#[cfg(test)]
Expand Down Expand Up @@ -425,15 +407,49 @@ mod tests {
}

#[test]
fn test_parse_disabled_features_case_insensitive() {
fn test_parse_disabled_features_is_case_sensitive() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-avx2,-Avx512f");
let mut expected = BTreeSet::new();
assert_eq!(result, BTreeSet::new());
}

expected.insert(HardwareFeature::Avx2);
expected.insert(HardwareFeature::Avx512);
#[test]
fn test_parse_disabled_features_last_setting_wins() {
let result =
parse_disabled_features("glibc.cpu.hwcaps=-AVX2:glibc.cpu.hwcaps=-AVX512F:x=y");
assert_eq!(result, BTreeSet::from([HardwareFeature::Avx512]));
}

// Only features with '-' prefix are disabled
assert_eq!(result, expected);
#[test]
fn test_parse_disabled_features_whole_names_only() {
// A space after the name or after the dash is not a name.
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2 ,- AVX512F");
assert_eq!(result, BTreeSet::new());
// A space before the dash is tolerated.
let result = parse_disabled_features("glibc.cpu.hwcaps= -AVX2, -AVX512F");
assert_eq!(
result,
BTreeSet::from([HardwareFeature::Avx2, HardwareFeature::Avx512])
);
}

#[test]
fn test_parse_disabled_features_ignores_invalid_last_setting() {
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2:glibc.cpu.hwcaps =");
Comment thread
ChihweiLHBird marked this conversation as resolved.
assert_eq!(result, BTreeSet::from([HardwareFeature::Avx2]));
let result = parse_disabled_features("glibc.cpu.hwcaps=-AVX2: glibc.cpu.hwcaps=-SSE2");
assert_eq!(result, BTreeSet::from([HardwareFeature::Sse2]));
}

#[test]
fn test_parse_disabled_features_empty_setting_enables_everything() {
assert_eq!(
parse_disabled_features("glibc.cpu.hwcaps="),
BTreeSet::new()
);
assert_eq!(
parse_disabled_features("glibc.cpu.hwcaps=-AVX2:glibc.cpu.hwcaps="),
BTreeSet::new()
);
}

#[test]
Expand Down
23 changes: 23 additions & 0 deletions tests/by-util/test_cksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3171,6 +3171,29 @@ mod debug_flag {
.stderr_contains("pclmul");
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn test_debug_with_glibc_tunables() {
if is_x86_feature_detected!("avx2") {
for (tunables, expected) in [
("glibc.cpu.hwcaps=-AVX2", "avx2 support not detected"), // correctly disabling AVX2
("glibc.cpu.hwcaps=-avx2", "using avx2 hardware support"), // lowercase invalidates AVX2 disabling
(
"glibc.cpu.hwcaps=-AVX2:glibc.cpu.hwcaps=-AVX512F",
"using avx2 hardware support",
), // last wins
("glibc.cpu.hwcaps=-AVX2 ", "using avx2 hardware support"), // trailing spaces invalidate AVX2 disabling
] {
new_ucmd!()
.arg("--debug")
.arg("lorem_ipsum.txt")
.env("GLIBC_TUNABLES", tunables)
.succeeds()
.stderr_contains(expected);
}
}
}

#[test]
#[cfg(target_arch = "aarch64")]
fn test_debug_flag_aarch64() {
Expand Down
Loading