This module adds the <fenv.h> functions that deal with tracking which floating-point exceptions have occurred: feclearexcept feraiseexcept fetestexcept
It not only adds the functions for old platforms, but also provides workarounds for glibc and Cygwin bugs. With this done, it is possible to fix a deficiency in the 'fpe-trapping' module: Although sigfpe_on_invalid is documented to first clear the FE_INVALID exception flag — which is needed in order to avoid an undesired trap —, for some platforms I had omitted that code, because I had not considered feclearexcept to be portable. Now that we have feclearexcept on all platform, we can fix this. 2023-10-28 Bruno Haible <br...@clisp.org> fpe-trapping: Always clear the FE_INVALID exception flag first. * lib/fpe-trapping.h: Include <fenv.h> on all platforms. (sigfpe_on_invalid) [AIX, HP-UX, IRIX, Solaris] : Clear the FE_INVALID exception flag first. * modules/fpe-trapping (Depends-on): Add fenv-exceptions-tracking-c99. 2023-10-28 Bruno Haible <br...@clisp.org> fenv-exceptions-tracking-c99: Add tests. * tests/test-fenv-except-tracking-1.c: New file. * tests/test-fenv-except-tracking-2.sh: New file. * tests/test-fenv-except-tracking-2.c: New file. * tests/test-fenv-except-tracking-3.sh: New file. * tests/test-fenv-except-tracking-3.c: New file. * modules/fenv-exceptions-tracking-c99-tests: New file. fenv-exceptions-tracking-c99: New module. * lib/fenv.in.h (feclearexcept, feraiseexcept, fetestexcept): New declarations. * lib/fenv-except-tracking-clear.c: New file, based on glibc. * lib/fenv-except-tracking-raise.c: New file, based on glibc. * lib/fenv-except-tracking-test.c: New file, based on glibc. * m4/fenv-exceptions-tracking.m4: New file. * m4/fenv-exceptions.m4: New file. * modules/fenv-exceptions-tracking-c99: New file. * doc/posix-functions/feclearexcept.texi: Mention the new module. * doc/posix-functions/fetestexcept.texi: Likewise. * doc/posix-functions/feraiseexcept.texi: Likewise. Mention the glibc and Cygwin bugs.
From 2ce970a2718bdf9685517fba9efa5ec60d773953 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sat, 28 Oct 2023 18:31:22 +0200 Subject: [PATCH 1/3] fenv-exceptions-tracking-c99: New module. * lib/fenv.in.h (feclearexcept, feraiseexcept, fetestexcept): New declarations. * lib/fenv-except-tracking-clear.c: New file, based on glibc. * lib/fenv-except-tracking-raise.c: New file, based on glibc. * lib/fenv-except-tracking-test.c: New file, based on glibc. * m4/fenv-exceptions-tracking.m4: New file. * m4/fenv-exceptions.m4: New file. * modules/fenv-exceptions-tracking-c99: New file. * doc/posix-functions/feclearexcept.texi: Mention the new module. * doc/posix-functions/fetestexcept.texi: Likewise. * doc/posix-functions/feraiseexcept.texi: Likewise. Mention the glibc and Cygwin bugs. --- ChangeLog | 16 + doc/posix-functions/feclearexcept.texi | 8 +- doc/posix-functions/feraiseexcept.texi | 15 +- doc/posix-functions/fetestexcept.texi | 8 +- lib/fenv-except-tracking-clear.c | 392 +++++++++++++++++++++ lib/fenv-except-tracking-raise.c | 457 +++++++++++++++++++++++++ lib/fenv-except-tracking-test.c | 266 ++++++++++++++ lib/fenv.in.h | 57 +++ m4/fenv-exceptions-tracking.m4 | 152 ++++++++ m4/fenv-exceptions.m4 | 23 ++ modules/fenv-exceptions-tracking-c99 | 50 +++ 11 files changed, 1432 insertions(+), 12 deletions(-) create mode 100644 lib/fenv-except-tracking-clear.c create mode 100644 lib/fenv-except-tracking-raise.c create mode 100644 lib/fenv-except-tracking-test.c create mode 100644 m4/fenv-exceptions-tracking.m4 create mode 100644 m4/fenv-exceptions.m4 create mode 100644 modules/fenv-exceptions-tracking-c99 diff --git a/ChangeLog b/ChangeLog index 2eb57b8d9d..b1c73c0f98 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2023-10-28 Bruno Haible <br...@clisp.org> + + fenv-exceptions-tracking-c99: New module. + * lib/fenv.in.h (feclearexcept, feraiseexcept, fetestexcept): New + declarations. + * lib/fenv-except-tracking-clear.c: New file, based on glibc. + * lib/fenv-except-tracking-raise.c: New file, based on glibc. + * lib/fenv-except-tracking-test.c: New file, based on glibc. + * m4/fenv-exceptions-tracking.m4: New file. + * m4/fenv-exceptions.m4: New file. + * modules/fenv-exceptions-tracking-c99: New file. + * doc/posix-functions/feclearexcept.texi: Mention the new module. + * doc/posix-functions/fetestexcept.texi: Likewise. + * doc/posix-functions/feraiseexcept.texi: Likewise. Mention the glibc + and Cygwin bugs. + 2023-10-27 Bruno Haible <br...@clisp.org> fenv-rounding: Add tests. diff --git a/doc/posix-functions/feclearexcept.texi b/doc/posix-functions/feclearexcept.texi index dc56c3fd45..8b218804bd 100644 --- a/doc/posix-functions/feclearexcept.texi +++ b/doc/posix-functions/feclearexcept.texi @@ -4,15 +4,15 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/feclearexcept.html} -Gnulib module: --- +Gnulib module: fenv-exceptions-tracking-c99 Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 9, Android 4.4. @end itemize Portability problems not fixed by Gnulib: @itemize -@item -This function is missing on some platforms: -FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 9, Android 4.4. @end itemize diff --git a/doc/posix-functions/feraiseexcept.texi b/doc/posix-functions/feraiseexcept.texi index 07befbf263..a4f2ebab01 100644 --- a/doc/posix-functions/feraiseexcept.texi +++ b/doc/posix-functions/feraiseexcept.texi @@ -4,15 +4,22 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/feraiseexcept.html} -Gnulib module: --- +Gnulib module: fenv-exceptions-tracking-c99 Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 9, Android 4.4. +@item +This function does not detect failures on +glibc 2.19/arm. +@item +This function does not trigger traps on +@c https://sourceware.org/pipermail/cygwin/2023-October/254667.html +Cygwin 3.4.6/x86_64. @end itemize Portability problems not fixed by Gnulib: @itemize -@item -This function is missing on some platforms: -FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 14, Android 4.4. @end itemize diff --git a/doc/posix-functions/fetestexcept.texi b/doc/posix-functions/fetestexcept.texi index 182e23ff35..b8eba903be 100644 --- a/doc/posix-functions/fetestexcept.texi +++ b/doc/posix-functions/fetestexcept.texi @@ -4,15 +4,15 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/fetestexcept.html} -Gnulib module: --- +Gnulib module: fenv-exceptions-tracking-c99 Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 9, Android 4.4. @end itemize Portability problems not fixed by Gnulib: @itemize -@item -This function is missing on some platforms: -FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 9, Android 4.4. @end itemize diff --git a/lib/fenv-except-tracking-clear.c b/lib/fenv-except-tracking-clear.c new file mode 100644 index 0000000000..22c85c98e0 --- /dev/null +++ b/lib/fenv-except-tracking-clear.c @@ -0,0 +1,392 @@ +/* Functions for tracking which floating-point exceptions have occurred. + Copyright (C) 1997-2023 Free Software Foundation, Inc. + + This file 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. + + This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Based on glibc/sysdeps/<cpu>/{fclrexcpt.c,fraiseexcpt.c,ftestexcept.c} + together with glibc/sysdeps/<cpu>/{fpu_control.h,fenv_private.h,fenv_libc.h}. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +#include "fenv-private.h" + +#if defined __GNUC__ || defined __clang__ || defined _MSC_VER + +# if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86) + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + +# if defined _MSC_VER + + exceptions = exceptions_to_x86hardware (exceptions); + + /* Clear the bits only in the SSE unit. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr & ~exceptions; + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + +# else + + /* Clear the bits in the 387 unit. */ + x86_387_fenv_t env; + unsigned short orig_status_word; + __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env)); + orig_status_word = env.__status_word; + env.__status_word &= ~exceptions; + if (env.__status_word != orig_status_word) + __asm__ __volatile__ ("fldenv %0" : : "m" (*&env)); + + if (CPU_HAS_SSE ()) + { + /* Clear the bits in the SSE unit as well. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr & ~exceptions; + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + } + +# endif + + return 0; +} + +# elif defined __aarch64__ /* arm64 */ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpsr, orig_fpsr; + _FPU_GETFPSR (orig_fpsr); + fpsr = orig_fpsr & ~exceptions; + if (fpsr != orig_fpsr) + _FPU_SETFPSR (fpsr); + + return 0; +} + +# elif defined __arm__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + +# ifdef __SOFTFP__ + if (exceptions != 0) + return -1; +# else + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr & ~exceptions; + if (fpscr != orig_fpscr) + _FPU_SETCW (fpscr); +# endif + return 0; +} + +# elif defined __alpha + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long swcr, orig_swcr; + orig_swcr = __ieee_get_fp_control (); + swcr = orig_swcr & ~exceptions; + if (swcr != orig_swcr) + __ieee_set_fp_control (swcr); + + return 0; +} + +# elif defined __hppa + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + union { unsigned long long fpreg; unsigned int halfreg[2]; } s; + /* Get the current status word. */ + __asm__ __volatile__ ("fstd %%fr0,0(%1)" : "=m" (s.fpreg) : "r" (&s.fpreg) : "%r0"); + unsigned int old_halfreg0 = s.halfreg[0]; + /* Clear all the relevant bits. */ + s.halfreg[0] &= ~ ((unsigned int) exceptions << 27); + if (s.halfreg[0] != old_halfreg0) + { + /* Store the new status word. */ + __asm__ __volatile__ ("fldd 0(%0),%%fr0" : : "r" (&s.fpreg), "m" (s.fpreg) : "%r0"); + } + + return 0; +} + +# elif defined __ia64__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpsr, orig_fpsr; + _FPU_GETCW (orig_fpsr); + fpsr = orig_fpsr & ~ (unsigned long) (exceptions << 13); + if (fpsr != orig_fpsr) + _FPU_SETCW (fpsr); + + return 0; +} + +# elif defined __m68k__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpsr, orig_fpsr; + _FPU_GETFPSR (orig_fpsr); + fpsr = orig_fpsr & ~ exceptions; + if (fpsr != orig_fpsr) + _FPU_SETFPSR (fpsr); + + return 0; +} + +# elif defined __mips__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + /* Clear also the cause bits. If the cause bit is not cleared, the next + CTC instruction (just below) will re-generate the exception. */ + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr & ~ ((exceptions << 10) | exceptions); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return 0; +} + +# elif defined __loongarch__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + /* Clear also the cause bits. If the cause bit is not cleared, the next + CTC instruction (just below) will re-generate the exception. */ + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr & ~ ((exceptions << 8) | exceptions); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return 0; +} + +# elif defined __powerpc__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + union { unsigned long long u; double f; } memenv, orig_memenv; + _FPU_GETCW_AS_DOUBLE (memenv.f); + orig_memenv = memenv; + + /* Instead of clearing FE_INVALID (= bit 29), we need to clear the + individual bits. */ + memenv.u &= ~ (exceptions & FE_INVALID + ? (exceptions & ~FE_INVALID) | 0x01F80700U + : exceptions); + + if (!(memenv.u == orig_memenv.u)) + _FPU_SETCW_AS_DOUBLE (memenv.f); + + return 0; +} + +# elif defined __riscv + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + __asm__ __volatile__ ("csrc fflags, %0" : : "r" (exceptions)); + return 0; +} + +# elif defined __s390__ || defined __s390x__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpc, orig_fpc; + _FPU_GETCW (orig_fpc); +# if FE_INEXACT == 8 /* glibc compatible FE_* values */ + fpc = orig_fpc & ~(exceptions << 16); + if ((fpc & 0x00000300) == 0) + fpc &= ~(exceptions << 8); +# else /* musl libc compatible FE_* values */ + fpc = orig_fpc & ~exceptions; + if ((fpc & 0x00000300) == 0) + fpc &= ~(exceptions >> 8); +# endif + if (fpc != orig_fpc) + _FPU_SETCW (fpc); + + return 0; +} + +# elif defined __sh__ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr & ~exceptions; + if (fpscr != orig_fpscr) + _FPU_SETCW (fpscr); + + return 0; +} + +# elif defined __sparc + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fsr, orig_fsr; + _FPU_GETCW (orig_fsr); +# if FE_INEXACT == 32 /* glibc compatible FE_* values */ + fsr = orig_fsr & ~exceptions; +# else /* Solaris compatible FE_* values */ + fsr = orig_fsr & ~(exceptions << 5); +# endif + if (fsr != orig_fsr) + _FPU_SETCW (fsr); + + return 0; +} + +# else + +# if defined __GNUC__ || defined __clang__ +# warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>." +# endif +# define NEED_FALLBACK 1 + +# endif + +#else + +/* The compiler does not support __asm__ statements or equivalent + intrinsics. */ + +# if HAVE_FPSETSTICKY +/* FreeBSD ≥ 3.1, NetBSD ≥ 1.1, OpenBSD, IRIX, Solaris, Minix ≥ 3.2. */ + +/* Get fpgetsticky, fpsetsticky. */ +# include <ieeefp.h> +/* The type is called 'fp_except_t' on FreeBSD, but 'fp_except' on + all other systems. */ +# if !defined __FreeBSD__ +# define fp_except_t fp_except +# endif + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + fp_except_t flags, orig_flags; + orig_flags = fpgetsticky (); + flags = orig_flags & ~exceptions; + if (flags != orig_flags) + fpsetsticky (flags); + + return 0; +} + +# elif defined _AIX && defined __powerpc__ /* AIX */ + +# include <float.h> +# include <fpxcp.h> + +/* Documentation: + <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-clr-flag-fp-set-flag-fp-read-flag-fp-swap-flag-subroutine> */ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + /* In addition to clearing FE_INVALID (= bit 29), we also need to clear the + individual bits. */ + fpflag_t f_to_clear = + exceptions_to_fpflag (exceptions) + | (exceptions & FE_INVALID ? 0x01F80700U : 0); + if (f_to_clear != 0) + fp_clr_flag (f_to_clear); + + return 0; +} + +# else + +# define NEED_FALLBACK 1 + +# endif + +#endif + +#if NEED_FALLBACK + +/* A dummy fallback. */ + +int +feclearexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + if (exceptions != 0) + return -1; + return 0; +} + +#endif diff --git a/lib/fenv-except-tracking-raise.c b/lib/fenv-except-tracking-raise.c new file mode 100644 index 0000000000..a9f8b04963 --- /dev/null +++ b/lib/fenv-except-tracking-raise.c @@ -0,0 +1,457 @@ +/* Functions for tracking which floating-point exceptions have occurred. + Copyright (C) 1997-2023 Free Software Foundation, Inc. + + This file 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. + + This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Based on glibc/sysdeps/<cpu>/{fclrexcpt.c,fraiseexcpt.c,ftestexcept.c} + together with glibc/sysdeps/<cpu>/{fpu_control.h,fenv_private.h,fenv_libc.h}. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +#include "fenv-private.h" + +_GL_UNUSED static void +generic_feraiseexcept (int exceptions) +{ + /* First: invalid exception. */ + if (exceptions & FE_INVALID) + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + /* Next: division by zero. */ + if (exceptions & FE_DIVBYZERO) + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 1; b = 0; c = a / b; + } + /* Next: overflow. */ + if (exceptions & FE_OVERFLOW) + { + double volatile a; + _GL_UNUSED double volatile b; + a = 1e200; b = a * a; + } + /* Next: underflow. */ + if (exceptions & FE_UNDERFLOW) + { + double volatile a; + _GL_UNUSED double volatile b; + a = 1e-200; b = a * a; + } + /* Last: inexact. */ + if (exceptions & FE_INEXACT) + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 1; b = 3; c = a / b; + } +} + +#if defined __GNUC__ || defined __clang__ || defined _MSC_VER + +# if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86) + +int +feraiseexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + if ((exceptions & ~(FE_INVALID | FE_DIVBYZERO)) == 0) + { + /* Like generic_feraiseexcept (exceptions). */ + /* This code is probably faster than the general code below. */ + /* First: invalid exception. */ + if (exceptions & FE_INVALID) + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + /* Next: division by zero. */ + if (exceptions & FE_DIVBYZERO) + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 1; b = 0; c = a / b; + } + } + else + { + /* The general case. */ +# if defined _MSC_VER + + exceptions = exceptions_to_x86hardware (exceptions); + + /* Set the bits only in the SSE unit. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr | exceptions; + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + +# else + + /* Set the bits in the 387 unit. */ + x86_387_fenv_t env; + unsigned short orig_status_word; + __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env)); + orig_status_word = env.__status_word; + env.__status_word |= exceptions; + if (env.__status_word != orig_status_word) + { + __asm__ __volatile__ ("fldenv %0" : : "m" (*&env)); + /* A trap (if enabled) is triggered only at the next floating-point + instruction. Force it to occur here. */ + __asm__ __volatile__ ("fwait"); + } + +# endif + } + return 0; +} + +# elif defined __aarch64__ /* arm64 */ + +int +feraiseexcept (int exceptions) +{ +# if 0 + /* This would just set the flag bits and make fetestexcept() work as expected. + But it would not cause the hardware to trap on the exception. */ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpsr, orig_fpsr; + _FPU_GETFPSR (orig_fpsr); + fpsr = orig_fpsr | exceptions; + if (fpsr != orig_fpsr) + _FPU_SETFPSR (fpsr); +# else + /* This is how glibc does it. + The drawback is that when FE_OVERFLOW is raised, FE_INEXACT is raised + with it. */ + generic_feraiseexcept (exceptions); +# endif + return 0; +} + +# elif defined __arm__ + +int +feraiseexcept (int exceptions) +{ +# ifdef __SOFTFP__ + exceptions &= FE_ALL_EXCEPT; + + if (exceptions != 0) + return -1; +# else + /* Raise exceptions represented by EXCEPTIONS. But we must raise only + one signal at a time. It is important that if the overflow/underflow + exception and the inexact exception are given at the same time, + the overflow/underflow exception follows the inexact exception. After + each exception we read from the fpscr, to force the exception to be + raised immediately. */ + unsigned int fpscr, orig_fpscr; + /* First: invalid exception. */ + if (exceptions & FE_INVALID) + { + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | FE_INVALID; + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + _FPU_GETCW (fpscr); + } + } + /* Next: division by zero. */ + if (exceptions & FE_DIVBYZERO) + { + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | FE_DIVBYZERO; + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + _FPU_GETCW (fpscr); + } + } + /* Next: overflow. */ + if (exceptions & FE_OVERFLOW) + { + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | FE_OVERFLOW; + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + _FPU_GETCW (fpscr); + } + } + /* Next: underflow. */ + if (exceptions & FE_UNDERFLOW) + { + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | FE_UNDERFLOW; + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + _FPU_GETCW (fpscr); + } + } + /* Last: inexact. */ + if (exceptions & FE_INEXACT) + { + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | FE_INEXACT; + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + _FPU_GETCW (fpscr); + } + } +# endif + return 0; +} + +# elif defined __alpha + +/* Prefer the Linux system call when available. + See glibc/sysdeps/unix/sysv/linux/alpha/fraiseexcpt.S */ +# if !defined __linux__ +int +feraiseexcept (int exceptions) +{ + /* This implementation cannot raise FE_INEXACT. */ + generic_feraiseexcept (exceptions); + return 0; +} +# endif + +# elif defined __hppa + +int +feraiseexcept (int exceptions) +{ + generic_feraiseexcept (exceptions); + return 0; +} + +# elif defined __ia64__ + +int +feraiseexcept (int exceptions) +{ + /* Raise exceptions represented by EXCEPTIONS. But we must raise only + one signal at a time. It is important that if the overflow/underflow + exception and the inexact exception are given at the same time, + the overflow/underflow exception precedes the inexact exception. */ + generic_feraiseexcept (exceptions); + return 0; +} + +# elif defined __m68k__ + +int +feraiseexcept (int exceptions) +{ + generic_feraiseexcept (exceptions); + return 0; +} + +# elif defined __mips__ + +int +feraiseexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + /* Set also the cause bits. The setting of the cause bits is what actually + causes the hardware to trap on the exception, if the corresponding enable + bit is set as well. */ + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr | ((exceptions << 10) | exceptions); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return 0; +} + +# elif defined __loongarch__ + +int +feraiseexcept (int exceptions) +{ +# if 0 + /* This would just set the flag bits and make fetestexcept() work as expected. + But it would not cause the hardware to trap on the exception. */ + exceptions &= FE_ALL_EXCEPT; + + /* Set also the cause bits. The setting of the cause bits is what actually + causes the hardware to trap on the exception, if the corresponding enable + bit is set as well. */ + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr | ((exceptions << 8) | exceptions); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); +# else + /* This is how glibc does it. + The drawback is that when FE_OVERFLOW is raised, FE_INEXACT is raised + with it. */ + generic_feraiseexcept (exceptions); +# endif + return 0; +} + +# elif defined __powerpc__ + +int +feraiseexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + union { unsigned long long u; double f; } memenv, orig_memenv; + _FPU_GETCW_AS_DOUBLE (memenv.f); + orig_memenv = memenv; + + /* Instead of setting FE_INVALID (= bit 29), we need to set one of the + individual bits: bit 10 or, if that does not work, bit 24. */ + memenv.u |= (exceptions & FE_INVALID + ? (exceptions & ~FE_INVALID) | (1U << 10) + : exceptions); + + if (!(memenv.u == orig_memenv.u)) + { + _FPU_SETCW_AS_DOUBLE (memenv.f); + if (exceptions & FE_INVALID) + { + /* Did it work? */ + _FPU_GETCW_AS_DOUBLE (memenv.f); + if ((memenv.u & FE_INVALID) == 0) + { + memenv.u |= (1U << 24); + _FPU_SETCW_AS_DOUBLE (memenv.f); + } + } + } + + return 0; +} + +# elif defined __riscv + +int +feraiseexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + __asm__ __volatile__ ("csrs fflags, %0" : : "r" (exceptions)); + return 0; +} + +# elif defined __s390__ || defined __s390x__ + +int +feraiseexcept (int exceptions) +{ + generic_feraiseexcept (exceptions); + return 0; +} + +# elif defined __sh__ + +int +feraiseexcept (int exceptions) +{ +# if 0 + /* This would just set the flag bits and make fetestexcept() work as expected. + But it would not cause the hardware to trap on the exception. */ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | exceptions; + if (fpscr != orig_fpscr) + _FPU_SETCW (fpscr); +# else + /* This is how glibc does it. + The drawback is that when FE_OVERFLOW is raised, FE_INEXACT is raised + with it. */ + generic_feraiseexcept (exceptions); +# endif + return 0; +} + +# elif defined __sparc + +int +feraiseexcept (int exceptions) +{ +# if 0 + /* This would just set the flag bits and make fetestexcept() work as expected. + But it would not cause the hardware to trap on the exception. */ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fsr, orig_fsr; + _FPU_GETCW (orig_fsr); +# if FE_INEXACT == 32 /* glibc compatible FE_* values */ + fsr = orig_fsr | exceptions; +# else /* Solaris compatible FE_* values */ + fsr = orig_fsr | (exceptions << 5); +# endif + if (fsr != orig_fsr) + _FPU_SETCW (fsr); +# else + /* This is how glibc does it. + The drawback is that when FE_OVERFLOW is raised, FE_INEXACT is raised + with it. */ + generic_feraiseexcept (exceptions); +# endif + return 0; +} + +# else + +# if defined __GNUC__ || defined __clang__ +# warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>." +# endif +# define NEED_FALLBACK 1 + +# endif + +#else + +/* The compiler does not support __asm__ statements or equivalent + intrinsics. */ + +# define NEED_FALLBACK 1 + +#endif + +#if NEED_FALLBACK + +/* A fallback that should work everywhere. */ + +int +feraiseexcept (int exceptions) +{ + generic_feraiseexcept (exceptions); + return 0; +} + +#endif diff --git a/lib/fenv-except-tracking-test.c b/lib/fenv-except-tracking-test.c new file mode 100644 index 0000000000..efb9baa9ae --- /dev/null +++ b/lib/fenv-except-tracking-test.c @@ -0,0 +1,266 @@ +/* Functions for tracking which floating-point exceptions have occurred. + Copyright (C) 1997-2023 Free Software Foundation, Inc. + + This file 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. + + This file 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 this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Based on glibc/sysdeps/<cpu>/{fclrexcpt.c,fraiseexcpt.c,ftestexcept.c} + together with glibc/sysdeps/<cpu>/{fpu_control.h,fenv_private.h,fenv_libc.h}. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +#include "fenv-private.h" + +#if defined __GNUC__ || defined __clang__ || defined _MSC_VER + +# if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86) + +int +fetestexcept (int exceptions) +{ +# if defined _MSC_VER + + /* Look at the flags in the SSE unit. */ + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + return x86hardware_to_exceptions (mxcsr) & FE_ALL_EXCEPT & exceptions; + +# else + + unsigned short fstat; + _FPU_GETSTAT (fstat); + + unsigned int mxcsr = 0; + if (CPU_HAS_SSE ()) + { + /* Look at the flags in the SSE unit as well. */ + _FPU_GETSSECW (mxcsr); + } + + return x86hardware_to_exceptions (fstat | mxcsr) & FE_ALL_EXCEPT & exceptions; +# endif +} + +# elif defined __aarch64__ /* arm64 */ + +int +fetestexcept (int exceptions) +{ + unsigned long fpsr; + _FPU_GETFPSR (fpsr); + return fpsr & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __arm__ + +int +fetestexcept (int exceptions) +{ +# ifdef __SOFTFP__ + return 0; +# else + unsigned int fpscr; + _FPU_GETCW (fpscr); + return fpscr & FE_ALL_EXCEPT & exceptions; +# endif +} + +# elif defined __alpha + +int +fetestexcept (int exceptions) +{ + unsigned long swcr = __ieee_get_fp_control (); + return swcr & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __hppa + +int +fetestexcept (int exceptions) +{ + union { unsigned long long fpreg; unsigned int halfreg[2]; } s; + /* Get the current status word. */ + __asm__ __volatile__ ("fstd %%fr0,0(%1)" : "=m" (s.fpreg) : "r" (&s.fpreg) : "%r0"); + return (s.halfreg[0] >> 27) & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __ia64__ + +int +fetestexcept (int exceptions) +{ + unsigned long fpsr; + _FPU_GETCW (fpsr); + return (fpsr >> 13) & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __m68k__ + +int +fetestexcept (int exceptions) +{ + unsigned int fpsr; + _FPU_GETFPSR (fpsr); + return fpsr & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __mips__ + +int +fetestexcept (int exceptions) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); + return fcsr & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __loongarch__ + +int +fetestexcept (int exceptions) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); + return fcsr & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __powerpc__ + +int +fetestexcept (int exceptions) +{ + union { unsigned long long u; double f; } memenv; + _FPU_GETCW_AS_DOUBLE (memenv.f); + return memenv.u & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __riscv + +int +fetestexcept (int exceptions) +{ + unsigned int flags; + __asm__ __volatile__ ("frflags %0" : "=r" (flags)); /* same as "csrr %0, fflags" */ + return flags & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __s390__ || defined __s390x__ + +int +fetestexcept (int exceptions) +{ + unsigned int fpc; + _FPU_GETCW (fpc); +# if FE_INEXACT == 8 /* glibc compatible FE_* values */ + return ((fpc >> 16) | ((fpc & 0x00000300) == 0 ? fpc >> 8 : 0)) + & FE_ALL_EXCEPT & exceptions; +# else /* musl libc compatible FE_* values */ + return (fpc | ((fpc & 0x00000300) == 0 ? fpc << 8 : 0)) + & FE_ALL_EXCEPT & exceptions; +# endif +} + +# elif defined __sh__ + +int +fetestexcept (int exceptions) +{ + unsigned int fpscr; + _FPU_GETCW (fpscr); + return fpscr & FE_ALL_EXCEPT & exceptions; +} + +# elif defined __sparc + +int +fetestexcept (int exceptions) +{ + unsigned long fsr; + _FPU_GETCW (fsr); +# if FE_INEXACT == 32 /* glibc compatible FE_* values */ + return fsr & FE_ALL_EXCEPT & exceptions; +# else /* Solaris compatible FE_* values */ + return (fsr >> 5) & FE_ALL_EXCEPT & exceptions; +# endif +} + +# else + +# if defined __GNUC__ || defined __clang__ +# warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>." +# endif +# define NEED_FALLBACK 1 + +# endif + +#else + +/* The compiler does not support __asm__ statements or equivalent + intrinsics. */ + +# if HAVE_FPSETSTICKY +/* FreeBSD ≥ 3.1, NetBSD ≥ 1.1, OpenBSD, IRIX, Solaris, Minix ≥ 3.2. */ + +/* Get fpgetsticky, fpsetsticky. */ +# include <ieeefp.h> +/* The type is called 'fp_except_t' on FreeBSD, but 'fp_except' on + all other systems. */ +# if !defined __FreeBSD__ +# define fp_except_t fp_except +# endif + +int +fetestexcept (int exceptions) +{ + fp_except_t flags = fpgetsticky (); + return flags & FE_ALL_EXCEPT & exceptions; +} + +# elif defined _AIX && defined __powerpc__ /* AIX */ + +# include <float.h> +# include <fpxcp.h> + +/* Documentation: + <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-clr-flag-fp-set-flag-fp-read-flag-fp-swap-flag-subroutine> */ + +int +fetestexcept (int exceptions) +{ + fpflag_t flags = fp_read_flag (); + return fpflag_to_exceptions (flags) & FE_ALL_EXCEPT & exceptions; +} + +# else + +# define NEED_FALLBACK 1 + +# endif + +#endif + +#if NEED_FALLBACK + +/* A dummy fallback. */ + +int +fetestexcept (int exceptions) +{ + return 0; +} + +#endif diff --git a/lib/fenv.in.h b/lib/fenv.in.h index 2c70e343eb..9662558805 100644 --- a/lib/fenv.in.h +++ b/lib/fenv.in.h @@ -457,6 +457,63 @@ _GL_CXXALIASWARN (fesetround); #endif +#if @GNULIB_FECLEAREXCEPT@ +/* Clears the specified exception flags, and returns 0. + Upon failure, it returns non-zero. */ +# if @REPLACE_FECLEAREXCEPT@ || (!@HAVE_FECLEAREXCEPT@ && (defined __GLIBC__ || defined __FreeBSD__)) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef feclearexcept +# define feclearexcept rpl_feclearexcept +# endif +_GL_FUNCDECL_RPL (feclearexcept, int, (int exceptions)); +_GL_CXXALIAS_RPL (feclearexcept, int, (int exceptions)); +# else +# if !@HAVE_FECLEAREXCEPT@ +_GL_FUNCDECL_SYS (feclearexcept, int, (int exceptions)); +# endif +_GL_CXXALIAS_SYS (feclearexcept, int, (int exceptions)); +# endif +_GL_CXXALIASWARN (feclearexcept); +#endif + +#if @GNULIB_FERAISEEXCEPT@ +/* Sets the specified exception flags, triggering handlers or traps if enabled, + and returns 0. Upon failure, it returns non-zero. */ +# if @REPLACE_FERAISEEXCEPT@ || (!@HAVE_FERAISEEXCEPT@ && (defined __GLIBC__ || defined __FreeBSD__ || defined _MSC_VER)) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef feraiseexcept +# define feraiseexcept rpl_feraiseexcept +# endif +_GL_FUNCDECL_RPL (feraiseexcept, int, (int exceptions)); +_GL_CXXALIAS_RPL (feraiseexcept, int, (int exceptions)); +# else +# if !@HAVE_FERAISEEXCEPT@ +_GL_FUNCDECL_SYS (feraiseexcept, int, (int exceptions)); +# endif +_GL_CXXALIAS_SYS (feraiseexcept, int, (int exceptions)); +# endif +_GL_CXXALIASWARN (feraiseexcept); +#endif + +#if @GNULIB_FETESTEXCEPT@ +/* Returns a bitmask of those exception flags among EXCEPTIONS that are + currently set. */ +# if @REPLACE_FETESTEXCEPT@ || (!@HAVE_FETESTEXCEPT@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef fetestexcept +# define fetestexcept rpl_fetestexcept +# endif +_GL_FUNCDECL_RPL (fetestexcept, int, (int exceptions)); +_GL_CXXALIAS_RPL (fetestexcept, int, (int exceptions)); +# else +# if !@HAVE_FETESTEXCEPT@ +_GL_FUNCDECL_SYS (fetestexcept, int, (int exceptions)); +# endif +_GL_CXXALIAS_SYS (fetestexcept, int, (int exceptions)); +# endif +_GL_CXXALIASWARN (fetestexcept); +#endif + /* ISO C 99 § 7.6.2 Floating-point exceptions ISO C 23 § 7.6.4 Floating-point exceptions diff --git a/m4/fenv-exceptions-tracking.m4 b/m4/fenv-exceptions-tracking.m4 new file mode 100644 index 0000000000..a7ffa80784 --- /dev/null +++ b/m4/fenv-exceptions-tracking.m4 @@ -0,0 +1,152 @@ +# fenv-exceptions-tracking.m4 serial 1 +dnl Copyright (C) 2023 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN_ONCE([gl_FENV_EXCEPTIONS_TRACKING], +[ + AC_REQUIRE([gl_FENV_H_DEFAULTS]) + AC_REQUIRE([AC_CANONICAL_HOST]) + + gl_MATHFUNC([feraiseexcept], [int], [(int)], [#include <fenv.h>]) + if test $gl_cv_func_feraiseexcept_no_libm = yes \ + || test $gl_cv_func_feraiseexcept_in_libm = yes; then + dnl On glibc 2.19/arm, feraiseexcept does not detect failures. + dnl Fixed through + dnl <https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=1a2f40e5d14ed6450696feacf04fca5eeceae7ef>. + dnl On Cygwin 3.4.6/x86_64, feraiseexcept does not trigger traps. + dnl See <https://sourceware.org/pipermail/cygwin/2023-October/254667.html>. + dnl On musl libc, on those CPUs where fenv-except-tracking-raise.c + dnl uses the "generic" approach, feraiseexcept does not trigger traps + dnl because it merely manipulates flags in the control register. + case "$host" in + arm*-*-linux*) + AC_CACHE_CHECK([whether feraiseexcept works], + [gl_cv_func_feraiseexcept_works], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + #ifdef __SOFTFP__ + #include <string.h> /* for __GNU_LIBRARY__ */ + #ifdef __GNU_LIBRARY__ + #include <features.h> + #if __GLIBC__ == 2 && __GLIBC_MINOR__ <= 19 + Unlucky user + #endif + #endif + #endif + ]], + [])], + [gl_cv_func_feraiseexcept_works="guessing yes"], + [gl_cv_func_feraiseexcept_works="guessing no"]) + ]) + case "$gl_cv_func_feraiseexcept_works" in + *yes) ;; + *) REPLACE_FERAISEEXCEPT=1 ;; + esac + ;; + x86_64-*-cygwin*) + AC_CACHE_CHECK([whether feraiseexcept works], + [gl_cv_func_feraiseexcept_works], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + ]], + [[feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + feraiseexcept (FE_INVALID); + return 0; + ]]) + ], + [gl_cv_func_feraiseexcept_works=no], + [gl_cv_func_feraiseexcept_works=yes], + [gl_cv_func_feraiseexcept_works="guessing no"]) + ]) + case "$gl_cv_func_feraiseexcept_works" in + *yes) ;; + *) REPLACE_FERAISEEXCEPT=1 ;; + esac + ;; + *-*-*-musl* | *-*-midipix*) + dnl This is only needed when the module 'fenv-exceptions-trapping' is + dnl in use. + m4_ifdef([g][l_FENV_EXCEPTIONS_TRAPPING], [ + AC_CACHE_CHECK([whether feraiseexcept works], + [gl_cv_func_feraiseexcept_works], + [case "$host_cpu" in + aarch64* | loongarch* | m68k* | s390* | sh4* | *86* | sparc*) + gl_cv_func_feraiseexcept_works="guessing no" ;; + *) + gl_cv_func_feraiseexcept_works="guessing yes" ;; + esac + ]) + case "$gl_cv_func_feraiseexcept_works" in + *yes) ;; + *) REPLACE_FERAISEEXCEPT=1 ;; + esac + ]) + ;; + powerpc*-*-aix*) + dnl On AIX, the unit test test-fenv-except-state-1 fails if we don't + dnl override feraiseexcept. XXX Not reproducible any more. + dnl REPLACE_FERAISEEXCEPT=1 + ;; + *86*-*-mingw* | *86*-*-windows*) + dnl On MSVC 14/clang, without this override, there are test failures + dnl (but not with MSVC 14 itself). Maybe fetestexcept does not consider + dnl the exception flags in the SSE unit? It's not clear. + AC_CACHE_CHECK([whether fetestexcept works], + [gl_cv_func_fetestexcept_works], + [AC_EGREP_CPP([Problem], [ +#ifndef __MINGW32__ + Problem +#endif + ], + [gl_cv_func_fetestexcept_works="guessing no"], + [gl_cv_func_fetestexcept_works="guessing yes"]) + ]) + case "$gl_cv_func_fetestexcept_works" in + *yes) ;; + *) REPLACE_FERAISEEXCEPT=1 ;; + esac + ;; + esac + if test $REPLACE_FECLEAREXCEPT = 1 \ + && test $REPLACE_FETESTEXCEPT = 1 \ + && test $REPLACE_FERAISEEXCEPT = 1; then + FENV_EXCEPTIONS_TRACKING_LIBM= + else + dnl It needs linking with -lm on + dnl glibc, FreeBSD, NetBSD, OpenBSD, AIX, HP-UX, IRIX, Solaris, Android. + if test $gl_cv_func_feraiseexcept_no_libm = yes; then + FENV_EXCEPTIONS_TRACKING_LIBM= + else + FENV_EXCEPTIONS_TRACKING_LIBM=-lm + fi + fi + else + HAVE_FECLEAREXCEPT=0 + HAVE_FETESTEXCEPT=0 + HAVE_FERAISEEXCEPT=0 + case "$host" in + alpha*-*-linux*) + dnl For feraiseexcept, which we take from libm. + FENV_EXCEPTIONS_TRACKING_LIBM=-lm + ;; + *) + FENV_EXCEPTIONS_TRACKING_LIBM= + ;; + esac + fi + if { test $HAVE_FECLEAREXCEPT = 0 || test $REPLACE_FECLEAREXCEPT = 1; } \ + || { test $HAVE_FETESTEXCEPT = 0 || test $REPLACE_FETESTEXCEPT = 1; }; then + gl_PREREQ_FENV_EXCEPTIONS + dnl Possibly need -lm for fpgetsticky(), fpsetsticky(). + if test $gl_cv_func_fpsetsticky_no_libm = no \ + && test $gl_cv_func_fpsetsticky_in_libm = yes \ + && test -z "$FENV_EXCEPTIONS_TRACKING_LIBM"; then + FENV_EXCEPTIONS_TRACKING_LIBM=-lm + fi + fi + AC_SUBST([FENV_EXCEPTIONS_TRACKING_LIBM]) +]) diff --git a/m4/fenv-exceptions.m4 b/m4/fenv-exceptions.m4 new file mode 100644 index 0000000000..be5e92c4ee --- /dev/null +++ b/m4/fenv-exceptions.m4 @@ -0,0 +1,23 @@ +# fenv-exceptions.m4 serial 1 +dnl Copyright (C) 2023 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Prerequisites of most lib/fenv-except-*.c files. +AC_DEFUN([gl_PREREQ_FENV_EXCEPTIONS], +[ + gl_MATHFUNC([fpsetsticky], [fp_except_t], [(fp_except_t)], + [#include <ieeefp.h> + /* The type is called 'fp_except_t' on FreeBSD, but 'fp_except' on + all other systems. */ + #if !defined __FreeBSD__ + #define fp_except_t fp_except + #endif + ]) + if test $gl_cv_func_fpsetsticky_no_libm = yes \ + || test $gl_cv_func_fpsetsticky_in_libm = yes; then + AC_DEFINE([HAVE_FPSETSTICKY], [1], + [Define to 1 if you have the 'fpsetsticky' function.]) + fi +]) diff --git a/modules/fenv-exceptions-tracking-c99 b/modules/fenv-exceptions-tracking-c99 new file mode 100644 index 0000000000..2e800cc47b --- /dev/null +++ b/modules/fenv-exceptions-tracking-c99 @@ -0,0 +1,50 @@ +Description: +Functions for tracking which floating-point exceptions have occurred: +feclearexcept, feraiseexcept, fetestexcept. + +Files: +lib/fenv-except-tracking-clear.c +lib/fenv-except-tracking-test.c +lib/fenv-except-tracking-raise.c +lib/fenv-private.h +m4/fenv-exceptions-tracking.m4 +m4/fenv-exceptions.m4 +m4/mathfunc.m4 + +Depends-on: +fenv + +configure.ac: +gl_FENV_EXCEPTIONS_TRACKING +gl_CONDITIONAL([GL_COND_OBJ_FENV_EXCEPTIONS_TRACKING_CLEAR], + [test $HAVE_FECLEAREXCEPT = 0 || test $REPLACE_FECLEAREXCEPT = 1]) +gl_CONDITIONAL([GL_COND_OBJ_FENV_EXCEPTIONS_TRACKING_TEST], + [test $HAVE_FETESTEXCEPT = 0 || test $REPLACE_FETESTEXCEPT = 1]) +gl_CONDITIONAL([GL_COND_OBJ_FENV_EXCEPTIONS_TRACKING_RAISE], + [test $HAVE_FERAISEEXCEPT = 0 || test $REPLACE_FERAISEEXCEPT = 1]) +gl_FENV_MODULE_INDICATOR([feclearexcept]) +gl_FENV_MODULE_INDICATOR([fetestexcept]) +gl_FENV_MODULE_INDICATOR([feraiseexcept]) + +Makefile.am: +if GL_COND_OBJ_FENV_EXCEPTIONS_TRACKING_CLEAR +lib_SOURCES += fenv-except-tracking-clear.c +endif +if GL_COND_OBJ_FENV_EXCEPTIONS_TRACKING_TEST +lib_SOURCES += fenv-except-tracking-test.c +endif +if GL_COND_OBJ_FENV_EXCEPTIONS_TRACKING_RAISE +lib_SOURCES += fenv-except-tracking-raise.c +endif + +Include: +#include <fenv.h> + +Link: +$(FENV_EXCEPTIONS_TRACKING_LIBM) + +License: +LGPLv2+ + +Maintainer: +all -- 2.34.1
>From 09095af14fcc2d2903a65901e204e40d18ca4fe2 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sat, 28 Oct 2023 18:39:32 +0200 Subject: [PATCH 2/3] fenv-exceptions-tracking-c99: Add tests. * tests/test-fenv-except-tracking-1.c: New file. * tests/test-fenv-except-tracking-2.sh: New file. * tests/test-fenv-except-tracking-2.c: New file. * tests/test-fenv-except-tracking-3.sh: New file. * tests/test-fenv-except-tracking-3.c: New file. * modules/fenv-exceptions-tracking-c99-tests: New file. --- ChangeLog | 8 + modules/fenv-exceptions-tracking-c99-tests | 27 +++ tests/test-fenv-except-tracking-1.c | 250 +++++++++++++++++++++ tests/test-fenv-except-tracking-2.c | 101 +++++++++ tests/test-fenv-except-tracking-2.sh | 20 ++ tests/test-fenv-except-tracking-3.c | 64 ++++++ tests/test-fenv-except-tracking-3.sh | 18 ++ 7 files changed, 488 insertions(+) create mode 100644 modules/fenv-exceptions-tracking-c99-tests create mode 100644 tests/test-fenv-except-tracking-1.c create mode 100644 tests/test-fenv-except-tracking-2.c create mode 100755 tests/test-fenv-except-tracking-2.sh create mode 100644 tests/test-fenv-except-tracking-3.c create mode 100755 tests/test-fenv-except-tracking-3.sh diff --git a/ChangeLog b/ChangeLog index b1c73c0f98..3af21529c2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,13 @@ 2023-10-28 Bruno Haible <br...@clisp.org> + fenv-exceptions-tracking-c99: Add tests. + * tests/test-fenv-except-tracking-1.c: New file. + * tests/test-fenv-except-tracking-2.sh: New file. + * tests/test-fenv-except-tracking-2.c: New file. + * tests/test-fenv-except-tracking-3.sh: New file. + * tests/test-fenv-except-tracking-3.c: New file. + * modules/fenv-exceptions-tracking-c99-tests: New file. + fenv-exceptions-tracking-c99: New module. * lib/fenv.in.h (feclearexcept, feraiseexcept, fetestexcept): New declarations. diff --git a/modules/fenv-exceptions-tracking-c99-tests b/modules/fenv-exceptions-tracking-c99-tests new file mode 100644 index 0000000000..8c1dde15c6 --- /dev/null +++ b/modules/fenv-exceptions-tracking-c99-tests @@ -0,0 +1,27 @@ +Files: +tests/test-fenv-except-tracking-1.c +tests/test-fenv-except-tracking-2.sh +tests/test-fenv-except-tracking-2.c +tests/test-fenv-except-tracking-3.sh +tests/test-fenv-except-tracking-3.c +tests/macros.h +m4/musl.m4 + +Depends-on: +fpe-trapping + +configure.ac: +gl_MUSL_LIBC + +Makefile.am: +TESTS += \ + test-fenv-except-tracking-1 \ + test-fenv-except-tracking-2.sh \ + test-fenv-except-tracking-3.sh +check_PROGRAMS += \ + test-fenv-except-tracking-1 \ + test-fenv-except-tracking-2 \ + test-fenv-except-tracking-3 +test_fenv_except_tracking_1_LDADD = $(LDADD) @FENV_EXCEPTIONS_TRACKING_LIBM@ +test_fenv_except_tracking_2_LDADD = $(LDADD) @FENV_EXCEPTIONS_TRACKING_LIBM@ @FPE_TRAPPING_LIBM@ +test_fenv_except_tracking_3_LDADD = $(LDADD) @FENV_EXCEPTIONS_TRACKING_LIBM@ @FPE_TRAPPING_LIBM@ diff --git a/tests/test-fenv-except-tracking-1.c b/tests/test-fenv-except-tracking-1.c new file mode 100644 index 0000000000..b7f871e12b --- /dev/null +++ b/tests/test-fenv-except-tracking-1.c @@ -0,0 +1,250 @@ +/* Test of tracking of floating-point exceptions. + Copyright (C) 2023 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/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2023. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +#include "macros.h" + +static volatile double a, b, c; + +int +main () +{ + /* Test setting all exception flags. */ + if (feraiseexcept (FE_ALL_EXCEPT) != 0) + { + fputs ("Skipping test: floating-point exceptions are not supported on this machine.\n", stderr); + return 77; + } + ASSERT (/* with the libc's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) == FE_ALL_EXCEPT + || /* with gnulib's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) + == (FE_DIVBYZERO | FE_INEXACT | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW)); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); + + /* Test clearing all exception flags. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == 0); + + + /* Test setting just one exception flag: FE_INVALID. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feraiseexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == FE_INVALID); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == 0); + + /* Test setting just one exception flag: FE_DIVBYZERO. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feraiseexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == 0); + + /* Test setting just one exception flag: FE_OVERFLOW. + On many architectures, this has the side-effect of also setting FE_INEXACT: + arm64, arm, alpha, hppa, ia64, loongarch64, s390, sh, sparc. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feraiseexcept (FE_OVERFLOW) == 0); + ASSERT ((fetestexcept (FE_ALL_EXCEPT) & ~FE_INEXACT) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + + /* Test setting just one exception flag: FE_UNDERFLOW. + On many architectures, this has the side-effect of also setting FE_INEXACT: + arm64, arm, alpha, hppa, ia64, loongarch64, s390, sh, sparc. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feraiseexcept (FE_UNDERFLOW) == 0); + ASSERT ((fetestexcept (FE_ALL_EXCEPT) & ~FE_INEXACT) == FE_UNDERFLOW); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + + /* Test setting just one exception flag: FE_INEXACT. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feraiseexcept (FE_INEXACT) == 0); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == FE_INEXACT); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); + + + /* Test clearing just one exception flag: FE_INVALID. */ + ASSERT (feraiseexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feclearexcept (FE_INVALID) == 0); + ASSERT (/* with the libc's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) == (FE_ALL_EXCEPT & ~FE_INVALID) + || /* with gnulib's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) + == (FE_DIVBYZERO | FE_INEXACT | FE_OVERFLOW | FE_UNDERFLOW)); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); + + /* Test clearing just one exception flag: FE_DIVBYZERO. */ + ASSERT (feraiseexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feclearexcept (FE_DIVBYZERO) == 0); + ASSERT (/* with the libc's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) == (FE_ALL_EXCEPT & ~FE_DIVBYZERO) + || /* with gnulib's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) + == (FE_INEXACT | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW)); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); + + /* Test clearing just one exception flag: FE_OVERFLOW. */ + ASSERT (feraiseexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feclearexcept (FE_OVERFLOW) == 0); + ASSERT (/* with the libc's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) == (FE_ALL_EXCEPT & ~FE_OVERFLOW) + || /* with gnulib's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) + == (FE_DIVBYZERO | FE_INEXACT | FE_INVALID | FE_UNDERFLOW)); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); + + /* Test clearing just one exception flag: FE_UNDERFLOW. */ + ASSERT (feraiseexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feclearexcept (FE_UNDERFLOW) == 0); + ASSERT (/* with the libc's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) == (FE_ALL_EXCEPT & ~FE_UNDERFLOW) + || /* with gnulib's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) + == (FE_DIVBYZERO | FE_INEXACT | FE_INVALID | FE_OVERFLOW)); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); + + /* Test clearing just one exception flag: FE_INEXACT. */ + ASSERT (feraiseexcept (FE_ALL_EXCEPT) == 0); + ASSERT (feclearexcept (FE_INEXACT) == 0); + ASSERT (/* with the libc's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) == (FE_ALL_EXCEPT & ~FE_INEXACT) + || /* with gnulib's feraiseexcept(): */ + fetestexcept (FE_ALL_EXCEPT) + == (FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW)); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + ASSERT (fetestexcept (FE_INEXACT) == 0); + + +#if !(defined __arm__ && defined __SOFTFP__) + /* Test the effects of an operation that produces FE_INVALID. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + a = 0; b = 0; c = a / b; + ASSERT (fetestexcept (FE_ALL_EXCEPT) == FE_INVALID); + ASSERT (fetestexcept (FE_INVALID) == FE_INVALID); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == 0); + + /* Test the effects of an operation that produces FE_DIVBYZERO. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + a = 3; b = 0; c = a / b; + ASSERT (fetestexcept (FE_ALL_EXCEPT) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == FE_DIVBYZERO); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == 0); + + /* Test the effects of an operation that produces FE_OVERFLOW. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + a = 1; for (int i = 0; i < 800; i++) a = 2.0 * a; + b = 1; for (int i = 0; i < 700; i++) b = 2.0 * b; + c = a * b; + { + int exc = fetestexcept (FE_ALL_EXCEPT); + ASSERT ((FE_OVERFLOW & !exc) == 0); + ASSERT ((exc & ~(FE_OVERFLOW | FE_INEXACT)) == 0); + } + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == FE_OVERFLOW); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + + /* Test the effects of an operation that produces FE_UNDERFLOW. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + a = 1; for (int i = 0; i < 800; i++) a = 0.5 * a; + b = 1; for (int i = 0; i < 700; i++) b = 0.5 * b; + c = a * b; + { + int exc = fetestexcept (FE_ALL_EXCEPT); + ASSERT ((FE_UNDERFLOW & !exc) == 0); + ASSERT ((exc & ~(FE_UNDERFLOW | FE_INEXACT)) == 0); + } + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == FE_UNDERFLOW); + +/* On alpha, this test works only when compiled with the GCC option + '-mieee-with-inexact'. */ +# if !defined __alpha + /* Test the effects of an operation that produces FE_INEXACT. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + a = 1; b = 3; c = a / b; + ASSERT (fetestexcept (FE_ALL_EXCEPT) == FE_INEXACT); + ASSERT (fetestexcept (FE_INVALID) == 0); + ASSERT (fetestexcept (FE_DIVBYZERO) == 0); + ASSERT (fetestexcept (FE_OVERFLOW) == 0); + ASSERT (fetestexcept (FE_UNDERFLOW) == 0); + ASSERT (fetestexcept (FE_INEXACT) == FE_INEXACT); +# endif +#endif + + + return 0; +} diff --git a/tests/test-fenv-except-tracking-2.c b/tests/test-fenv-except-tracking-2.c new file mode 100644 index 0000000000..e6f9bf1f9d --- /dev/null +++ b/tests/test-fenv-except-tracking-2.c @@ -0,0 +1,101 @@ +/* Test of tracking of floating-point exceptions. + Copyright (C) 2023 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/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2023. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +#include <stdio.h> + +#include "fpe-trapping.h" +#include "macros.h" + +#if HAVE_FPE_TRAPPING + +/* Check that triggering a floating-point operation can trigger a trap. */ + +int +main (int argc, char *argv[]) +{ + /* Clear FE_INVALID exceptions from past operations. */ + feclearexcept (FE_INVALID); + + /* An FE_INVALID exception shall trigger a SIGFPE signal, which by default + terminates the program. */ + if (sigfpe_on_invalid () < 0) + { + fputs ("Skipping test: trapping floating-point exceptions are not supported on this machine.\n", stderr); + return 77; + } + + if (argc > 1) + switch (argv[1][0]) + { + case 'f': + { + volatile float a, b; + _GL_UNUSED volatile float c; + a = 0; b = 0; c = a / b; + } + break; + + case 'd': + { + volatile double a, b; + _GL_UNUSED volatile double c; + a = 0; b = 0; c = a / b; + } + break; + + case 'l': + /* This test does not work on Linux/loongarch64 with glibc 2.37. + Likewise on Linux/alpha with glibc 2.7 on Linux 2.6.26. + Likewise on FreeBSD 12.2/sparc. + Cause unknown. */ + #if !((__GLIBC__ >= 2 && defined __loongarch__) \ + || ((__GLIBC__ == 2 && __GLIBC_MINOR__ < 36) && defined __alpha) \ + || (defined __FreeBSD__ && defined __sparc)) + { + volatile long double a, b; + _GL_UNUSED volatile long double c; + a = 0; b = 0; c = a / b; + } + #else + fputs ("Skipping test: known failure on this platform\n", stderr); + return 77; + #endif + break; + + default: + break; + } + + return 0; +} + +#else + +int +main () +{ + fputs ("Skipping test: feenableexcept or fpsetmask or fp_enable not available\n", stderr); + return 77; +} + +#endif diff --git a/tests/test-fenv-except-tracking-2.sh b/tests/test-fenv-except-tracking-2.sh new file mode 100755 index 0000000000..deb3ed6c79 --- /dev/null +++ b/tests/test-fenv-except-tracking-2.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Test that a floating-point operation can trigger a trap. + +final_rc=0 + +for arg in f d l; do + ${CHECKER} ./test-fenv-except-tracking-2${EXEEXT} $arg + rc=$? + if test $rc = 77; then + final_rc=77 + else + if test $rc = 0; then + echo "Failed: ./test-fenv-except-tracking-2 $arg" 1>&2 + exit 1 + fi + fi +done + +exit $final_rc diff --git a/tests/test-fenv-except-tracking-3.c b/tests/test-fenv-except-tracking-3.c new file mode 100644 index 0000000000..a7ef77a206 --- /dev/null +++ b/tests/test-fenv-except-tracking-3.c @@ -0,0 +1,64 @@ +/* Test of tracking of floating-point exceptions. + Copyright (C) 2023 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/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2023. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +#include <stdio.h> + +#include "fpe-trapping.h" +#include "macros.h" + +/* musl libc does not support floating-point exception trapping, even where + the hardware supports it. See + <https://wiki.musl-libc.org/functional-differences-from-glibc.html> */ +#if HAVE_FPE_TRAPPING && (!MUSL_LIBC || GNULIB_FEENABLEEXCEPT) + +/* Check that feraiseexcept() can trigger a trap. */ + +int +main () +{ + /* Clear FE_INVALID exceptions from past operations. */ + feclearexcept (FE_INVALID); + + /* An FE_INVALID exception shall trigger a SIGFPE signal, which by default + terminates the program. */ + if (sigfpe_on_invalid () < 0) + { + fputs ("Skipping test: trapping floating-point exceptions are not supported on this machine.\n", stderr); + return 77; + } + + feraiseexcept (FE_INVALID); + + return 0; +} + +#else + +int +main () +{ + fputs ("Skipping test: feenableexcept or fpsetmask or fp_enable not available\n", stderr); + return 77; +} + +#endif diff --git a/tests/test-fenv-except-tracking-3.sh b/tests/test-fenv-except-tracking-3.sh new file mode 100755 index 0000000000..f44e43a59b --- /dev/null +++ b/tests/test-fenv-except-tracking-3.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Test that feraiseexcept() can trigger a trap. + +final_rc=0 + +${CHECKER} ./test-fenv-except-tracking-3${EXEEXT} +rc=$? +if test $rc = 77; then + final_rc=77 +else + if test $rc = 0; then + echo "Failed: ./test-fenv-except-tracking-3" 1>&2 + exit 1 + fi +fi + +exit $final_rc -- 2.34.1
From ab7f5659b785f1db4192f67e66090a4ab7cd6e94 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sat, 28 Oct 2023 18:42:47 +0200 Subject: [PATCH 3/3] fpe-trapping: Always clear the FE_INVALID exception flag first. * lib/fpe-trapping.h: Include <fenv.h> on all platforms. (sigfpe_on_invalid) [AIX, HP-UX, IRIX, Solaris] : Clear the FE_INVALID exception flag first. * modules/fpe-trapping (Depends-on): Add fenv-exceptions-tracking-c99. --- ChangeLog | 8 ++++++++ lib/fpe-trapping.h | 21 ++++++++++++++++++--- modules/fpe-trapping | 1 + 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3af21529c2..a527f14ae8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2023-10-28 Bruno Haible <br...@clisp.org> + + fpe-trapping: Always clear the FE_INVALID exception flag first. + * lib/fpe-trapping.h: Include <fenv.h> on all platforms. + (sigfpe_on_invalid) [AIX, HP-UX, IRIX, Solaris] : Clear the FE_INVALID + exception flag first. + * modules/fpe-trapping (Depends-on): Add fenv-exceptions-tracking-c99. + 2023-10-28 Bruno Haible <br...@clisp.org> fenv-exceptions-tracking-c99: Add tests. diff --git a/lib/fpe-trapping.h b/lib/fpe-trapping.h index 6a63327da9..9040a218ce 100644 --- a/lib/fpe-trapping.h +++ b/lib/fpe-trapping.h @@ -25,11 +25,11 @@ #define HAVE_FPE_TRAPPING 1 +#include <fenv.h> + #if HAVE_FEENABLEEXCEPT /* glibc, FreeBSD ≥ 6.0, NetBSD ≥ 7.0, OpenBSD ≥ 5.0, Cygwin ≥ 1.7.8, Android, Haiku. */ -# include <fenv.h> - static int sigfpe_on_invalid () { @@ -69,7 +69,7 @@ sigfpe_on_invalid () #endif #if (defined __FreeBSD__ || defined __NetBSD__) && defined __arm__ - /* Work around a bug with FreeBSD 12.2 on arm64 CPUs: + /* Work around a bug with FreeBSD 12.2 on arm CPUs: feenableexcept returns success even if the CPU does not support the request. */ /* Test whether fpscr was actually changed as desired. */ @@ -102,8 +102,17 @@ sigfpe_on_invalid () static int sigfpe_on_invalid () { + /* Clear FE_INVALID exceptions from past operations. */ + feclearexcept (FE_INVALID); + fpsetmask (fpgetmask () | FP_X_INV); + #if defined __arm__ || defined __aarch64__ + /* Test whether the CPU supports the request. */ + if ((fpgetmask () & ~FP_X_INV) == 0) + return -1; + #endif + return 0; } @@ -115,6 +124,9 @@ sigfpe_on_invalid () static int sigfpe_on_invalid () { + /* Clear FE_INVALID exceptions from past operations. */ + feclearexcept (FE_INVALID); + fesettrapenable (fegettrapenable () | FE_INVALID); return 0; @@ -128,6 +140,9 @@ sigfpe_on_invalid () static int sigfpe_on_invalid () { + /* Clear FE_INVALID exceptions from past operations. */ + feclearexcept (FE_INVALID); + /* Enable precise trapping mode. Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine> */ fp_trap (FP_TRAP_SYNC); diff --git a/modules/fpe-trapping b/modules/fpe-trapping index d8acbe8a27..353e77bdef 100644 --- a/modules/fpe-trapping +++ b/modules/fpe-trapping @@ -10,6 +10,7 @@ m4/musl.m4 Depends-on: extensions +fenv-exceptions-tracking-c99 configure.ac: gl_FPE_TRAPPING -- 2.34.1