From: Adhemerval Zanella Date: Fri, 19 Dec 2025 18:52:20 +0000 (-0300) Subject: x86: Do not use __builtin_fpclassify for _Float64x/long double X-Git-Tag: glibc-2.43~64 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=50112103993b042f52fb6afb0e4eee24fe4cb9af;p=thirdparty%2Fglibc.git x86: Do not use __builtin_fpclassify for _Float64x/long double Neither gcc [1] nor clang [2] handles pseudo-normal numbers correctly with the __builtin_fpclassify, so disable its usage for _Float64x and long double types. This only affects x86, so add a new header, fp-builtin-denormal.h, that defines whether the architecture requires disabling the optimization through a new glibc define (__FP_BUILTIN_FPCLASSIFY_DENORMAL). It fixes the regression on test-ldouble-fpclassify and test-float64x-fpclassify when built with clang: Failure: fpclassify (pseudo_zero): Exception "Invalid operation" set Failure: fpclassify (pseudo_inf): Exception "Invalid operation" set Failure: fpclassify (pseudo_qnan): Exception "Invalid operation" set Failure: fpclassify (pseudo_snan): Exception "Invalid operation" set Failure: fpclassify (pseudo_unnormal): Exception "Invalid operation" set Failure: fpclassify_downward (pseudo_zero): Exception "Invalid operation" set Failure: fpclassify_downward (pseudo_inf): Exception "Invalid operation" set Failure: fpclassify_downward (pseudo_qnan): Exception "Invalid operation" set Failure: fpclassify_downward (pseudo_snan): Exception "Invalid operation" set Failure: fpclassify_downward (pseudo_unnormal): Exception "Invalid operation" set Failure: fpclassify_towardzero (pseudo_zero): Exception "Invalid operation" set Failure: fpclassify_towardzero (pseudo_inf): Exception "Invalid operation" set Failure: fpclassify_towardzero (pseudo_qnan): Exception "Invalid operation" set Failure: fpclassify_towardzero (pseudo_snan): Exception "Invalid operation" set Failure: fpclassify_towardzero (pseudo_unnormal): Exception "Invalid operation" set Failure: fpclassify_upward (pseudo_zero): Exception "Invalid operation" set Failure: fpclassify_upward (pseudo_inf): Exception "Invalid operation" set Failure: fpclassify_upward (pseudo_qnan): Exception "Invalid operation" set Failure: fpclassify_upward (pseudo_snan): Exception "Invalid operation" set Failure: fpclassify_upward (pseudo_unnormal): Exception "Invalid operation" set Checked on x86_64-linux-gnu with gcc-15 and clang-18. [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123161 [2] https://github.com/llvm/llvm-project/issues/172533 Reviewed-by: H.J. Lu --- diff --git a/bits/fp-builtin-denormal.h b/bits/fp-builtin-denormal.h new file mode 100644 index 0000000000..77b7184975 --- /dev/null +++ b/bits/fp-builtin-denormal.h @@ -0,0 +1,28 @@ +/* Denormal number definitions. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _MATH_H +# error "Never use directly; include instead." +#endif + +/* __FP_BUILTIN_FPCLASSIFY_DENORMAL is defined to 1 if compiler supports + handling pseudo-denormal numbers with fpclassify builtin. Pseudo-denormal + is a non-standard denormalized floating-point number only supported by + Intel double extended-precision (long double). By default assume 1 to + enable the usage of compiler builtin on math.h. */ +#define __FP_BUILTIN_FPCLASSIFY_DENORMAL 1 diff --git a/math/Makefile b/math/Makefile index f7a1d9016c..8fe2540ce0 100644 --- a/math/Makefile +++ b/math/Makefile @@ -28,6 +28,7 @@ headers := \ bits/floatn-common.h \ bits/floatn.h \ bits/flt-eval-method.h \ + bits/fp-builtin-denormal.h \ bits/fp-fast.h \ bits/fp-logb.h \ bits/iscanonical.h \ diff --git a/math/math.h b/math/math.h index 74b064d96e..cbec2f406a 100644 --- a/math/math.h +++ b/math/math.h @@ -1064,6 +1064,86 @@ extern int signgam; : FUNC ## l ARGS) #endif + +/* Depending on the type of TG_ARG and extra DEFINE to check, either call the + BUILTIN with ARGS_B or an appropriately suffixed version of FUNC with + arguments (including parentheses) ARGS_B. The function call is used for + long double and/or _Float64x is the builtin can not be safely used on all + arguments (defined by DEFINE). */ + +#include + +#ifdef __NO_LONG_DOUBLE_MATH +# define __MATH_TG_BUILTIN_CLASSIFY(TG_ARG, BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + BUILTIN ARGS_B +#elif __HAVE_DISTINCT_FLOAT128 +# if __HAVE_GENERIC_SELECTION +# if __HAVE_FLOATN_NOT_TYPEDEF && __HAVE_FLOAT32 +# define __MATH_TG_BUILTIN_CLASSIFY_F32(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + _Float32: BUILTIN ARGS_B, +# else +# define __MATH_TG_BUILTIN_CLASSIFY_F32(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) +# endif +# define __MATH_TG_BUILTIN_CLASSIFY_LDOUBLE(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + long double: DEFINE ? BUILTIN ARGS_B : __ ## FUNC ## l ARGS_F, +# if __HAVE_FLOATN_NOT_TYPEDEF && __HAVE_FLOAT64X +# if __HAVE_FLOAT64X_LONG_DOUBLE +# define __MATH_TG_BUILTIN_CLASSIFY_F64X(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + _Float64x: DEFINE ? BUILTIN ARGS_B : __ ## FUNC ## l ARGS_F, +# else +# define __MATH_TG_BUILTIN_CLASSIFY_F64X(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + _Float64x: DEFINE ? BUILTIN ARGS_B : __ ## FUNC ## f128 ARGS_F, +# endif +# else +# define __MATH_TG_BUILTIN_CLASSIFY_F64X(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) +# endif +# define __MATH_TG_BUILTIN_CLASSIFY_F128(BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + _Float128: BUILTIN ARGS_B +# define __MATH_TG_BUILTIN_CLASSIFY(TG_ARG, BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + _Generic ((TG_ARG), \ + float: BUILTIN ARGS_B, \ + __MATH_TG_BUILTIN_CLASSIFY_F32 (BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + default: BUILTIN ARGS_B, \ + __MATH_TG_BUILTIN_CLASSIFY_LDOUBLE (BUILTIN, ARGS_B, FUNC, \ + ARGS_F, DEFINE) \ + __MATH_TG_BUILTIN_CLASSIFY_F64X (BUILTIN, ARGS_B, FUNC, ARGS_F,\ + DEFINE) \ + __MATH_TG_BUILTIN_CLASSIFY_F128 (BUILTIN, ARGS_B, FUNC, ARGS_F,\ + DEFINE)) +# else +# define __MATH_TG_BUILTIN_CLASSIFY(TG_ARG, BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + __builtin_choose_expr \ + (__builtin_types_compatible_p (__typeof (TG_ARG), float), \ + __builtin ## BUILTIN ARGS_B, \ + __builtin_choose_expr \ + (__builtin_types_compatible_p (__typeof (TG_ARG), double), \ + __builtin ## BUILTIN ARGS_B, \ + __builtin_choose_expr \ + (__builtin_types_compatible_p (__typeof (TG_ARG), long double), \ + DEFINE ? BUILTIN ARGS_B : __ ## FUNC ## l ARGS_F, \ + BUILTIN ARGS_B))) +# endif +#else +# define __MATH_TG_BUILTIN_CLASSIFY(TG_ARG, BUILTIN, ARGS_B, FUNC, ARGS_F, \ + DEFINE) \ + (sizeof (TG_ARG) == sizeof (float) \ + ? BUILTIN ARGS_B \ + : sizeof (TG_ARG) == sizeof (double) \ + ? BUILTIN ARGS_B \ + : DEFINE ? BUILTIN ARGS_B : __ ## FUNC ## l ARGS_F) +#endif + /* ISO C99 defines some generic macros which work on any data type. */ #ifdef __USE_ISOC99 @@ -1101,8 +1181,13 @@ enum with -Os. No further use of this definition of fpclassify is expected in C++ mode, since libstdc++ provides its own version of fpclassify in cmath (which undefines fpclassify). */ -# define fpclassify(x) __builtin_fpclassify (FP_NAN, FP_INFINITE, \ - FP_NORMAL, FP_SUBNORMAL, FP_ZERO, x) +# define fpclassify(x) \ + __MATH_TG_BUILTIN_CLASSIFY ((x), \ + __builtin_fpclassify, (FP_NAN, FP_INFINITE, \ + FP_NORMAL, FP_SUBNORMAL, \ + FP_ZERO, x), \ + fpclassify, (x), \ + __FP_BUILTIN_FPCLASSIFY_DENORMAL) # else # define fpclassify(x) __MATH_TG ((x), __fpclassify, (x)) # endif diff --git a/sysdeps/x86/bits/fp-builtin-denormal.h b/sysdeps/x86/bits/fp-builtin-denormal.h new file mode 100644 index 0000000000..bfb8f54237 --- /dev/null +++ b/sysdeps/x86/bits/fp-builtin-denormal.h @@ -0,0 +1,25 @@ +/* Define __FP_BUILTIN_DENORMAL. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _MATH_H +# error "Never use directly; include instead." +#endif + +/* Neither GCC (bug 123161) nor clang (issue 172533) handles pseudo-normal + numbers correctly with fpclassify builtin. */ +#define __FP_BUILTIN_FPCLASSIFY_DENORMAL 0 diff --git a/sysdeps/x86/fpu/Makefile b/sysdeps/x86/fpu/Makefile index 91b2444f3a..8566d3c80f 100644 --- a/sysdeps/x86/fpu/Makefile +++ b/sysdeps/x86/fpu/Makefile @@ -4,6 +4,7 @@ CPPFLAGS += -I../soft-fp libm-support += powl_helper tests += \ + test-builtin-denormal \ test-fenv-clear-sse \ test-fenv-sse \ test-fenv-sse-2 \ diff --git a/sysdeps/x86/fpu/test-builtin-denormal.c b/sysdeps/x86/fpu/test-builtin-denormal.c new file mode 100644 index 0000000000..1fb8aeedf6 --- /dev/null +++ b/sysdeps/x86/fpu/test-builtin-denormal.c @@ -0,0 +1,53 @@ +/* Ccheck if math.h optimizations to call compiler builtin + does not trigger FE_INVALID on x86 denormal numbers. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include + +#define pseudo_inf { .parts = { 0x00000000, 0x00000000, 0x7fff }} +#define pseudo_zero { .parts = { 0x00000000, 0x00000000, 0x0100 }} +#define pseudo_qnan { .parts = { 0x00000001, 0x00000000, 0x7fff }} +#define pseudo_snan { .parts = { 0x00000001, 0x40000000, 0x7fff }} +#define pseudo_unnormal { .parts = { 0x00000001, 0x40000000, 0x0100 }} + +static const ieee_long_double_shape_type inputs[] = { + pseudo_inf, + pseudo_zero, + pseudo_qnan, + pseudo_snan, + pseudo_unnormal +}; + +static int +do_test (void) +{ + for (int i = 0; i < array_length (inputs); i++) + { + TEST_COMPARE (feclearexcept (FE_INVALID), 0); + TEST_COMPARE (fpclassify (inputs[i].value), FP_NAN); + TEST_COMPARE (fetestexcept (FE_INVALID), 0); + } + + return 0; +} + +#include