On 13/09/2025 05:42, Bruno Haible wrote:
Collin Funk wrote:
It would be nice to add a test module, but I'm conflicted on whether it
is too much of a chore. Since there isn't an easy way to check if a
hwcap is supported at runtime.
A unit test for the hwcap_allowed function would be useful. That's a
string parsing function, that apparently needs to deal with multiple
cases:
- GLIBC_TUNABLES not set,
- GLIBC_TUNABLES set to empty,
- other tunables than glibc.cpu.hwcaps,
- glibc.cpu.hwcaps being the first one,
- glibc.cpu.hwcaps being the last one,
- glibc.cpu.hwcaps present but preceded and followed by other tunables,
- glibc.cpu.hwcaps specifying multiple features.
It's complicated enough to confuse me during code review...
Thanks for the reviews all.
I'll push the attached later (with a ChangeLog entry).
cheers,
Padraig
From 1cc2ed0bb3dc6dff52222bae4bfb45b48408d907 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Fri, 12 Sep 2025 16:55:45 +0100
Subject: [PATCH] cpu-supports: a module to honor
GLIBC_TUNABLES=glibc.cpu.hwcaps
This functionality is useful to allow better test coverage at least,
and may be useful for users to tune their environment,
avoiding CPU throttling for example.
* lib/cpu-supports.h (cpu_supports): A new wrapper that
checks that the GLIBC_TUNABLES environment variable allows
the hardware feature, before checking with __builtin_cpu_supports().
(cpu_may_support): Only perform the GLIBC_TUNABLES check,
which is useful if using other interfaces like getauxval().
(gcc_feature_to_glibc_hwcap): An internal helper that will resolve
at compile time with standard optimizations enabled.
* lib/cpu-supports.c (hwcap_allowed): Query the GLIBC_TUNABLES
environment variable (read once per process), to see if the
passed GLIBC_HWCAP is allowed.
* modules/cpu-supports: New module definition.
* modules/cpu-supports-tests: New test module definition.
* tests/test-cpu-supports.c: New tests.
---
lib/cpu-supports.c | 79 ++++++++++++++++++++++++++++++++
lib/cpu-supports.h | 92 +++++++++++++++++++++++++++++++++++++
modules/cpu-supports | 27 +++++++++++
modules/cpu-supports-tests | 12 +++++
tests/test-cpu-supports.c | 93 ++++++++++++++++++++++++++++++++++++++
5 files changed, 303 insertions(+)
create mode 100644 lib/cpu-supports.c
create mode 100644 lib/cpu-supports.h
create mode 100644 modules/cpu-supports
create mode 100644 modules/cpu-supports-tests
create mode 100644 tests/test-cpu-supports.c
diff --git a/lib/cpu-supports.c b/lib/cpu-supports.c
new file mode 100644
index 0000000000..94b7c4d7bf
--- /dev/null
+++ b/lib/cpu-supports.c
@@ -0,0 +1,79 @@
+/* Support routines to query GLIBC_TUNABLES=glibc.cpu.hwcaps
+
+ Copyright 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/>. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cpu-supports.h"
+
+/* Allow reparsing of env var for testing. */
+bool hwcap_allowed_nocache = false;
+
+extern bool
+hwcap_allowed (char const *glibc_hwcap)
+{
+ if (! glibc_hwcap)
+ return true;
+
+ assert (*glibc_hwcap == '-'); /* Pass HWCAP with a leading - */
+
+ /* Match how GLIBC parses tunables as indicated with:
+ GLIBC_TUNABLES=glibc.cpu.hwcaps=... ld.so --list-tunables | grep hwcaps
+
+ GLIBC_TUNABLES items are delimited with ':',
+ and like glibc we take the last instance of glibc.cpu.hwcaps.
+
+ An example of the usual format is:
+ GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX,-AVX2 */
+
+ static char const *hwcaps;
+ if (hwcap_allowed_nocache || ! hwcaps)
+ { /* Cache glibc.cpu.hwcaps once per process. */
+ if ((hwcaps = getenv ("GLIBC_TUNABLES")))
+ {
+ char const *tunables_start = hwcaps;
+ char const *last_hwcaps;
+ while ((last_hwcaps = strstr (hwcaps, "glibc.cpu.hwcaps=")))
+ hwcaps = last_hwcaps + sizeof "glibc.cpu.hwcaps=" - 1;
+ if (hwcaps == tunables_start) /* No match. */
+ hwcaps = "";
+ }
+ else
+ hwcaps = "";
+ }
+
+ assert (hwcaps);
+
+ if (! *hwcaps)
+ return true;
+
+ char const *sentinel = strchr (hwcaps, ':');
+ if (! sentinel)
+ sentinel = hwcaps + strlen (hwcaps);
+ char const *cap = hwcaps;
+ while ((cap = strstr (cap, glibc_hwcap)) && cap < sentinel)
+ { /* Check it's not a partial match. */
+ cap += strlen (glibc_hwcap);
+ 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/lib/cpu-supports.h b/lib/cpu-supports.h
new file mode 100644
index 0000000000..4f86b8683a
--- /dev/null
+++ b/lib/cpu-supports.h
@@ -0,0 +1,92 @@
+/* Support routines to query GLIBC_TUNABLES=glibc.cpu.hwcaps
+
+ Copyright 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/>. */
+
+#ifndef _CPU_SUPPORTS_H
+# define _CPU_SUPPORTS_H
+
+# include <string.h>
+# include "attribute.h"
+
+/* The main interface to this module is cpu_supports("feature"),
+ which is like __builtin_cpu_supports("feature"), but we also check if
+ the feature has been disabled by the GLIBC_TUNABLES env variable.
+
+ Notes:
+
+ You can call this with any "feature" accepted by __builtin_cpu_supports(),
+ but we only check that the feature is disabled against the set listed
+ in gcc_feature_to_glibc_hwcap() below.
+
+ We don't currently support amalgamated feature checks like
+ cpu_supports("feature1+feature2").
+
+ We do not need glibc support for GLIBC_TUNABLES,
+ rather mimic that functionality on all systems. */
+
+# define cpu_supports(feature) \
+ (cpu_may_support (feature) && 0 < __builtin_cpu_supports (feature))
+
+
+/* Check if the feature has been disabled by GLIBC_TUNABLES,
+ but do NOT call __builtin_cpu_supports(), as some platforms
+ use other interfaces like getaxuval() instead. */
+
+# define cpu_may_support(feature) \
+ (hwcap_allowed (gcc_feature_to_glibc_hwcap (feature)))
+
+
+
+# ifndef STREQ
+# define STREQ(a, b) (strcmp (a, b) == 0)
+# endif
+
+/* Return the glibc.cpu.hwcaps setting (prepended with "-"),
+ corresponding to the passed gcc _builtin_cpu_supports(FEATURE).
+ Supported hwcaps can be identified from the bit_cpu_* defines
+ in GLIBC's sysdeps/x86/include/cpu-features.h
+ Note this mapping should resolve at compile time. */
+ATTRIBUTE_PURE
+static inline char const *
+gcc_feature_to_glibc_hwcap (char const *feature)
+{
+ char const *hwcap = NULL;
+
+ if (0)
+ ;
+# if defined __x86_64__
+ else if (STREQ (feature, "avx")) hwcap = "-AVX";
+ else if (STREQ (feature, "avx2")) hwcap = "-AVX2";
+ else if (STREQ (feature, "avx512bw")) hwcap = "-AVX512BW";
+ else if (STREQ (feature, "avx512f")) hwcap = "-AVX512F";
+ else if (STREQ (feature, "pclmul")) hwcap = "-PCLMULQDQ";
+ else if (STREQ (feature, "vpclmulqdq")) hwcap = "-VPCLMULQDQ";
+# elif defined __aarch64__
+ else if (STREQ (feature, "pmull")) hwcap = "-PMULL";
+# endif
+
+ return hwcap;
+}
+
+/* Support GLIBC's interface to disable features using:
+ export GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX512F,-AVX2,-AVX,-PMULL
+ Return true if the HWCAP is allowed. */
+extern bool hwcap_allowed (char const *glibc_hwcap);
+
+/* Enable reparsing of GLIBC_TUNABLES on each call (useful for testing). */
+extern bool hwcap_allowed_nocache;
+
+#endif /* _CPU_SUPPORTS_H */
diff --git a/modules/cpu-supports b/modules/cpu-supports
new file mode 100644
index 0000000000..a66453a4d4
--- /dev/null
+++ b/modules/cpu-supports
@@ -0,0 +1,27 @@
+Description:
+A wrapper for __builtin_cpu_supports to also check GLIBC_TUNABLES
+
+Files:
+lib/cpu-supports.h
+lib/cpu-supports.c
+
+Depends-on:
+assert
+attribute
+bool
+c99
+
+configure.ac:
+AC_REQUIRE([AC_C_INLINE])
+
+Makefile.am:
+lib_SOURCES += cpu-supports.c
+
+Include:
+"cpu-supports.h"
+
+License:
+GPL
+
+Maintainer:
+all
diff --git a/modules/cpu-supports-tests b/modules/cpu-supports-tests
new file mode 100644
index 0000000000..f9b14efb6b
--- /dev/null
+++ b/modules/cpu-supports-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-cpu-supports.c
+tests/macros.h
+
+Depends-on:
+bool
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-cpu-supports
+check_PROGRAMS += test-cpu-supports
diff --git a/tests/test-cpu-supports.c b/tests/test-cpu-supports.c
new file mode 100644
index 0000000000..58ccd3cbaf
--- /dev/null
+++ b/tests/test-cpu-supports.c
@@ -0,0 +1,93 @@
+/* Test parsing of GLIBC_TUNABLES enviroment variable.
+ 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/>. */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+/* Specification. */
+#include "cpu-supports.h"
+
+#include "macros.h"
+
+int
+main ()
+{
+ /* Enable reparsing the GLIBC_TUNABLES env var. */
+ hwcap_allowed_nocache = true;
+
+ { /* unset GLIBC_TUNABLES. */
+ unsetenv ("GLIBC_TUNABLES");
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX"));
+ }
+
+ { /* empty GLIBC_TUNABLES. */
+ setenv ("GLIBC_TUNABLES", "", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX"));
+ }
+
+ { /* empty hwcaps. */
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX"));
+ }
+
+ { /* Single hwcaps with partial substring match. */
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX2", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX"));
+ ASSERT (hwcap_allowed ("-AVX2") == 0);
+ }
+
+ { /* Multiple hwcaps. */
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX,-AVX2", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX") == 0);
+ ASSERT (hwcap_allowed ("-AVX2") == 0);
+ }
+
+ { /* last hwcaps takes precedence. */
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX:"
+ "glibc.cpu.hwcaps=-AVX2", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX"));
+ ASSERT (hwcap_allowed ("-AVX2") == 0);
+ }
+
+ { /* another tunable before hwcaps. */
+ setenv ("GLIBC_TUNABLES", ":foo=bar:glibc.cpu.hwcaps=-AVX,-AVX2", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX") == 0);
+ ASSERT (hwcap_allowed ("-AVX2") == 0);
+ }
+
+ { /* another tunable after hwcaps. */
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-AVX,-AVX2:foo=bar", 1);
+ ASSERT (hwcap_allowed (NULL));
+ ASSERT (hwcap_allowed ("-AVX") == 0);
+ ASSERT (hwcap_allowed ("-AVX2") == 0);
+ }
+
+ { /* Unsupported features not matched by cpu_*(). */
+ setenv ("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-FOO", 1);
+ ASSERT (cpu_may_support ("foo"));
+ ASSERT (hwcap_allowed ("-FOO") == 0);
+ }
+
+ return test_exit_status;
+}
--
2.50.1