From: Pádraig Brady Date: Fri, 12 Sep 2025 15:55:45 +0000 (+0100) Subject: cpu-supports: a module to honor GLIBC_TUNABLES=glibc.cpu.hwcaps X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=16deb62f09149e8fc2a4be55fcb3bfe6f9f7310c;p=thirdparty%2Fgnulib.git 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. --- diff --git a/ChangeLog b/ChangeLog index b06463402c..0b0400ca62 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2025-09-14 Pádraig Brady + + cpu-supports: a module to honor GLIBC_TUNABLES=glibc.cpu.hwcaps + * 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. + 2025-09-13 Bruno Haible gettext-h: Avoid -Wtrailing-whitespace in a better way. 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 . */ + +#include +#include +#include +#include + +#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..88fd4deaf0 --- /dev/null +++ b/lib/cpu-supports.h @@ -0,0 +1,93 @@ +/* __builtin_cpu_supports() wrapper that honors 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 . */ + +#ifndef _CPU_SUPPORTS_H +# define _CPU_SUPPORTS_H + +# include +# 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 getauxval() 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); + +/* Set to true to enable reparsing of GLIBC_TUNABLES on each call. + This can be useful for testing. Off by default. */ +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..799d87fd88 --- /dev/null +++ b/modules/cpu-supports-tests @@ -0,0 +1,14 @@ +Files: +tests/test-cpu-supports.c +tests/macros.h + +Depends-on: +bool +setenv +unsetenv + +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..8c8e563a85 --- /dev/null +++ b/tests/test-cpu-supports.c @@ -0,0 +1,93 @@ +/* Test parsing of GLIBC_TUNABLES environment 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 . */ + +#include + +/* Specification. */ +#include "cpu-supports.h" + +#include + +#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; +}