This is useful to give better test coverage at least,
and may be useful for users who already may have
GLIBC_TUNABLES defined to tune their environment,
avoiding CPU throttling for example.
* src/cpu-supports.h: A new header that provides cpu_supports()
that checks the GLIBC_TUNABLES environment variable allows
the hardware feature, before checking with __builtin_cpu_supports().
* src/cksum.c: Use cpu_supports() rather than __builtin_cpu_supports().
* src/wc.c: Likewise.
* src/local.mk: Reference the new header.
* tests/cksum/cksum.sh: Adjust to testing all implementations.
* tests/wc/wc-cpu.sh: A new test to do likewise.
* tests/local.mk: Reference the new wc test.
---
src/cksum.c | 25 ++++++++------
src/cpu-supports.h | 78 ++++++++++++++++++++++++++++++++++++++++++++
src/local.mk | 1 +
src/wc.c | 3 +-
tests/cksum/cksum.sh | 23 +++++++++----
tests/local.mk | 1 +
tests/wc/wc-cpu.sh | 34 +++++++++++++++++++
7 files changed, 148 insertions(+), 17 deletions(-)
create mode 100644 src/cpu-supports.h
create mode 100755 tests/wc/wc-cpu.sh
diff --git a/src/cksum.c b/src/cksum.c
index 310986912..e8273c87d 100644
--- a/src/cksum.c
+++ b/src/cksum.c
@@ -134,6 +134,7 @@ main (void)
# endif
# include "crc.h"
+# include "cpu-supports.h"
/* Number of bytes to read at once. */
# define BUFLEN (1 << 16)
@@ -143,9 +144,9 @@ typedef bool (*cksum_fp_t) (FILE *, uint_fast32_t *,
uintmax_t *);
static cksum_fp_t
pclmul_supported (void)
{
-# if USE_PCLMUL_CRC32 || GL_CRC_X86_64_PCLMUL
- bool pclmul_enabled = (0 < __builtin_cpu_supports ("pclmul")
- && 0 < __builtin_cpu_supports ("avx"));
+# if USE_PCLMUL_CRC32
+ bool pclmul_enabled = (cpu_supports ("pclmul")
+ && cpu_supports ("avx"));
if (cksum_debug)
error (0, 0, "%s",
(pclmul_enabled
@@ -165,8 +166,8 @@ avx2_supported (void)
the avx512 version, but it implies that the avx2 version
is supported */
# if USE_AVX2_CRC32
- bool avx2_enabled = (0 < __builtin_cpu_supports ("vpclmulqdq")
- && 0 < __builtin_cpu_supports ("avx2"));
+ bool avx2_enabled = (cpu_supports ("vpclmulqdq")
+ && cpu_supports ("avx2"));
if (cksum_debug)
error (0, 0, "%s",
(avx2_enabled
@@ -186,9 +187,9 @@ avx512_supported (void)
mavx512f for most of the avx512 functions we're using
mavx512bw for byte swapping */
# if USE_AVX512_CRC32
- bool avx512_enabled = (0 < __builtin_cpu_supports ("vpclmulqdq")
- && 0 < __builtin_cpu_supports ("avx512bw")
- && 0 < __builtin_cpu_supports ("avx512f"));
+ bool avx512_enabled = (cpu_supports ("vpclmulqdq")
+ && cpu_supports ("avx512bw")
+ && cpu_supports ("avx512f"));
if (cksum_debug)
error (0, 0, "%s",
(avx512_enabled
@@ -206,7 +207,8 @@ vmull_supported (void)
{
/* vmull for multiplication */
# if USE_VMULL_CRC32
- bool vmull_enabled = (getauxval (AT_HWCAP) & HWCAP_PMULL) > 0;
+ bool vmull_enabled = (cpu_may_support ("pmull")
+ && ((getauxval (AT_HWCAP) & HWCAP_PMULL) > 0);
if (cksum_debug)
error (0, 0, "%s",
(vmull_enabled
@@ -325,7 +327,10 @@ crc32b_sum_stream (FILE *stream, void *resstream,
uintmax_t *reslen)
# if GL_CRC_X86_64_PCLMUL
if (cksum_debug)
- (void) pclmul_supported ();
+ error (0, 0, "%s",
+ (__builtin_cpu_supports ("pclmul") /* Match gnulib. */
+ ? _("using pclmul hardware support")
+ : _("pclmul support not detected")));
# endif
while ((bytes_read = fread (buf, 1, BUFLEN, stream)) > 0)
diff --git a/src/cpu-supports.h b/src/cpu-supports.h
new file mode 100644
index 000000000..4ac27cb54
--- /dev/null
+++ b/src/cpu-supports.h
@@ -0,0 +1,78 @@
+#include <assert.h>
+
+#define cpu_supports(feature) \
+ (cpu_may_support (feature) && __builtin_cpu_supports (feature))
+
+/* Support GLIBC's interface to disable features using:
+ export GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX512F,-AVX2,-AVX,-PMULL
+ Return true if the feature is NOT disabled. */
+static inline bool
+cpu_may_support (char const* feature)
+{
+ typedef struct {
+ char const *gcc;
+ char const *glibc;
+ } tunables_t;
+
+ static const tunables_t tunables[] = {
+ {"pclmul", "-PCLMULQDQ"},
+ {"avx", "-AVX"},
+
+ {"vpclmulqdq", "-VPCLMULQDQ"},
+ {"avx2", "-AVX2"},
+
+ {"avx512bw", "-AVX512BW"},
+ {"avx512f", "-AVX512F"},
+
+ {"pmull", "-PMULL"}, /* Not supported by glibc. */
+
+ {nullptr, nullptr},
+ };
+
+ char const* glibc_feature = nullptr;
+
+ tunables_t const *tunable = tunables;
+ do
+ if (STREQ (tunable->gcc, feature))
+ {
+ glibc_feature = tunable->glibc;
+ break;
+ }
+ while ((++tunable)->gcc);
+ assert (glibc_feature); /* Ensure mappings for all. */
+
+ /* Match how GLIBC parses tunables as indicated with:
+ GLIBC_TUNABLES=glibc.cpu.hwcaps=... ld.so --list-tunables | grep hwcaps
*/
+ static char const *GLIBC_TUNABLES;
+ if (! GLIBC_TUNABLES)
+ { /* Cache glibc.cpu.hwcaps once per process. */
+ if ((GLIBC_TUNABLES = getenv ("GLIBC_TUNABLES")))
+ {
+ char const *tunables_start = GLIBC_TUNABLES;
+ char const *last_hwcaps;
+ while ((last_hwcaps = strstr (GLIBC_TUNABLES, "glibc.cpu.hwcaps=")))
+ GLIBC_TUNABLES = last_hwcaps + sizeof "glibc.cpu.hwcaps=" - 1;
+ if (GLIBC_TUNABLES == tunables_start) /* No match. */
+ GLIBC_TUNABLES = "";
+ }
+ else
+ GLIBC_TUNABLES = "";
+ }
+
+ if (GLIBC_TUNABLES && *GLIBC_TUNABLES)
+ {
+ char const *sentinel = strchr (GLIBC_TUNABLES, ':');
+ if (! sentinel)
+ sentinel = GLIBC_TUNABLES + strlen (GLIBC_TUNABLES);
+ char const *cap = GLIBC_TUNABLES;
+ while ((cap = strstr (cap, glibc_feature)) && cap < sentinel)
+ { /* Check it's not a partial match. */
+ cap += strlen (glibc_feature);
+ if (*cap == ',' || *cap == ':' || *cap == '\0')
+ return false; /* Feature disabled. */
+ /* glibc hwcaps can't have '-' in name so ok to search from here. */
+ }
+ }
+
+ return true;
+}
diff --git a/src/local.mk b/src/local.mk
index 3f93a7507..87cedbe88 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -43,6 +43,7 @@ noinst_HEADERS = \
src/chown-core.h \
src/copy.h \
src/cp-hash.h \
+ src/cpu-supports.h \
src/dircolors.h \
src/expand-common.h \
src/find-mount-point.h \
diff --git a/src/wc.c b/src/wc.c
index 268b947bb..5f974e1c0 100644
--- a/src/wc.c
+++ b/src/wc.c
@@ -33,6 +33,7 @@
#include <xbinary-io.h>
#include "system.h"
+#include "cpu-supports.h"
#include "ioblksize.h"
#include "wc.h"
@@ -133,7 +134,7 @@ static enum total_type total_mode = total_auto;
static bool
avx2_supported (void)
{
- bool avx_enabled = 0 < __builtin_cpu_supports ("avx2");
+ bool avx_enabled = cpu_supports ("avx2");
if (debug)
error (0, 0, (avx_enabled
diff --git a/tests/cksum/cksum.sh b/tests/cksum/cksum.sh
index 83f61ad2f..e216183c6 100755
--- a/tests/cksum/cksum.sh
+++ b/tests/cksum/cksum.sh
@@ -22,15 +22,26 @@ print_ver_ cksum printf
returns_ 1 cksum missing 2> /dev/null || fail=1
+GLIBC_TUNABLES='glibc.cpu.hwcaps=-AVX512F,-AVX2,-AVX,-PMULL' \
+ cksum --debug /dev/null 2>debug || fail=1
+grep 'using.*hardware support' debug && fail=1
+
# Pass in expected crc and crc32b for file "in"
# Sets fail=1 upon failure
crc_check() {
- for crct in crc crc32b; do
- cksum -a $crct in > out || fail=1
- case "$crct" in crc) crce="$1";; crc32b) crce="$2";; esac
- size=$(stat -c %s in) || framework_failure_
- printf '%s\n' "$crce $size in" > exp || framework_failure_
- compare exp out || fail=1
+ TUNABLE_DISABLE='glibc.cpu.hwcaps='
+ for DHW in NONE AVX512F AVX2 AVX PMULL; do
+ TUNABLE_DISABLE="$TUNABLE_DISABLE-$DHW,"
+ for crct in crc crc32b; do
+ GLIBC_TUNABLES="$TUNABLE_DISABLE" \
+ cksum -a $crct in || fail=1
+ GLIBC_TUNABLES="$TUNABLE_DISABLE" \
+ cksum -a $crct in > out || fail=1
+ case "$crct" in crc) crce="$1";; crc32b) crce="$2";; esac
+ size=$(stat -c %s in) || framework_failure_
+ printf '%s\n' "$crce $size in" > exp || framework_failure_
+ compare exp out || fail=1
+ done
done
}
diff --git a/tests/local.mk b/tests/local.mk
index 885787c3a..67a919e84 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -283,6 +283,7 @@ all_tests = \
tests/cut/cut.pl \
tests/cut/cut-huge-range.sh \
tests/wc/wc.pl \
+ tests/wc/wc-cpu.sh \
tests/wc/wc-files0-from.pl \
tests/wc/wc-files0.sh \
tests/wc/wc-nbsp.sh \
diff --git a/tests/wc/wc-cpu.sh b/tests/wc/wc-cpu.sh
new file mode 100755
index 000000000..725817a7c
--- /dev/null
+++ b/tests/wc/wc-cpu.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Ensure cpu specific code operates correctly
+
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ wc
+
+GLIBC_TUNABLES='glibc.cpu.hwcaps=-AVX2' \
+ wc --debug /dev/null 2>debug || fail=1
+grep 'using.*hardware support' debug && fail=1
+
+lines=$(shuf -i 0-1000 | head -n1) || framework_failure_
+seq 1000 | head -n "$lines" > lines || framework_failure_
+
+wc_accelerated=$(wc -l < lines) || fail=1
+wc_base=$(GLIBC_TUNABLES='glibc.cpu.hwcaps=-AVX2' wc -l < lines) || fail=1
+
+test "$wc_accelerated" = "$wc_base" || fail=1
+
+Exit $fail
--
2.50.1