Skip to content

Commit da543cb

Browse files
fix(wc):GNU wc-cpu.sh (#9144)
* wc: align SIMD policy integration - use SimdPolicy::detect with hardware feature labeling - keep SIMD behavior respecting GLIBC_TUNABLES - consolidate wc SIMD debug output and tests * refactor(wc): simplify SIMD feature collection in debug output - Changed multi-line SIMD feature vector creation to a single-line expression for improved readability and consistency with surrounding code. - No functional changes; only stylistic refactoring in the wc debug logic. * feat(wc): enhance debug output for SIMD hardware support limitations Add new localization strings and logic to provide detailed debug information when SIMD support is limited by GLIBC_TUNABLES, including lists of disabled and enabled features. Refactor SIMD allowance check for better accuracy in detecting runtime support. * refactor: consolidate SIMD feature handling in wc command Refactor SIMD feature detection and reporting in the wc utility by introducing a WcSimdFeatures struct to group enabled, disabled, and runtime-disabled features. This replaces multiple separate functions with a single function, improving code organization and efficiency by reducing redundant iterations over feature lists. Also rename helper functions for clarity and update debug output logic accordingly. * Update src/uu/wc/src/wc.rs Co-authored-by: Dorian Péron <[email protected]> * Update src/uu/wc/locales/en-US.ftl Co-authored-by: Dorian Péron <[email protected]> * Update src/uu/wc/locales/fr-FR.ftl Co-authored-by: Dorian Péron <[email protected]> * feat(wc): import show_error for enhanced error reporting Add the show_error import from uucore to enable better error handling in the wc utility, allowing for consistent error messages in line with the project's style. --------- Co-authored-by: Dorian Péron <[email protected]>
1 parent ec063a7 commit da543cb

File tree

8 files changed

+236
-9
lines changed

8 files changed

+236
-9
lines changed

.vscode/cspell.dictionaries/jargon.wordlist.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
AFAICT
2+
asimd
3+
ASIMD
24
alloc
35
arity
46
autogenerate
@@ -71,6 +73,7 @@ hardlink
7173
hardlinks
7274
hasher
7375
hashsums
76+
hwcaps
7477
infile
7578
iflag
7679
iflags
@@ -150,6 +153,8 @@ tokenize
150153
toolchain
151154
totalram
152155
truthy
156+
tunables
157+
TUNABLES
153158
ucase
154159
unbuffered
155160
udeps

src/uu/wc/Cargo.toml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@ workspace = true
1818
path = "src/wc.rs"
1919

2020
[dependencies]
21-
clap = { workspace = true }
22-
uucore = { workspace = true, features = ["parser", "pipes", "quoting-style"] }
2321
bytecount = { workspace = true, features = ["runtime-dispatch-simd"] }
22+
clap = { workspace = true }
23+
fluent = { workspace = true }
2424
thiserror = { workspace = true }
25+
uucore = { workspace = true, features = [
26+
"hardware",
27+
"parser",
28+
"pipes",
29+
"quoting-style",
30+
] }
2531
unicode-width = { workspace = true }
26-
fluent = { workspace = true }
2732

2833
[target.'cfg(unix)'.dependencies]
29-
nix = { workspace = true }
3034
libc = { workspace = true }
35+
nix = { workspace = true }
3136

3237
[dev-dependencies]
3338
divan = { workspace = true }

src/uu/wc/locales/en-US.ftl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ decoder-error-io = underlying bytestream error: { $error }
3131
# Other messages
3232
wc-standard-input = standard input
3333
wc-total = total
34+
35+
# Debug messages
36+
wc-debug-hw-unavailable = debug: hardware support unavailable on this CPU
37+
wc-debug-hw-using = debug: using hardware support (features: { $features })
38+
wc-debug-hw-disabled-env = debug: hardware support disabled by environment
39+
wc-debug-hw-disabled-glibc = debug: hardware support disabled by GLIBC_TUNABLES ({ $features })
40+
wc-debug-hw-limited-glibc = debug: hardware support limited by GLIBC_TUNABLES (disabled: { $disabled }; enabled: { $enabled })

src/uu/wc/locales/fr-FR.ftl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ decoder-error-io = erreur du flux d'octets sous-jacent : { $error }
3131
# Autres messages
3232
wc-standard-input = entrée standard
3333
wc-total = total
34+
35+
# Messages de débogage
36+
wc-debug-hw-unavailable = debug : prise en charge matérielle indisponible sur ce CPU
37+
wc-debug-hw-using = debug : utilisation de l'accélération matérielle (fonctions : { $features })
38+
wc-debug-hw-disabled-env = debug : prise en charge matérielle désactivée par l'environnement
39+
wc-debug-hw-disabled-glibc = debug : prise en charge matérielle désactivée par GLIBC_TUNABLES ({ $features })
40+
wc-debug-hw-limited-glibc = debug : prise en charge matérielle limitée par GLIBC_TUNABLES (désactivé : { $disabled } ; activé : { $enabled })

src/uu/wc/src/count_fast.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
// file that was distributed with this source code.
55

66
// cSpell:ignore sysconf
7-
use crate::word_count::WordCount;
7+
use crate::{wc_simd_allowed, word_count::WordCount};
8+
use uucore::hardware::SimdPolicy;
89

910
use super::WordCountable;
1011

@@ -232,6 +233,8 @@ pub(crate) fn count_bytes_chars_and_lines_fast<
232233
) -> (WordCount, Option<io::Error>) {
233234
let mut total = WordCount::default();
234235
let buf: &mut [u8] = &mut AlignedBuffer::default().data;
236+
let policy = SimdPolicy::detect();
237+
let simd_allowed = wc_simd_allowed(policy);
235238
loop {
236239
match handle.read(buf) {
237240
Ok(0) => return (total, None),
@@ -240,10 +243,18 @@ pub(crate) fn count_bytes_chars_and_lines_fast<
240243
total.bytes += n;
241244
}
242245
if COUNT_CHARS {
243-
total.chars += bytecount::num_chars(&buf[..n]);
246+
total.chars += if simd_allowed {
247+
bytecount::num_chars(&buf[..n])
248+
} else {
249+
bytecount::naive_num_chars(&buf[..n])
250+
};
244251
}
245252
if COUNT_LINES {
246-
total.lines += bytecount::count(&buf[..n], b'\n');
253+
total.lines += if simd_allowed {
254+
bytecount::count(&buf[..n], b'\n')
255+
} else {
256+
bytecount::naive_count(&buf[..n], b'\n')
257+
};
247258
}
248259
}
249260
Err(ref e) if e.kind() == ErrorKind::Interrupted => (),

src/uu/wc/src/wc.rs

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ use uucore::translate;
2929
use uucore::{
3030
error::{FromIo, UError, UResult},
3131
format_usage,
32+
hardware::{HardwareFeature, HasHardwareFeatures as _, SimdPolicy},
3233
parser::shortcut_value_parser::ShortcutValueParser,
3334
quoting_style::{self, QuotingStyle},
34-
show,
35+
show, show_error,
3536
};
3637

3738
use crate::{
@@ -49,6 +50,7 @@ struct Settings<'a> {
4950
show_lines: bool,
5051
show_words: bool,
5152
show_max_line_length: bool,
53+
debug: bool,
5254
files0_from: Option<Input<'a>>,
5355
total_when: TotalWhen,
5456
}
@@ -62,6 +64,7 @@ impl Default for Settings<'_> {
6264
show_lines: true,
6365
show_words: true,
6466
show_max_line_length: false,
67+
debug: false,
6568
files0_from: None,
6669
total_when: TotalWhen::default(),
6770
}
@@ -85,6 +88,7 @@ impl<'a> Settings<'a> {
8588
show_lines: matches.get_flag(options::LINES),
8689
show_words: matches.get_flag(options::WORDS),
8790
show_max_line_length: matches.get_flag(options::MAX_LINE_LENGTH),
91+
debug: matches.get_flag(options::DEBUG),
8892
files0_from,
8993
total_when,
9094
};
@@ -95,6 +99,7 @@ impl<'a> Settings<'a> {
9599
Self {
96100
files0_from: settings.files0_from,
97101
total_when,
102+
debug: settings.debug,
98103
..Default::default()
99104
}
100105
}
@@ -122,6 +127,7 @@ mod options {
122127
pub static MAX_LINE_LENGTH: &str = "max-line-length";
123128
pub static TOTAL: &str = "total";
124129
pub static WORDS: &str = "words";
130+
pub static DEBUG: &str = "debug";
125131
}
126132
static ARG_FILES: &str = "files";
127133
static STDIN_REPR: &str = "-";
@@ -445,6 +451,12 @@ pub fn uu_app() -> Command {
445451
.help(translate!("wc-help-words"))
446452
.action(ArgAction::SetTrue),
447453
)
454+
.arg(
455+
Arg::new(options::DEBUG)
456+
.long(options::DEBUG)
457+
.action(ArgAction::SetTrue)
458+
.hide(true),
459+
)
448460
.arg(
449461
Arg::new(ARG_FILES)
450462
.action(ArgAction::Append)
@@ -805,6 +817,74 @@ fn escape_name_wrapper(name: &OsStr) -> String {
805817
.expect("All escaped names with the escaping option return valid strings.")
806818
}
807819

820+
fn hardware_feature_label(feature: HardwareFeature) -> &'static str {
821+
match feature {
822+
HardwareFeature::Avx512 => "AVX512F",
823+
HardwareFeature::Avx2 => "AVX2",
824+
HardwareFeature::PclMul => "PCLMUL",
825+
HardwareFeature::Vmull => "VMULL",
826+
HardwareFeature::Sse2 => "SSE2",
827+
HardwareFeature::Asimd => "ASIMD",
828+
}
829+
}
830+
831+
fn is_simd_runtime_feature(feature: &HardwareFeature) -> bool {
832+
matches!(
833+
feature,
834+
HardwareFeature::Avx2 | HardwareFeature::Sse2 | HardwareFeature::Asimd
835+
)
836+
}
837+
838+
fn is_simd_debug_feature(feature: &HardwareFeature) -> bool {
839+
matches!(
840+
feature,
841+
HardwareFeature::Avx512
842+
| HardwareFeature::Avx2
843+
| HardwareFeature::Sse2
844+
| HardwareFeature::Asimd
845+
)
846+
}
847+
848+
struct WcSimdFeatures {
849+
enabled: Vec<HardwareFeature>,
850+
disabled: Vec<HardwareFeature>,
851+
disabled_runtime: Vec<HardwareFeature>,
852+
}
853+
854+
fn wc_simd_features(policy: &SimdPolicy) -> WcSimdFeatures {
855+
let enabled = policy
856+
.iter_features()
857+
.filter(is_simd_runtime_feature)
858+
.collect();
859+
860+
let mut disabled = Vec::new();
861+
let mut disabled_runtime = Vec::new();
862+
for feature in policy.disabled_features() {
863+
if is_simd_debug_feature(&feature) {
864+
disabled.push(feature);
865+
}
866+
if is_simd_runtime_feature(&feature) {
867+
disabled_runtime.push(feature);
868+
}
869+
}
870+
871+
WcSimdFeatures {
872+
enabled,
873+
disabled,
874+
disabled_runtime,
875+
}
876+
}
877+
878+
pub(crate) fn wc_simd_allowed(policy: &SimdPolicy) -> bool {
879+
let disabled_features = policy.disabled_features();
880+
if disabled_features.iter().any(is_simd_runtime_feature) {
881+
return false;
882+
}
883+
policy
884+
.iter_features()
885+
.any(|feature| is_simd_runtime_feature(&feature))
886+
}
887+
808888
fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> {
809889
let mut total_word_count = WordCount::default();
810890
let mut num_inputs: usize = 0;
@@ -814,6 +894,51 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> {
814894
_ => (compute_number_width(inputs, settings), true),
815895
};
816896

897+
if settings.debug {
898+
let policy = SimdPolicy::detect();
899+
let features = wc_simd_features(policy);
900+
901+
let enabled: Vec<&'static str> = features
902+
.enabled
903+
.iter()
904+
.copied()
905+
.map(hardware_feature_label)
906+
.collect();
907+
let disabled: Vec<&'static str> = features
908+
.disabled
909+
.iter()
910+
.copied()
911+
.map(hardware_feature_label)
912+
.collect();
913+
914+
let enabled_empty = enabled.is_empty();
915+
let disabled_empty = disabled.is_empty();
916+
let runtime_disabled = !features.disabled_runtime.is_empty();
917+
918+
if enabled_empty && !runtime_disabled {
919+
show_error!("{}", translate!("wc-debug-hw-unavailable"));
920+
} else if runtime_disabled {
921+
show_error!(
922+
"{}",
923+
translate!("wc-debug-hw-disabled-glibc", "features" => disabled.join(", "))
924+
);
925+
} else if !enabled_empty && disabled_empty {
926+
show_error!(
927+
"{}",
928+
translate!("wc-debug-hw-using", "features" => enabled.join(", "))
929+
);
930+
} else {
931+
show_error!(
932+
"{}",
933+
translate!(
934+
"wc-debug-hw-limited-glibc",
935+
"disabled" => disabled.join(", "),
936+
"enabled" => enabled.join(", ")
937+
)
938+
);
939+
}
940+
}
941+
817942
for maybe_input in inputs.try_iter(settings)? {
818943
num_inputs += 1;
819944

src/uucore/src/lib/features/hardware.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@ impl SimdPolicy {
214214
}
215215
}
216216

217+
/// Returns true if any SIMD feature remains enabled after applying GLIBC_TUNABLES.
217218
pub fn allows_simd(&self) -> bool {
218-
self.disabled_by_env.is_empty()
219+
self.iter_features().next().is_some()
219220
}
220221

221222
pub fn disabled_features(&self) -> Vec<HardwareFeature> {

tests/by-util/test_wc.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,3 +808,69 @@ fn wc_w_words_with_emoji_separator() {
808808
.succeeds()
809809
.stdout_contains("3");
810810
}
811+
812+
#[cfg(unix)]
813+
#[test]
814+
fn test_simd_respects_glibc_tunables() {
815+
// Ensure debug output reflects that SIMD paths are disabled via GLIBC_TUNABLES
816+
let debug_output = new_ucmd!()
817+
.args(&["-l", "--debug", "/dev/null"])
818+
.env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX2,-AVX512F")
819+
.succeeds()
820+
.stderr_str()
821+
.to_string();
822+
assert!(
823+
!debug_output.contains("using hardware support"),
824+
"SIMD should be reported as disabled when GLIBC_TUNABLES blocks AVX features: {debug_output}"
825+
);
826+
assert!(
827+
debug_output.contains("hardware support disabled"),
828+
"Debug output should acknowledge GLIBC_TUNABLES restrictions: {debug_output}"
829+
);
830+
831+
// WC results should be identical with and without GLIBC_TUNABLES overrides
832+
let sample_sizes = [0usize, 1, 7, 128, 513, 999];
833+
use std::fmt::Write as _;
834+
for &lines in &sample_sizes {
835+
let content: String = (0..lines).fold(String::new(), |mut acc, i| {
836+
// Build the input buffer efficiently without allocating per line.
837+
let _ = writeln!(acc, "{i}");
838+
acc
839+
});
840+
841+
let base = new_ucmd!()
842+
.arg("-l")
843+
.pipe_in(content.clone())
844+
.succeeds()
845+
.stdout_str()
846+
.trim()
847+
.to_string();
848+
849+
let no_avx512 = new_ucmd!()
850+
.arg("-l")
851+
.env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX512F")
852+
.pipe_in(content.clone())
853+
.succeeds()
854+
.stdout_str()
855+
.trim()
856+
.to_string();
857+
858+
let no_avx2_avx512 = new_ucmd!()
859+
.arg("-l")
860+
.env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX2,-AVX512F")
861+
.pipe_in(content)
862+
.succeeds()
863+
.stdout_str()
864+
.trim()
865+
.to_string();
866+
867+
assert_eq!(
868+
base, no_avx512,
869+
"Line counts should not change when AVX512 is disabled (lines={lines})"
870+
);
871+
assert_eq!(
872+
base, no_avx2_avx512,
873+
"Line counts should not change when AVX2/AVX512 are disabled (lines={lines})"
874+
);
875+
}
876+
}

0 commit comments

Comments
 (0)