From 4bbd98771ea77200d913d0ed8bce4288e7befa47 Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 4 Nov 2025 19:43:14 +0900 Subject: [PATCH 01/12] feat(wc): add conditional SIMD support for counting with debug flag Add CPU feature detection to conditionally use SIMD-accelerated bytecount functions for chars and lines, falling back to naive methods when SIMD is disabled by environment or unavailable on CPU. Introduce a hidden --debug flag to output SIMD status information, aiding in troubleshooting performance issues. --- src/uu/wc/src/count_fast.rs | 15 ++++- src/uu/wc/src/cpu_features.rs | 108 ++++++++++++++++++++++++++++++++++ src/uu/wc/src/wc.rs | 37 ++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/uu/wc/src/cpu_features.rs diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index 9a473401e24..a291a1ae2c6 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. // cSpell:ignore sysconf -use crate::word_count::WordCount; +use crate::{cpu_features, word_count::WordCount}; use super::WordCountable; @@ -232,6 +232,7 @@ pub(crate) fn count_bytes_chars_and_lines_fast< ) -> (WordCount, Option) { let mut total = WordCount::default(); let buf: &mut [u8] = &mut AlignedBuffer::default().data; + let simd_allowed = cpu_features::simd_policy().env_allows_simd(); loop { match handle.read(buf) { Ok(0) => return (total, None), @@ -240,10 +241,18 @@ pub(crate) fn count_bytes_chars_and_lines_fast< total.bytes += n; } if COUNT_CHARS { - total.chars += bytecount::num_chars(&buf[..n]); + total.chars += if simd_allowed { + bytecount::num_chars(&buf[..n]) + } else { + bytecount::naive_num_chars(&buf[..n]) + }; } if COUNT_LINES { - total.lines += bytecount::count(&buf[..n], b'\n'); + total.lines += if simd_allowed { + bytecount::count(&buf[..n], b'\n') + } else { + bytecount::naive_count(&buf[..n], b'\n') + }; } } Err(ref e) if e.kind() == ErrorKind::Interrupted => (), diff --git a/src/uu/wc/src/cpu_features.rs b/src/uu/wc/src/cpu_features.rs new file mode 100644 index 00000000000..8ebf018be9e --- /dev/null +++ b/src/uu/wc/src/cpu_features.rs @@ -0,0 +1,108 @@ +// 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. + +use std::env; +use std::sync::OnceLock; + +#[derive(Debug)] +pub(crate) struct SimdPolicy { + disabled_by_env: Vec, + available: Vec<&'static str>, +} + +impl SimdPolicy { + fn detect() -> Self { + let tunables = env::var_os("GLIBC_TUNABLES") + .and_then(|value| value.into_string().ok()) + .unwrap_or_default(); + + let disabled_by_env = parse_disabled_features(&tunables); + let available = detect_available_features(); + + Self { + disabled_by_env, + available, + } + } + + pub(crate) fn env_allows_simd(&self) -> bool { + self.disabled_by_env.is_empty() + } + + pub(crate) fn disabled_features(&self) -> &[String] { + &self.disabled_by_env + } + + pub(crate) fn available_features(&self) -> &[&'static str] { + &self.available + } +} + +static SIMD_POLICY: OnceLock = OnceLock::new(); + +pub(crate) fn simd_policy() -> &'static SimdPolicy { + SIMD_POLICY.get_or_init(SimdPolicy::detect) +} + +fn parse_disabled_features(tunables: &str) -> Vec { + if tunables.is_empty() { + return Vec::new(); + } + + let mut disabled = Vec::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 !token.starts_with('-') { + continue; + } + let feature = token.trim_start_matches('-').to_ascii_uppercase(); + if !feature.is_empty() { + disabled.push(feature); + } + } + } + + disabled +} + +fn detect_available_features() -> Vec<&'static str> { + let mut features = Vec::new(); + #[cfg(target_arch = "x86")] + { + if std::arch::is_x86_feature_detected!("avx2") { + features.push("AVX2"); + } + if std::arch::is_x86_feature_detected!("sse2") { + features.push("SSE2"); + } + } + #[cfg(target_arch = "x86_64")] + { + if std::arch::is_x86_feature_detected!("avx2") { + features.push("AVX2"); + } + if std::arch::is_x86_feature_detected!("sse2") { + features.push("SSE2"); + } + } + #[cfg(all(target_arch = "aarch64", target_endian = "little"))] + { + if std::arch::is_aarch64_feature_detected!("asimd") { + features.push("ASIMD"); + } + } + features +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 44362e03fb4..9c1e1849265 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -7,6 +7,7 @@ mod count_fast; mod countable; +mod cpu_features; mod utf8; mod word_count; @@ -49,6 +50,7 @@ struct Settings<'a> { show_lines: bool, show_words: bool, show_max_line_length: bool, + debug: bool, files0_from: Option>, total_when: TotalWhen, } @@ -62,6 +64,7 @@ impl Default for Settings<'_> { show_lines: true, show_words: true, show_max_line_length: false, + debug: false, files0_from: None, total_when: TotalWhen::default(), } @@ -85,6 +88,7 @@ impl<'a> Settings<'a> { show_lines: matches.get_flag(options::LINES), show_words: matches.get_flag(options::WORDS), show_max_line_length: matches.get_flag(options::MAX_LINE_LENGTH), + debug: matches.get_flag(options::DEBUG), files0_from, total_when, }; @@ -95,6 +99,7 @@ impl<'a> Settings<'a> { Self { files0_from: settings.files0_from, total_when, + debug: settings.debug, ..Default::default() } } @@ -122,6 +127,7 @@ mod options { pub static MAX_LINE_LENGTH: &str = "max-line-length"; pub static TOTAL: &str = "total"; pub static WORDS: &str = "words"; + pub static DEBUG: &str = "debug"; } static ARG_FILES: &str = "files"; static STDIN_REPR: &str = "-"; @@ -445,6 +451,12 @@ pub fn uu_app() -> Command { .help(translate!("wc-help-words")) .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::DEBUG) + .long(options::DEBUG) + .action(ArgAction::SetTrue) + .hide(true), + ) .arg( Arg::new(ARG_FILES) .action(ArgAction::Append) @@ -814,6 +826,31 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { _ => (compute_number_width(inputs, settings), true), }; + if settings.debug { + let policy = cpu_features::simd_policy(); + if !policy.env_allows_simd() { + let disabled = policy.disabled_features(); + if disabled.is_empty() { + eprintln!("wc: debug: hardware support disabled by environment"); + } else { + eprintln!( + "wc: debug: hardware support disabled by GLIBC_TUNABLES ({})", + disabled.join(", ") + ); + } + } else { + let available = policy.available_features(); + if available.is_empty() { + eprintln!("wc: debug: hardware support unavailable on this CPU"); + } else { + eprintln!( + "wc: debug: using hardware support (features: {})", + available.join(", ") + ); + } + } + } + for maybe_input in inputs.try_iter(settings)? { num_inputs += 1; From 4752e2a6f4f3268d604235a0cd08078b899928ca Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 4 Nov 2025 19:49:59 +0900 Subject: [PATCH 02/12] refactor(wc): reorder SIMD debug output conditions for better readability Swap the if-else blocks in the wc function's debug output to check for SIMD allowance first, improving code flow without changing functionality. --- src/uu/wc/src/wc.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 9c1e1849265..718c753868e 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -828,17 +828,7 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { if settings.debug { let policy = cpu_features::simd_policy(); - if !policy.env_allows_simd() { - let disabled = policy.disabled_features(); - if disabled.is_empty() { - eprintln!("wc: debug: hardware support disabled by environment"); - } else { - eprintln!( - "wc: debug: hardware support disabled by GLIBC_TUNABLES ({})", - disabled.join(", ") - ); - } - } else { + if policy.env_allows_simd() { let available = policy.available_features(); if available.is_empty() { eprintln!("wc: debug: hardware support unavailable on this CPU"); @@ -848,6 +838,16 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { available.join(", ") ); } + } else { + let disabled = policy.disabled_features(); + if disabled.is_empty() { + eprintln!("wc: debug: hardware support disabled by environment"); + } else { + eprintln!( + "wc: debug: hardware support disabled by GLIBC_TUNABLES ({})", + disabled.join(", ") + ); + } } } From ead1881030784816d39cf7b8de7605cc79b356ae Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 4 Nov 2025 19:58:14 +0900 Subject: [PATCH 03/12] chore: add jargon words to spellcheck dictionary - Add asimd, ASIMD, hwcaps, tunables, TUNABLES to .vscode/cspell.dictionaries/jargon.wordlist.txt - Prevents spellchecker from flagging valid technical terms used in the codebase --- .vscode/cspell.dictionaries/jargon.wordlist.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 70cbd937933..a5c0d873f06 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -1,4 +1,6 @@ AFAICT +asimd +ASIMD alloc arity autogenerate @@ -70,6 +72,7 @@ hardlink hardlinks hasher hashsums +hwcaps infile iflag iflags @@ -145,6 +148,8 @@ tokenize toolchain totalram truthy +tunables +TUNABLES ucase unbuffered udeps From 6ea2596ddf34ad86fdb1217847aea7e60ac1930f Mon Sep 17 00:00:00 2001 From: mattsu <35655889+mattsu2020@users.noreply.github.com> Date: Wed, 5 Nov 2025 08:34:34 +0900 Subject: [PATCH 04/12] Update src/uu/wc/src/cpu_features.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dorian Péron <72708393+RenjiSann@users.noreply.github.com> --- src/uu/wc/src/cpu_features.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/uu/wc/src/cpu_features.rs b/src/uu/wc/src/cpu_features.rs index 8ebf018be9e..ae413ac73d5 100644 --- a/src/uu/wc/src/cpu_features.rs +++ b/src/uu/wc/src/cpu_features.rs @@ -80,16 +80,7 @@ fn parse_disabled_features(tunables: &str) -> Vec { fn detect_available_features() -> Vec<&'static str> { let mut features = Vec::new(); - #[cfg(target_arch = "x86")] - { - if std::arch::is_x86_feature_detected!("avx2") { - features.push("AVX2"); - } - if std::arch::is_x86_feature_detected!("sse2") { - features.push("SSE2"); - } - } - #[cfg(target_arch = "x86_64")] + #[cfg(any(target_arch = "x86", target_arch = "x86_64")] { if std::arch::is_x86_feature_detected!("avx2") { features.push("AVX2"); From a7400a66125de946db7415bd8a04dd63bc81db66 Mon Sep 17 00:00:00 2001 From: mattsu Date: Mon, 24 Nov 2025 21:15:18 +0900 Subject: [PATCH 05/12] refactor(wc): use uucore's SIMD policy instead of local cpu_features - Add "hardware" feature to uucore dependency in Cargo.toml - Replace local cpu_features module with uucore::hardware::simd_policy - Update debug output to use enabled_features() method for consistency --- src/uu/wc/Cargo.toml | 2 +- src/uu/wc/src/count_fast.rs | 5 +- src/uu/wc/src/cpu_features.rs | 99 ----------------------------------- src/uu/wc/src/wc.rs | 12 ++--- 4 files changed, 10 insertions(+), 108 deletions(-) delete mode 100644 src/uu/wc/src/cpu_features.rs diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 144fcd083ad..9b8d7199a26 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -19,7 +19,7 @@ path = "src/wc.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["parser", "pipes", "quoting-style"] } +uucore = { workspace = true, features = ["parser", "pipes", "quoting-style", "hardware"] } bytecount = { workspace = true, features = ["runtime-dispatch-simd"] } thiserror = { workspace = true } unicode-width = { workspace = true } diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index a291a1ae2c6..8804a378146 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -4,7 +4,8 @@ // file that was distributed with this source code. // cSpell:ignore sysconf -use crate::{cpu_features, word_count::WordCount}; +use crate::word_count::WordCount; +use uucore::hardware::simd_policy; use super::WordCountable; @@ -232,7 +233,7 @@ pub(crate) fn count_bytes_chars_and_lines_fast< ) -> (WordCount, Option) { let mut total = WordCount::default(); let buf: &mut [u8] = &mut AlignedBuffer::default().data; - let simd_allowed = cpu_features::simd_policy().env_allows_simd(); + let simd_allowed = simd_policy().allows_simd(); loop { match handle.read(buf) { Ok(0) => return (total, None), diff --git a/src/uu/wc/src/cpu_features.rs b/src/uu/wc/src/cpu_features.rs deleted file mode 100644 index ae413ac73d5..00000000000 --- a/src/uu/wc/src/cpu_features.rs +++ /dev/null @@ -1,99 +0,0 @@ -// 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. - -use std::env; -use std::sync::OnceLock; - -#[derive(Debug)] -pub(crate) struct SimdPolicy { - disabled_by_env: Vec, - available: Vec<&'static str>, -} - -impl SimdPolicy { - fn detect() -> Self { - let tunables = env::var_os("GLIBC_TUNABLES") - .and_then(|value| value.into_string().ok()) - .unwrap_or_default(); - - let disabled_by_env = parse_disabled_features(&tunables); - let available = detect_available_features(); - - Self { - disabled_by_env, - available, - } - } - - pub(crate) fn env_allows_simd(&self) -> bool { - self.disabled_by_env.is_empty() - } - - pub(crate) fn disabled_features(&self) -> &[String] { - &self.disabled_by_env - } - - pub(crate) fn available_features(&self) -> &[&'static str] { - &self.available - } -} - -static SIMD_POLICY: OnceLock = OnceLock::new(); - -pub(crate) fn simd_policy() -> &'static SimdPolicy { - SIMD_POLICY.get_or_init(SimdPolicy::detect) -} - -fn parse_disabled_features(tunables: &str) -> Vec { - if tunables.is_empty() { - return Vec::new(); - } - - let mut disabled = Vec::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 !token.starts_with('-') { - continue; - } - let feature = token.trim_start_matches('-').to_ascii_uppercase(); - if !feature.is_empty() { - disabled.push(feature); - } - } - } - - disabled -} - -fn detect_available_features() -> Vec<&'static str> { - let mut features = Vec::new(); - #[cfg(any(target_arch = "x86", target_arch = "x86_64")] - { - if std::arch::is_x86_feature_detected!("avx2") { - features.push("AVX2"); - } - if std::arch::is_x86_feature_detected!("sse2") { - features.push("SSE2"); - } - } - #[cfg(all(target_arch = "aarch64", target_endian = "little"))] - { - if std::arch::is_aarch64_feature_detected!("asimd") { - features.push("ASIMD"); - } - } - features -} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 718c753868e..00da6ab0f00 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -7,7 +7,6 @@ mod count_fast; mod countable; -mod cpu_features; mod utf8; mod word_count; @@ -28,6 +27,7 @@ use utf8::{BufReadDecoder, BufReadDecoderError}; use uucore::translate; use uucore::{ + hardware::simd_policy, error::{FromIo, UError, UResult}, format_usage, parser::shortcut_value_parser::ShortcutValueParser, @@ -827,15 +827,15 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { }; if settings.debug { - let policy = cpu_features::simd_policy(); - if policy.env_allows_simd() { - let available = policy.available_features(); - if available.is_empty() { + let policy = simd_policy(); + if policy.allows_simd() { + let enabled = policy.enabled_features(); + if enabled.is_empty() { eprintln!("wc: debug: hardware support unavailable on this CPU"); } else { eprintln!( "wc: debug: using hardware support (features: {})", - available.join(", ") + enabled.join(", ") ); } } else { From 78ec0098eb57046465b7cb3302d10e2e2a7efb78 Mon Sep 17 00:00:00 2001 From: mattsu Date: Mon, 24 Nov 2025 21:19:09 +0900 Subject: [PATCH 06/12] chore: alphabetize uucore imports in wc.rs Reorder the imports within the `use uucore::{ ... };` block for consistency and better readability. The previous order had `hardware::simd_policy` misplaced; it is now sorted alphabetically (e.g., error, format_usage, hardware, parser, etc.). No functional changes were made. --- src/uu/wc/src/wc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 00da6ab0f00..dd38ca72e1f 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -27,9 +27,9 @@ use utf8::{BufReadDecoder, BufReadDecoderError}; use uucore::translate; use uucore::{ - hardware::simd_policy, error::{FromIo, UError, UResult}, format_usage, + hardware::simd_policy, parser::shortcut_value_parser::ShortcutValueParser, quoting_style::{self, QuotingStyle}, show, From b2f7ead04b6295e04fe64b830f580bb21b5c4a34 Mon Sep 17 00:00:00 2001 From: mattsu Date: Mon, 24 Nov 2025 21:40:10 +0900 Subject: [PATCH 07/12] refactor(wc): sort dependencies in Cargo.toml alphabetically Reorder [dependencies] and [target.'cfg(unix)'.dependencies] sections in src/uu/wc/Cargo.toml to follow alphabetical order, improving file organization and consistency without changing functionality. --- src/uu/wc/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 9b8d7199a26..b299e332c27 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -18,16 +18,16 @@ workspace = true path = "src/wc.rs" [dependencies] -clap = { workspace = true } -uucore = { workspace = true, features = ["parser", "pipes", "quoting-style", "hardware"] } bytecount = { workspace = true, features = ["runtime-dispatch-simd"] } +clap = { workspace = true } +fluent = { workspace = true } thiserror = { workspace = true } +uucore = { workspace = true, features = ["hardware", "parser", "pipes", "quoting-style"] } unicode-width = { workspace = true } -fluent = { workspace = true } [target.'cfg(unix)'.dependencies] -nix = { workspace = true } libc = { workspace = true } +nix = { workspace = true } [dev-dependencies] divan = { workspace = true } From e5662a2ef164c040450fadaca21509a093a272c8 Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 25 Nov 2025 23:24:27 +0900 Subject: [PATCH 08/12] test(wc): add test for SIMD respect of GLIBC tunables Add a new Unix-specific test to ensure that SIMD optimizations in `wc` correctly respect GLIBC_TUNABLES, disabling SIMD paths for AVX2/AVX512 when specified. The test verifies both debug output accuracy and functional equivalence of line count results with and without these tunables, addressing potential correctness issues when hardware acceleration is restricted. --- tests/by-util/test_wc.rs | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index fa861a4c3f2..793825806db 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -808,3 +808,64 @@ fn wc_w_words_with_emoji_separator() { .succeeds() .stdout_contains("3"); } + +#[cfg(unix)] +#[test] +fn test_simd_respects_glibc_tunables() { + // Ensure debug output reflects that SIMD paths are disabled via GLIBC_TUNABLES + let debug_output = new_ucmd!() + .args(&["-l", "--debug", "/dev/null"]) + .env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX2,-AVX512F") + .succeeds() + .stderr_str() + .to_string(); + assert!( + !debug_output.contains("using hardware support"), + "SIMD should be reported as disabled when GLIBC_TUNABLES blocks AVX features: {debug_output}" + ); + assert!( + debug_output.contains("hardware support disabled"), + "Debug output should acknowledge GLIBC_TUNABLES restrictions: {debug_output}" + ); + + // WC results should be identical with and without GLIBC_TUNABLES overrides + let sample_sizes = [0usize, 1, 7, 128, 513, 999]; + for &lines in &sample_sizes { + let content: String = (0..lines).map(|i| format!("{i}\n")).collect(); + + let base = new_ucmd!() + .arg("-l") + .pipe_in(&content) + .succeeds() + .stdout_str() + .trim() + .to_string(); + + let no_avx512 = new_ucmd!() + .arg("-l") + .env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX512F") + .pipe_in(&content) + .succeeds() + .stdout_str() + .trim() + .to_string(); + + let no_avx2_avx512 = new_ucmd!() + .arg("-l") + .env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX2,-AVX512F") + .pipe_in(&content) + .succeeds() + .stdout_str() + .trim() + .to_string(); + + assert_eq!( + base, no_avx512, + "Line counts should not change when AVX512 is disabled (lines={lines})" + ); + assert_eq!( + base, no_avx2_avx512, + "Line counts should not change when AVX2/AVX512 are disabled (lines={lines})" + ); + } +} From dcf717452591025aca78a915439b3f239ba9a4be Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 25 Nov 2025 23:54:12 +0900 Subject: [PATCH 09/12] fix: adjust content ownership in SIMD GLIBC tunable test Use `content.clone()` for the first two `pipe_in` calls to avoid moving the string, and `content` directly for the third to consume it, ensuring proper Rust ownership handling in the test. This prevents borrow checker errors and allows the test to compile correctly. --- tests/by-util/test_wc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 793825806db..8ad47f00f80 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -835,7 +835,7 @@ fn test_simd_respects_glibc_tunables() { let base = new_ucmd!() .arg("-l") - .pipe_in(&content) + .pipe_in(content.clone()) .succeeds() .stdout_str() .trim() @@ -844,7 +844,7 @@ fn test_simd_respects_glibc_tunables() { let no_avx512 = new_ucmd!() .arg("-l") .env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX512F") - .pipe_in(&content) + .pipe_in(content.clone()) .succeeds() .stdout_str() .trim() @@ -853,7 +853,7 @@ fn test_simd_respects_glibc_tunables() { let no_avx2_avx512 = new_ucmd!() .arg("-l") .env("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX2,-AVX512F") - .pipe_in(&content) + .pipe_in(content) .succeeds() .stdout_str() .trim() From d1935f3415af54bed385b01d6c26d43939c44ec2 Mon Sep 17 00:00:00 2001 From: mattsu Date: Mon, 1 Dec 2025 23:23:55 +0900 Subject: [PATCH 10/12] chore(wc): reformat uucore features to multiline array Reformat the uucore dependency features in src/uu/wc/Cargo.toml from an inline array to a multiline array with proper indentation for improved readability and consistency. No functional changes; this is solely a styling update. --- src/uu/wc/Cargo.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index b299e332c27..ae9bb6e899b 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -22,7 +22,12 @@ bytecount = { workspace = true, features = ["runtime-dispatch-simd"] } clap = { workspace = true } fluent = { workspace = true } thiserror = { workspace = true } -uucore = { workspace = true, features = ["hardware", "parser", "pipes", "quoting-style"] } +uucore = { workspace = true, features = [ + "hardware", + "parser", + "pipes", + "quoting-style", +] } unicode-width = { workspace = true } [target.'cfg(unix)'.dependencies] From d23fc8b96d4400ea8daf2ea9f6184cda04aa701d Mon Sep 17 00:00:00 2001 From: mattsu Date: Mon, 1 Dec 2025 23:28:46 +0900 Subject: [PATCH 11/12] refactor(test_wc): optimize test input generation by avoiding per-line allocations Use std::fmt::Write and fold with writeln! to build content string efficiently, reducing memory allocations in test_simd_respects_glibc_tunables for better performance. --- tests/by-util/test_wc.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 8ad47f00f80..d1266e09d5c 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -830,8 +830,13 @@ fn test_simd_respects_glibc_tunables() { // WC results should be identical with and without GLIBC_TUNABLES overrides let sample_sizes = [0usize, 1, 7, 128, 513, 999]; + use std::fmt::Write as _; for &lines in &sample_sizes { - let content: String = (0..lines).map(|i| format!("{i}\n")).collect(); + let content: String = (0..lines).fold(String::new(), |mut acc, i| { + // Build the input buffer efficiently without allocating per line. + let _ = writeln!(acc, "{i}"); + acc + }); let base = new_ucmd!() .arg("-l") From 1a253442d19811d5585027fb4acf75dba13d7ad9 Mon Sep 17 00:00:00 2001 From: mattsu Date: Tue, 2 Dec 2025 08:28:28 +0900 Subject: [PATCH 12/12] feat(wc): internationalize hardware support debug messages Add debug messages for hardware acceleration in English and French locales, and update Rust code to use translate! macro instead of hardcoded strings. This ensures debug output is language-agnostic and translatable. --- src/uu/wc/locales/en-US.ftl | 6 ++++++ src/uu/wc/locales/fr-FR.ftl | 6 ++++++ src/uu/wc/src/wc.rs | 15 +++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/uu/wc/locales/en-US.ftl b/src/uu/wc/locales/en-US.ftl index 410eb3e6e26..52ea93291d8 100644 --- a/src/uu/wc/locales/en-US.ftl +++ b/src/uu/wc/locales/en-US.ftl @@ -31,3 +31,9 @@ decoder-error-io = underlying bytestream error: { $error } # Other messages wc-standard-input = standard input wc-total = total + +# Debug messages +wc-debug-hw-unavailable = wc: debug: hardware support unavailable on this CPU +wc-debug-hw-using = wc: debug: using hardware support (features: { $features }) +wc-debug-hw-disabled-env = wc: debug: hardware support disabled by environment +wc-debug-hw-disabled-glibc = wc: debug: hardware support disabled by GLIBC_TUNABLES ({ $features }) diff --git a/src/uu/wc/locales/fr-FR.ftl b/src/uu/wc/locales/fr-FR.ftl index e04d89fd9be..641e6846111 100644 --- a/src/uu/wc/locales/fr-FR.ftl +++ b/src/uu/wc/locales/fr-FR.ftl @@ -31,3 +31,9 @@ decoder-error-io = erreur du flux d'octets sous-jacent : { $error } # Autres messages wc-standard-input = entrée standard wc-total = total + +# Messages de débogage +wc-debug-hw-unavailable = wc : debug : prise en charge matérielle indisponible sur ce CPU +wc-debug-hw-using = wc : debug : utilisation de l'accélération matérielle (fonctions : { $features }) +wc-debug-hw-disabled-env = wc : debug : prise en charge matérielle désactivée par l'environnement +wc-debug-hw-disabled-glibc = wc : debug : prise en charge matérielle désactivée par GLIBC_TUNABLES ({ $features }) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index dd38ca72e1f..c7eb6bd8c7e 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -831,21 +831,24 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { if policy.allows_simd() { let enabled = policy.enabled_features(); if enabled.is_empty() { - eprintln!("wc: debug: hardware support unavailable on this CPU"); + eprintln!("{}", translate!("wc-debug-hw-unavailable")); } else { eprintln!( - "wc: debug: using hardware support (features: {})", - enabled.join(", ") + "{}", + translate!("wc-debug-hw-using", "features" => enabled.join(", ")) ); } } else { let disabled = policy.disabled_features(); if disabled.is_empty() { - eprintln!("wc: debug: hardware support disabled by environment"); + eprintln!("{}", translate!("wc-debug-hw-disabled-env")); } else { eprintln!( - "wc: debug: hardware support disabled by GLIBC_TUNABLES ({})", - disabled.join(", ") + "{}", + translate!( + "wc-debug-hw-disabled-glibc", + "features" => disabled.join(", ") + ) ); } }