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

Reply via email to