These patches add a module 'fenv-exceptions-trapping', that portably implement the GNU functions feenableexcept fedisableexcept fegetexcept (a terrible misnomer, btw)
Again, the unit tests uncovered several problems on several platforms. 2023-10-31 Bruno Haible <br...@clisp.org> fenv-exceptions-trapping: Add tests. * tests/test-fenv-except-trapping-1.c: New file. * tests/test-fenv-except-trapping-2.sh: New file. * tests/test-fenv-except-trapping-2.c: New file. * modules/fenv-exceptions-trapping-tests: New file. fenv-exceptions-trapping: New module. * lib/fenv.in.h (feenableexcept, fedisableexcept, fegetexcept): New declarations. * lib/fenv-except-trapping.c: New file, based on glibc. * m4/fenv_h.m4 (gl_FENV_H): Test also whether fegetexcept is declared. * m4/fenv-exceptions-trapping.m4: New file. * modules/fenv-exceptions-trapping: New file. * doc/glibc-functions/fegetexcept.texi: Mention the new module. * doc/glibc-functions/fedisableexcept.texi: Likewise. * doc/glibc-functions/feenableexcept.texi: Likewise. Mention the glibc, macOS, FreeBSD bugs.
From e5a52e29dfa5a292f9f94c56c9ed2f3c9ff189af Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 31 Oct 2023 21:46:13 +0100 Subject: [PATCH 1/2] fenv-exceptions-trapping: New module. * lib/fenv.in.h (feenableexcept, fedisableexcept, fegetexcept): New declarations. * lib/fenv-except-trapping.c: New file, based on glibc. * m4/fenv_h.m4 (gl_FENV_H): Test also whether fegetexcept is declared. * m4/fenv-exceptions-trapping.m4: New file. * modules/fenv-exceptions-trapping: New file. * doc/glibc-functions/fegetexcept.texi: Mention the new module. * doc/glibc-functions/fedisableexcept.texi: Likewise. * doc/glibc-functions/feenableexcept.texi: Likewise. Mention the glibc, macOS, FreeBSD bugs. --- ChangeLog | 14 + doc/glibc-functions/fedisableexcept.texi | 8 +- doc/glibc-functions/feenableexcept.texi | 13 +- doc/glibc-functions/fegetexcept.texi | 8 +- lib/fenv-except-trapping.c | 951 +++++++++++++++++++++++ lib/fenv.in.h | 94 +++ m4/fenv-exceptions-trapping.m4 | 117 +++ m4/fenv_h.m4 | 3 +- modules/fenv-exceptions-trapping | 40 + 9 files changed, 1236 insertions(+), 12 deletions(-) create mode 100644 lib/fenv-except-trapping.c create mode 100644 m4/fenv-exceptions-trapping.m4 create mode 100644 modules/fenv-exceptions-trapping diff --git a/ChangeLog b/ChangeLog index 0476828c50..f7d1a2237e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2023-10-31 Bruno Haible <br...@clisp.org> + + fenv-exceptions-trapping: New module. + * lib/fenv.in.h (feenableexcept, fedisableexcept, fegetexcept): New + declarations. + * lib/fenv-except-trapping.c: New file, based on glibc. + * m4/fenv_h.m4 (gl_FENV_H): Test also whether fegetexcept is declared. + * m4/fenv-exceptions-trapping.m4: New file. + * modules/fenv-exceptions-trapping: New file. + * doc/glibc-functions/fegetexcept.texi: Mention the new module. + * doc/glibc-functions/fedisableexcept.texi: Likewise. + * doc/glibc-functions/feenableexcept.texi: Likewise. Mention the glibc, + macOS, FreeBSD bugs. + 2023-10-30 Bruno Haible <br...@clisp.org> fenv-exceptions-state-c99: Fix the x86_64 and i386 case. diff --git a/doc/glibc-functions/fedisableexcept.texi b/doc/glibc-functions/fedisableexcept.texi index 8b41ae4d87..d26397f44f 100644 --- a/doc/glibc-functions/fedisableexcept.texi +++ b/doc/glibc-functions/fedisableexcept.texi @@ -17,15 +17,15 @@ @uref{https://www.kernel.org/doc/man-pages/online/pages/man3/fedisableexcept.3.html,,man fedisableexcept}. @end itemize -Gnulib module: --- +Gnulib module: fenv-exceptions-trapping Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +macOS 11.1, musl libc, FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin 1.7.7, mingw, MSVC 14, Android 4.4. @end itemize Portability problems not fixed by Gnulib: @itemize -@item -This function is missing on some platforms: -macOS 11.1, FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin 1.7.7, mingw, MSVC 14, Android 4.4. @end itemize diff --git a/doc/glibc-functions/feenableexcept.texi b/doc/glibc-functions/feenableexcept.texi index dfca91f2a7..a858b4d6c6 100644 --- a/doc/glibc-functions/feenableexcept.texi +++ b/doc/glibc-functions/feenableexcept.texi @@ -17,15 +17,22 @@ @uref{https://www.kernel.org/doc/man-pages/online/pages/man3/feenableexcept.3.html,,man feenableexcept}. @end itemize -Gnulib module: --- +Gnulib module: fenv-exceptions-trapping Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +macOS 11.1, musl libc, FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin 1.7.7, mingw, MSVC 14, Android 4.4. +@item +This function does not detect failures on +glibc 2.19/aarch64, FreeBSD 12.2/arm, FreeBSD 12.2/arm64. @end itemize Portability problems not fixed by Gnulib: @itemize @item -This function is missing on some platforms: -macOS 11.1, FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin 1.7.7, mingw, MSVC 14, Android 4.4. +The resulting signal is @code{SIGILL} instead of @code{SIGFPE} +on some platforms: +macOS 12.5/arm64. @end itemize diff --git a/doc/glibc-functions/fegetexcept.texi b/doc/glibc-functions/fegetexcept.texi index e3e3e43959..eaa1882d83 100644 --- a/doc/glibc-functions/fegetexcept.texi +++ b/doc/glibc-functions/fegetexcept.texi @@ -17,15 +17,15 @@ @uref{https://www.kernel.org/doc/man-pages/online/pages/man3/fegetexcept.3.html,,man fegetexcept}. @end itemize -Gnulib module: --- +Gnulib module: fenv-exceptions-trapping Portability problems fixed by Gnulib: @itemize +@item +This function is missing on many non-glibc platforms: +macOS 11.1, musl libc, FreeBSD 13.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin 1.7.7, mingw, MSVC 14, Android 4.4. @end itemize Portability problems not fixed by Gnulib: @itemize -@item -This function is missing on many non-glibc platforms: -macOS 11.1, FreeBSD 13.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.4, Cygwin 1.7.7, mingw, MSVC 14, Android 4.4. @end itemize diff --git a/lib/fenv-except-trapping.c b/lib/fenv-except-trapping.c new file mode 100644 index 0000000000..a86cbd2d39 --- /dev/null +++ b/lib/fenv-except-trapping.c @@ -0,0 +1,951 @@ +/* Functions for turning floating-point exceptions into traps (signals). + 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>/{feenablxcpt.c,fedisblxcpt.c,fegetexcept.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 +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + +# if defined _MSC_VER + + exceptions = exceptions_to_x86hardware (exceptions); + + /* Enable the traps only in the SSE unit. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr & ~(exceptions << 7); + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + + unsigned int trapbits = 0x3f & ~(orig_mxcsr >> 7); + return x86hardware_to_exceptions (trapbits); + +# else + + /* Enable the traps in the 387 unit. */ + unsigned short fctrl, orig_fctrl; + _FPU_GETCW (orig_fctrl); + fctrl = orig_fctrl & ~exceptions; + if (fctrl != orig_fctrl) + _FPU_SETCW (fctrl); + + if (CPU_HAS_SSE ()) + { + /* Enable the traps in the SSE unit as well. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr & ~(exceptions << 7); + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + } + + return FE_ALL_EXCEPT & ~orig_fctrl; + +# endif +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + +# if defined _MSC_VER + + exceptions = exceptions_to_x86hardware (exceptions); + + /* Disable the traps only in the SSE unit. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr | (exceptions << 7); + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + + unsigned int trapbits = 0x3f & ~(orig_mxcsr >> 7); + return x86hardware_to_exceptions (trapbits); + +# else + + /* Disable the traps in the 387 unit. */ + unsigned short fctrl, orig_fctrl; + _FPU_GETCW (orig_fctrl); + fctrl = orig_fctrl | exceptions; + if (fctrl != orig_fctrl) + _FPU_SETCW (fctrl); + + if (CPU_HAS_SSE ()) + { + /* Disable the traps in the SSE unit as well. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = orig_mxcsr | (exceptions << 7); + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); + } + + return FE_ALL_EXCEPT & ~orig_fctrl; + +# endif +} + +int +fegetexcept (void) +{ +# if defined _MSC_VER + /* Look at the trap bits in the SSE unit. */ + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + unsigned int trapbits = 0x3f & ~(mxcsr >> 7); + return x86hardware_to_exceptions (trapbits); +# else + /* Look at the trap bits in the 387 unit. */ + unsigned short fctrl; + _FPU_GETCW (fctrl); + return FE_ALL_EXCEPT & ~fctrl; +# endif +} + +# elif defined __aarch64__ /* arm64 */ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpcr, orig_fpcr; + _FPU_GETCW (orig_fpcr); + fpcr = orig_fpcr | (exceptions << 8); + if (fpcr != orig_fpcr) + { + _FPU_SETCW (fpcr); + /* Test whether fpcr was actually changed as desired. */ + unsigned long new_fpcr; + _FPU_GETCW (new_fpcr); + if (new_fpcr != fpcr) + return -1; + } + + return FE_ALL_EXCEPT & (orig_fpcr >> 8); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpcr, orig_fpcr; + _FPU_GETCW (orig_fpcr); + fpcr = orig_fpcr & ~(exceptions << 8); + if (fpcr != orig_fpcr) + { + _FPU_SETCW (fpcr); + /* Testing whether fpcr was actually changed as desired is not needed + here, since we only cleared some bits. */ + } + + return FE_ALL_EXCEPT & (orig_fpcr >> 8); +} + +int +fegetexcept (void) +{ + unsigned long fpcr; + _FPU_GETCW (fpcr); + return FE_ALL_EXCEPT & (fpcr >> 8); +} + +# elif defined __arm__ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + +# ifdef __SOFTFP__ + if (exceptions != 0) + return -1; + return 0; +# else + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | (exceptions << 8); + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + /* Test whether fpscr was actually changed as desired. */ + unsigned int new_fpscr; + _FPU_GETCW (new_fpscr); + if (new_fpscr != fpscr) + return -1; + } + + return FE_ALL_EXCEPT & (orig_fpscr >> 8); +# endif +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + +# ifdef __SOFTFP__ + return 0; +# else + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr & ~(exceptions << 8); + if (fpscr != orig_fpscr) + { + _FPU_SETCW (fpscr); + /* Testing whether fpscr was actually changed as desired is not needed + here, since we only cleared some bits. */ + } + + return FE_ALL_EXCEPT & (orig_fpscr >> 8); +# endif +} + +int +fegetexcept (void) +{ +# ifdef __SOFTFP__ + return 0; +# else + unsigned int fpscr; + _FPU_GETCW (fpscr); + return FE_ALL_EXCEPT & (fpscr >> 8); +# endif +} + +# elif defined __alpha + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long swcr, orig_swcr; + orig_swcr = __ieee_get_fp_control (); + swcr = orig_swcr | ((unsigned long) exceptions >> 16); + if (swcr != orig_swcr) + __ieee_set_fp_control (swcr); + + return FE_ALL_EXCEPT & (orig_swcr << 16); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long swcr, orig_swcr; + orig_swcr = __ieee_get_fp_control (); + swcr = orig_swcr & ~((unsigned long) exceptions >> 16); + if (swcr != orig_swcr) + __ieee_set_fp_control (swcr); + + return FE_ALL_EXCEPT & (orig_swcr << 16); +} + +int +fegetexcept (void) +{ + unsigned long swcr = __ieee_get_fp_control (); + return FE_ALL_EXCEPT & (swcr << 16); +} + +# elif defined __hppa + +int +feenableexcept (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]; + /* Set all the relevant bits. */ + s.halfreg[0] |= (unsigned int) exceptions; + 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 FE_ALL_EXCEPT & old_halfreg0; +} + +int +fedisableexcept (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; + 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 FE_ALL_EXCEPT & old_halfreg0; +} + +int +fegetexcept (void) +{ + 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 FE_ALL_EXCEPT & s.halfreg[0]; +} + +# elif defined __ia64__ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpsr, orig_fpsr; + _FPU_GETCW (orig_fpsr); + fpsr = orig_fpsr & ~ (unsigned long) exceptions; + if (fpsr != orig_fpsr) + _FPU_SETCW (fpsr); + + return FE_ALL_EXCEPT & ~orig_fpsr; +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned long fpsr, orig_fpsr; + _FPU_GETCW (orig_fpsr); + fpsr = orig_fpsr | (unsigned long) exceptions; + if (fpsr != orig_fpsr) + _FPU_SETCW (fpsr); + + return FE_ALL_EXCEPT & ~orig_fpsr; +} + +int +fegetexcept (void) +{ + unsigned long fpsr; + _FPU_GETCW (fpsr); + return FE_ALL_EXCEPT & ~fpsr; +} + +# elif defined __m68k__ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpcr, orig_fpcr; + _FPU_GETCW (orig_fpcr); + fpcr = orig_fpcr | ((exceptions << 6) | (exceptions & FE_INVALID ? 0xc000 : 0)); + if (fpcr != orig_fpcr) + _FPU_SETCW (fpcr); + + return FE_ALL_EXCEPT & (orig_fpcr >> 6); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpcr, orig_fpcr; + _FPU_GETCW (orig_fpcr); + fpcr = orig_fpcr & ~ ((exceptions << 6) | (exceptions & FE_INVALID ? 0xc000 : 0)); + if (fpcr != orig_fpcr) + _FPU_SETCW (fpcr); + + return FE_ALL_EXCEPT & (orig_fpcr >> 6); +} + +int +fegetexcept (void) +{ + unsigned int fpcr; + _FPU_GETCW (fpcr); + return FE_ALL_EXCEPT & (fpcr >> 6); +} + +# elif defined __mips__ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr | (exceptions << 5); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return FE_ALL_EXCEPT & (orig_fcsr >> 5); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr & ~ (exceptions << 5); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return FE_ALL_EXCEPT & (orig_fcsr >> 5); +} + +int +fegetexcept (void) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); + return FE_ALL_EXCEPT & (fcsr >> 5); +} + +# elif defined __loongarch__ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr | (exceptions >> 16); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return FE_ALL_EXCEPT & (orig_fcsr << 16); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fcsr, orig_fcsr; + _FPU_GETCW (orig_fcsr); + fcsr = orig_fcsr & ~ (exceptions >> 16); + if (fcsr != orig_fcsr) + _FPU_SETCW (fcsr); + + return FE_ALL_EXCEPT & (orig_fcsr << 16); +} + +int +fegetexcept (void) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); + return FE_ALL_EXCEPT & (fcsr << 16); +} + +# elif defined __powerpc__ + +# if defined __linux__ +# include <sys/prctl.h> +# elif defined _AIX +# include <fptrap.h> +# endif + +int +feenableexcept (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; + + memenv.u |= ((unsigned int) exceptions >> 22); + + if (!(memenv.u == orig_memenv.u)) + { + if ((orig_memenv.u & 0x000000f7) == 0 && (memenv.u & 0x000000f7) != 0) + { + /* Put the thread into precise trapping mode. */ +# if defined __linux__ || defined __NetBSD__ + prctl (PR_SET_FPEXC, PR_FP_EXC_PRECISE); +# elif defined _AIX + /* Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine> */ + fp_trap (FP_TRAP_SYNC); +# endif + } + + _FPU_SETCW_AS_DOUBLE (memenv.f); + } + + return FE_ALL_EXCEPT & ((unsigned int) orig_memenv.u << 22); +} + +int +fedisableexcept (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; + + memenv.u &= ~ ((unsigned int) exceptions >> 22); + + if (!(memenv.u == orig_memenv.u)) + { + _FPU_SETCW_AS_DOUBLE (memenv.f); + + if ((orig_memenv.u & 0x000000f7) != 0 && (memenv.u & 0x000000f7) == 0) + { + /* Put the thread into no-trapping mode. */ +# if defined __linux__ || defined __NetBSD__ + prctl (PR_SET_FPEXC, PR_FP_EXC_DISABLED); +# elif defined _AIX + /* Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine> */ + fp_trap (FP_TRAP_OFF); +# endif + } + } + + return FE_ALL_EXCEPT & ((unsigned int) orig_memenv.u << 22); +} + +int +fegetexcept (void) +{ + union { unsigned long long u; double f; } memenv; + _FPU_GETCW_AS_DOUBLE (memenv.f); + + return FE_ALL_EXCEPT & ((unsigned int) memenv.u << 22); +} + +# elif defined __riscv + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + if (exceptions != 0) + return -1; + return 0; +} + +int +fedisableexcept (int exceptions) +{ + return 0; +} + +int +fegetexcept (void) +{ + return 0; +} + +# elif defined __s390__ || defined __s390x__ + +int +feenableexcept (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 | ((unsigned int) exceptions << 24); +# else /* musl libc compatible FE_* values */ + fpc = orig_fpc | ((unsigned int) exceptions << 8); +# endif + if (fpc != orig_fpc) + _FPU_SETCW (fpc); + +# if FE_INEXACT == 8 /* glibc compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fpc >> 24); +# else /* musl libc compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fpc >> 8); +# endif +} + +int +fedisableexcept (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 & ~((unsigned int) exceptions << 24); +# else /* musl libc compatible FE_* values */ + fpc = orig_fpc & ~((unsigned int) exceptions << 8); +# endif + if (fpc != orig_fpc) + _FPU_SETCW (fpc); + +# if FE_INEXACT == 8 /* glibc compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fpc >> 24); +# else /* musl libc compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fpc >> 8); +# endif +} + +int +fegetexcept (void) +{ + unsigned int fpc; + _FPU_GETCW (fpc); +# if FE_INEXACT == 8 /* glibc compatible FE_* values */ + return FE_ALL_EXCEPT & (fpc >> 24); +# else /* musl libc compatible FE_* values */ + return FE_ALL_EXCEPT & (fpc >> 8); +# endif +} + +# elif defined __sh__ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr | ((unsigned int) exceptions << 5); + if (fpscr != orig_fpscr) + _FPU_SETCW (fpscr); + + return FE_ALL_EXCEPT & (orig_fpscr >> 5); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + fpscr = orig_fpscr & ~ ((unsigned int) exceptions << 5); + if (fpscr != orig_fpscr) + _FPU_SETCW (fpscr); + + return FE_ALL_EXCEPT & (orig_fpscr >> 5); +} + +int +fegetexcept (void) +{ + unsigned int fpscr; + _FPU_GETCW (fpscr); + return FE_ALL_EXCEPT & (fpscr >> 5); +} + +# elif defined __sparc + +int +feenableexcept (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 << 18); +# else /* Solaris compatible FE_* values */ + fsr = orig_fsr | (exceptions << 23); +# endif + if (fsr != orig_fsr) + _FPU_SETCW (fsr); + +# if FE_INEXACT == 32 /* glibc compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fsr >> 18); +# else /* Solaris compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fsr >> 23); +# endif +} + +int +fedisableexcept (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 << 18); +# else /* Solaris compatible FE_* values */ + fsr = orig_fsr & ~(exceptions << 23); +# endif + if (fsr != orig_fsr) + _FPU_SETCW (fsr); + +# if FE_INEXACT == 32 /* glibc compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fsr >> 18); +# else /* Solaris compatible FE_* values */ + return FE_ALL_EXCEPT & (orig_fsr >> 23); +# endif +} + +int +fegetexcept (void) +{ + unsigned long fsr; + _FPU_GETCW (fsr); +# if FE_INEXACT == 32 /* glibc compatible FE_* values */ + return FE_ALL_EXCEPT & (fsr >> 18); +# else /* Solaris compatible FE_* values */ + return FE_ALL_EXCEPT & (fsr >> 23); +# 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_FPSETMASK +/* FreeBSD ≥ 3.1, NetBSD ≥ 1.1, OpenBSD, IRIX, Solaris, Minix ≥ 3.2. */ + +/* Get fpgetmask, fpsetmask. */ +# 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 + +static fp_except_t +exceptions_to_mask (int exceptions) +{ + fp_except_t m = 0; + if (exceptions & FE_INVALID) + m |= FP_X_INV; + if (exceptions & FE_DIVBYZERO) + m |= FP_X_DZ; + if (exceptions & FE_OVERFLOW) + m |= FP_X_OFL; + if (exceptions & FE_UNDERFLOW) + m |= FP_X_UFL; + if (exceptions & FE_INEXACT) + m |= FP_X_IMP; + return m; +} + +static int +mask_to_exceptions (fp_except_t m) +{ + int exceptions = 0; + if (m & FP_X_INV) + exceptions |= FE_INVALID; + if (m & FP_X_DZ) + exceptions |= FE_DIVBYZERO; + if (m & FP_X_OFL) + exceptions |= FE_OVERFLOW; + if (m & FP_X_UFL) + exceptions |= FE_UNDERFLOW; + if (m & FP_X_IMP) + exceptions |= FE_INEXACT; + return exceptions; +} + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + fp_except_t trapbits = fpgetmask (); + fpsetmask (trapbits | exceptions_to_mask (exceptions)); + return mask_to_exceptions (trapbits); +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + fp_except_t trapbits = fpgetmask (); + fpsetmask (trapbits & ~ exceptions_to_mask (exceptions)); + return mask_to_exceptions (trapbits); +} + +int +fegetexcept (void) +{ + fp_except_t trapbits = fpgetmask (); + return mask_to_exceptions (trapbits); +} + +# elif defined _AIX && defined __powerpc__ /* AIX */ + +# include <fptrap.h> +/* Documentation: + <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-any-enable-fp-is-enabled-fp-enable-all-fp-enable-fp-disable-all-fp-disable-subroutine> + <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine> */ + +/* Convert from an 'int exceptions' to an fptrap_t. */ +# if 0 /* Unoptimized */ +# define exceptions_to_fptrap(exceptions) \ + ( ((exceptions) & FE_INVALID ? TRP_INVALID : 0) \ + | ((exceptions) & FE_DIVBYZERO ? TRP_DIV_BY_ZERO : 0) \ + | ((exceptions) & FE_OVERFLOW ? TRP_OVERFLOW : 0) \ + | ((exceptions) & FE_UNDERFLOW ? TRP_UNDERFLOW : 0) \ + | ((exceptions) & FE_INEXACT ? TRP_INEXACT : 0)) +# else /* Optimized */ +# define exceptions_to_fptrap(exceptions) \ + (((unsigned int) (exceptions) & FE_ALL_EXCEPT) >> 22) +# endif + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + int prev_enabled = fegetexcept (); + if (exceptions != 0) + { + fp_enable (exceptions_to_fptrap (exceptions)); + if (prev_enabled == 0 /* && fegetexcept () != 0 */) + /* Enable precise trapping mode. */ + fp_trap (FP_TRAP_SYNC); + } + return prev_enabled; +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + int prev_enabled = fegetexcept (); + if (exceptions != 0) + { + fp_disable (exceptions_to_fptrap (exceptions)); + if (prev_enabled != 0 && fegetexcept () == 0) + /* Disable precise trapping mode. */ + fp_trap (FP_TRAP_OFF); + } + return prev_enabled; +} + +int +fegetexcept (void) +{ + return fegetexcept_impl (); +} + +# elif HAVE_FESETTRAPENABLE +/* HP-UX, QNX */ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + int prev_enabled = fegettrapenable (); + if (exceptions != 0) + fesettrapenable (prev_enabled | exceptions); + return prev_enabled; +} + +int +fedisableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + + int prev_enabled = fegettrapenable (); + if (exceptions != 0) + fesettrapenable (prev_enabled & ~exceptions); + return prev_enabled; +} + +int +fegetexcept (void) +{ + return fegettrapenable (); +} + +# else + +# define NEED_FALLBACK 1 + +# endif + +#endif + +#if NEED_FALLBACK + +/* A dummy fallback. */ + +int +feenableexcept (int exceptions) +{ + exceptions &= FE_ALL_EXCEPT; + if (exceptions != 0) + return -1; + return 0; +} + +int +fedisableexcept (int exceptions) +{ + return 0; +} + +int +fegetexcept (void) +{ + return 0; +} + +#endif diff --git a/lib/fenv.in.h b/lib/fenv.in.h index 2b2f770e2c..4fd5ea2f1a 100644 --- a/lib/fenv.in.h +++ b/lib/fenv.in.h @@ -542,6 +542,100 @@ _GL_WARN_ON_USE (fesetexcept, "fesetexcept is unportable - " #endif +/* GNU extensions. */ + +#if @GNULIB_FEENABLEEXCEPT@ +/* Enables trapping for the floating-point exceptions denoted by the bit mask + EXCEPTIONS. + Returns the bit mask of floating-point exceptions for which trapping was + enabled before the call, or -1 upon failure. + Note: This function is a misnomer. It does not enable the specified + floating-point exceptions; it enables *trapping* on them. It should better + be called 'feenabletraps'. */ +# if @REPLACE_FEENABLEEXCEPT@ || (!@HAVE_FEENABLEEXCEPT@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef feenableexcept +# define feenableexcept rpl_feenableexcept +# endif +_GL_FUNCDECL_RPL (feenableexcept, int, (int exceptions)); +_GL_CXXALIAS_RPL (feenableexcept, int, (int exceptions)); +# else +# if !@HAVE_FEENABLEEXCEPT@ +_GL_FUNCDECL_SYS (feenableexcept, int, (int exceptions)); +# endif +_GL_CXXALIAS_SYS (feenableexcept, int, (int exceptions)); +# endif +_GL_CXXALIASWARN (feenableexcept); +#elif defined GNULIB_POSIXCHECK +# undef feenableexcept +# if HAVE_RAW_DECL_FEENABLEEXCEPT +_GL_WARN_ON_USE (feenableexcept, "feenableexcept is unportable - " + "use gnulib module fenv-exceptions-trapping for portability"); +# endif +#endif + +#if @GNULIB_FEDISABLEEXCEPT@ +/* Disables trapping for the floating-point exceptions denoted by the bit mask + EXCEPTIONS. + Returns the bit mask of floating-point exceptions for which trapping was + enabled before the call, or -1 upon failure. + Note: This function is a misnomer. It does not disable the specified + floating-point exceptions; it disables *trapping* on them. It should better + be called 'fedisabletraps'. */ +# if @REPLACE_FEDISABLEEXCEPT@ || (!@HAVE_FEDISABLEEXCEPT@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef fedisableexcept +# define fedisableexcept rpl_fedisableexcept +# endif +_GL_FUNCDECL_RPL (fedisableexcept, int, (int exceptions)); +_GL_CXXALIAS_RPL (fedisableexcept, int, (int exceptions)); +# else +# if !@HAVE_FEDISABLEEXCEPT@ +_GL_FUNCDECL_SYS (fedisableexcept, int, (int exceptions)); +# endif +_GL_CXXALIAS_SYS (fedisableexcept, int, (int exceptions)); +# endif +_GL_CXXALIASWARN (fedisableexcept); +#elif defined GNULIB_POSIXCHECK +# undef fedisableexcept +# if HAVE_RAW_DECL_FEDISABLEEXCEPT +_GL_WARN_ON_USE (fedisableexcept, "fedisableexcept is unportable - " + "use gnulib module fenv-exceptions-trapping for portability"); +# endif +#endif + +#if @GNULIB_FEGETEXCEPT@ +/* Returns the bit mask of floating-point exceptions for which trapping is + enabled. + Note: This function is an even bigger misnomer: + - It does not test for the specified floating-point exceptions; + fetestexcept() does that. It tests whether *trapping* on these + exceptions is enabled. + - It is in no way the opposite of fesetexcept(). + It should better be called 'fegettraps'. */ +# if @REPLACE_FEGETEXCEPT@ || (!@HAVE_FEGETEXCEPT@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef fegetexcept +# define fegetexcept rpl_fegetexcept +# endif +_GL_FUNCDECL_RPL (fegetexcept, int, (void)); +_GL_CXXALIAS_RPL (fegetexcept, int, (void)); +# else +# if !@HAVE_FEGETEXCEPT@ +_GL_FUNCDECL_SYS (fegetexcept, int, (void)); +# endif +_GL_CXXALIAS_SYS (fegetexcept, int, (void)); +# endif +_GL_CXXALIASWARN (fegetexcept); +#elif defined GNULIB_POSIXCHECK +# undef fegetexcept +# if HAVE_RAW_DECL_FEGETEXCEPT +_GL_WARN_ON_USE (fegetexcept, "fegetexcept is unportable - " + "use gnulib module fenv-exceptions-trapping for portability"); +# endif +#endif + + /* ISO C 99 § 7.6.2 Floating-point exceptions ISO C 23 § 7.6.4 Floating-point exceptions API with fexcept_t. diff --git a/m4/fenv-exceptions-trapping.m4 b/m4/fenv-exceptions-trapping.m4 new file mode 100644 index 0000000000..f85e356a88 --- /dev/null +++ b/m4/fenv-exceptions-trapping.m4 @@ -0,0 +1,117 @@ +# fenv-exceptions-trapping.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_TRAPPING], +[ + AC_REQUIRE([gl_FENV_H_DEFAULTS]) + dnl Persuade glibc <fenv.h> to declare feenableexcept(). + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_REQUIRE([AC_CANONICAL_HOST]) + + gl_MATHFUNC([feenableexcept], [int], [(int)], [#include <fenv.h>]) + if test $gl_cv_func_feenableexcept_no_libm = yes \ + || test $gl_cv_func_feenableexcept_in_libm = yes; then + dnl On glibc 2.19/aarch64, feenableexcept does not detect failures. + dnl Fixed through + dnl <https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=302949e2940a9da3f6364a1574619e621b7e1e71>. + dnl Similarly on FreeBSD 12.2/arm and FreeBSD 12.2/arm64. + case "$host" in + aarch64*-*-linux*) + AC_CACHE_CHECK([whether feenableexcept works], + [gl_cv_func_feenableexcept_works], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + #include <string.h> /* for __GNU_LIBRARY__ */ + #ifdef __GNU_LIBRARY__ + #include <features.h> + #if __GLIBC__ == 2 && __GLIBC_MINOR__ <= 19 + Unlucky user + #endif + #endif + ]], + [])], + [gl_cv_func_feenableexcept_works="guessing yes"], + [gl_cv_func_feenableexcept_works="guessing no"]) + ]) + case "$gl_cv_func_feenableexcept_works" in + *yes) ;; + *) REPLACE_FEENABLEEXCEPT=1 ;; + esac + ;; + arm*-*-freebsd* | aarch64*-*-freebsd*) + REPLACE_FEENABLEEXCEPT=1 + ;; + esac + if test $REPLACE_FEENABLEEXCEPT = 1; then + dnl Two more functions are defined in the same compilation unit. + dnl Override them as well. + REPLACE_FEDISABLEEXCEPT=1 + REPLACE_FEGETEXCEPT=1 + FENV_EXCEPTIONS_TRAPPING_LIBM= + else + dnl It needs linking with -lm on glibc. + if test $gl_cv_func_feenableexcept_no_libm = yes; then + FENV_EXCEPTIONS_TRAPPING_LIBM= + else + FENV_EXCEPTIONS_TRAPPING_LIBM=-lm + fi + fi + else + HAVE_FEENABLEEXCEPT=0 + HAVE_FEDISABLEEXCEPT=0 + HAVE_FEGETEXCEPT=0 + case "$host" in + alpha*-*-linux*) + dnl For feenableexcept, which we take from libm. + FENV_EXCEPTIONS_TRAPPING_LIBM=-lm + ;; + *) + FENV_EXCEPTIONS_TRAPPING_LIBM= + ;; + esac + fi + if test $HAVE_FEENABLEEXCEPT = 0 || test $REPLACE_FEENABLEEXCEPT = 1; then + gl_PREREQ_FENV_EXCEPTIONS_TRAPPING + dnl Possibly need -lm for fpgetmask(), fpsetmask(). + if test $gl_cv_func_fpsetmask_no_libm = no \ + && test $gl_cv_func_fpsetmask_in_libm = yes \ + && test -z "$FENV_EXCEPTIONS_TRAPPING_LIBM"; then + FENV_EXCEPTIONS_TRAPPING_LIBM=-lm + fi + dnl Possibly need -lm for fegettrapenable(), fesettrapenable(). + if test $gl_cv_func_fesettrapenable_no_libm = no \ + && test $gl_cv_func_fesettrapenable_in_libm = yes \ + && test -z "$FENV_EXCEPTIONS_TRAPPING_LIBM"; then + FENV_EXCEPTIONS_TRAPPING_LIBM=-lm + fi + fi + AC_SUBST([FENV_EXCEPTIONS_TRAPPING_LIBM]) +]) + +dnl Prerequisites of lib/fenv-except-trapping.c. +AC_DEFUN([gl_PREREQ_FENV_EXCEPTIONS_TRAPPING], +[ + gl_MATHFUNC([fpsetmask], [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_fpsetmask_no_libm = yes \ + || test $gl_cv_func_fpsetmask_in_libm = yes; then + AC_DEFINE([HAVE_FPSETMASK], [1], + [Define to 1 if you have the 'fpsetmask' function.]) + fi + + gl_MATHFUNC([fesettrapenable], [int], [(int)], [#include <fenv.h>]) + if test $gl_cv_func_fesettrapenable_no_libm = yes \ + || test $gl_cv_func_fesettrapenable_in_libm = yes; then + AC_DEFINE([HAVE_FESETTRAPENABLE], [1], + [Define to 1 if you have the 'fesettrapenable' function.]) + fi +]) diff --git a/m4/fenv_h.m4 b/m4/fenv_h.m4 index 9e7c809d06..853f66e5a9 100644 --- a/m4/fenv_h.m4 +++ b/m4/fenv_h.m4 @@ -22,7 +22,8 @@ AC_DEFUN_ONCE([gl_FENV_H] dnl corresponding gnulib module is not in use, and which is not dnl guaranteed by C99. gl_WARN_ON_USE_PREPARE([[#include <fenv.h> - ]], [fedisableexcept feenableexcept fesetexcept fetestexceptflag]) + ]], [fedisableexcept feenableexcept fegetexcept fesetexcept + fetestexceptflag]) ]) # gl_FENV_MODULE_INDICATOR([modulename]) diff --git a/modules/fenv-exceptions-trapping b/modules/fenv-exceptions-trapping new file mode 100644 index 0000000000..7ce55fcbd6 --- /dev/null +++ b/modules/fenv-exceptions-trapping @@ -0,0 +1,40 @@ +Description: +Functions for turning floating-point exceptions into traps (signals): +feenableexcept, fedisableexcept, fegetexcept. + +Files: +lib/fenv-except-trapping.c +lib/fenv-private.h +m4/fenv-exceptions-trapping.m4 +m4/mathfunc.m4 +m4/musl.m4 + +Depends-on: +fenv +extensions + +configure.ac: +gl_FENV_EXCEPTIONS_TRAPPING +gl_CONDITIONAL([GL_COND_OBJ_FENV_EXCEPTIONS_TRAPPING], + [test $HAVE_FEENABLEEXCEPT = 0 || test $REPLACE_FEENABLEEXCEPT = 1]) +gl_FENV_MODULE_INDICATOR([feenableexcept]) +gl_FENV_MODULE_INDICATOR([fedisableexcept]) +gl_FENV_MODULE_INDICATOR([fegetexcept]) +gl_MODULE_INDICATOR([feenableexcept]) + +Makefile.am: +if GL_COND_OBJ_FENV_EXCEPTIONS_TRAPPING +lib_SOURCES += fenv-except-trapping.c +endif + +Include: +#include <fenv.h> + +Link: +$(FENV_EXCEPTIONS_TRAPPING_LIBM) + +License: +LGPLv2+ + +Maintainer: +all -- 2.34.1
>From e60ee371bb935823d18c98ad9cefe440eb4a43c7 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 31 Oct 2023 21:54:49 +0100 Subject: [PATCH 2/2] fenv-exceptions-trapping: Add tests. * tests/test-fenv-except-trapping-1.c: New file. * tests/test-fenv-except-trapping-2.sh: New file. * tests/test-fenv-except-trapping-2.c: New file. * modules/fenv-exceptions-trapping-tests: New file. --- ChangeLog | 6 + modules/fenv-exceptions-trapping-tests | 25 ++ tests/test-fenv-except-trapping-1.c | 69 ++++ tests/test-fenv-except-trapping-2.c | 488 +++++++++++++++++++++++++ tests/test-fenv-except-trapping-2.sh | 33 ++ 5 files changed, 621 insertions(+) create mode 100644 modules/fenv-exceptions-trapping-tests create mode 100644 tests/test-fenv-except-trapping-1.c create mode 100644 tests/test-fenv-except-trapping-2.c create mode 100755 tests/test-fenv-except-trapping-2.sh diff --git a/ChangeLog b/ChangeLog index f7d1a2237e..3b327c4c3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2023-10-31 Bruno Haible <br...@clisp.org> + fenv-exceptions-trapping: Add tests. + * tests/test-fenv-except-trapping-1.c: New file. + * tests/test-fenv-except-trapping-2.sh: New file. + * tests/test-fenv-except-trapping-2.c: New file. + * modules/fenv-exceptions-trapping-tests: New file. + fenv-exceptions-trapping: New module. * lib/fenv.in.h (feenableexcept, fedisableexcept, fegetexcept): New declarations. diff --git a/modules/fenv-exceptions-trapping-tests b/modules/fenv-exceptions-trapping-tests new file mode 100644 index 0000000000..21f178461f --- /dev/null +++ b/modules/fenv-exceptions-trapping-tests @@ -0,0 +1,25 @@ +Files: +tests/test-fenv-except-trapping-1.c +tests/test-fenv-except-trapping-2.sh +tests/test-fenv-except-trapping-2.c +tests/infinity.h +tests/macros.h +m4/musl.m4 + +Depends-on: +fenv-exceptions-tracking-c99 +nan +snan + +configure.ac: +gl_MUSL_LIBC + +Makefile.am: +TESTS += \ + test-fenv-except-trapping-1 \ + test-fenv-except-trapping-2.sh +check_PROGRAMS += \ + test-fenv-except-trapping-1 \ + test-fenv-except-trapping-2 +test_fenv_except_trapping_1_LDADD = $(LDADD) @FENV_EXCEPTIONS_TRAPPING_LIBM@ +test_fenv_except_trapping_2_LDADD = $(LDADD) @FENV_EXCEPTIONS_TRAPPING_LIBM@ @FENV_EXCEPTIONS_TRACKING_LIBM@ diff --git a/tests/test-fenv-except-trapping-1.c b/tests/test-fenv-except-trapping-1.c new file mode 100644 index 0000000000..7c67a06ec9 --- /dev/null +++ b/tests/test-fenv-except-trapping-1.c @@ -0,0 +1,69 @@ +/* Test of turning floating-point exceptions into traps (signals). + 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 "macros.h" + +/* Check of the Boole-an algebra. */ + +/* Converts 5 bits to an exceptions bit mask. */ +static int +uint_to_exceptions (unsigned int a) +{ + return (a & 0x01 ? FE_INVALID : 0) + | (a & 0x02 ? FE_DIVBYZERO : 0) + | (a & 0x04 ? FE_OVERFLOW : 0) + | (a & 0x08 ? FE_UNDERFLOW : 0) + | (a & 0x10 ? FE_INEXACT : 0); +} + +int +main () +{ + unsigned int a, b; + + /* Run through all possible valid arguments to feenableexcept and + fedisableexcept. */ + for (a = 0; a < 0x20; a++) + for (b = 0; b < 0x20; b++) + { + unsigned int c = a & ~b; + + if (fedisableexcept (FE_ALL_EXCEPT) == -1 + || feenableexcept (uint_to_exceptions (a)) == -1) + { + fputs ("Skipping test: trapping floating-point exceptions are not supported on this machine.\n", stderr); + return 77; + } + ASSERT (fedisableexcept (uint_to_exceptions (b)) + == uint_to_exceptions (a)); + /* Check fegetexcept. */ + ASSERT (fegetexcept () == uint_to_exceptions (c)); + /* Check the return value of feenableexcept. It should be consistent + with fegetexcept. */ + ASSERT (feenableexcept (0) == uint_to_exceptions (c)); + } + + return 0; +} diff --git a/tests/test-fenv-except-trapping-2.c b/tests/test-fenv-except-trapping-2.c new file mode 100644 index 0000000000..18f42b89c2 --- /dev/null +++ b/tests/test-fenv-except-trapping-2.c @@ -0,0 +1,488 @@ +/* Test of turning floating-point exceptions into traps (signals). + 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 "infinity.h" +#include "snan.h" +#include "macros.h" + +/* Operations that should raise various floating-point exceptions. */ + +/* For a list, see the glibc documentation + <https://www.gnu.org/software/libc/manual/html_node/FP-Exceptions.html> */ + +#define infinityf my_infinityf /* Avoid collision with Cygwin's <math.h> */ +static float volatile infinityf, qnanf; +static double volatile infinityd, qnand; +static long double volatile infinityl, qnanl; + +static void +raise_invalid_addition (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = infinityf; b = - infinityf; + c = a + b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = infinityd; b = - infinityd; + c = a + b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = infinityl; b = - infinityl; + c = a + b; + } + break; + } +} + +static void +raise_invalid_multiplication (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = infinityf; b = 0.0f; + c = a * b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = infinityd; b = 0.0; + c = a * b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = infinityl; b = 0.0L; + c = a * b; + } + break; + } +} + +static void +raise_invalid_division (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = infinityf; b = - infinityf; + c = a / b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = infinityd; b = - infinityd; + c = a / b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = infinityl; b = - infinityl; + c = a / b; + } + break; + } +} + +static void +raise_invalid_comparison (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED int volatile c; + a = qnanf; b = qnanf; + c = a > b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED int volatile c; + a = qnand; b = qnand; + c = a > b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED int volatile c; + a = qnanl; b = qnanl; + c = a > b; + } + break; + } +} + +static void +raise_invalid_snan (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = SNaNf (); b = 1.0f; + c = a + b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = SNaNd (); b = 1.0; + c = a + b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = SNaNl (); b = 1.0L; + c = a + b; + } + break; + } +} + +static void +raise_divbyzero (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = 2.5f; b = - 0.0f; + c = a / b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 2.5; b = - 0.0; + c = a / b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = 2.5L; b = - 0.0L; + c = a / b; + } + break; + } +} + +static void +raise_overflow (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = 1e20f; b = 1e30f; + c = a * b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 1e160; b = 1e260; + c = a * b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = 1e200L; b = 1e300L; + c = a * b; + c = c * c; + c = c * c; + c = c * c; + c = c * c; + } + break; + } +} + +static void +raise_underflow (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = 1e-20f; b = 1e-30f; + c = a * b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 1e-160; b = 1e-260; + c = a * b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = 1e-200L; b = 1e-300L; + c = a * b; + c = c * c; + c = c * c; + c = c * c; + c = c * c; + } + break; + } +} + +static void +raise_inexact (char type) +{ + switch (type) + { + case 'f': + { + float volatile a, b; + _GL_UNUSED float volatile c; + a = 2.5f; b = 3.0f; + c = a / b; + } + break; + case 'd': + { + double volatile a, b; + _GL_UNUSED double volatile c; + a = 2.5; b = 3.0; + c = a / b; + } + break; + case 'l': + { + long double volatile a, b; + _GL_UNUSED long double volatile c; + a = 2.5L; b = 3.0L; + c = a / b; + } + break; + } +} + +int +main (int argc, char *argv[]) +{ + if (argc > 3) + { + const char *operation_arg = argv[1]; + const char *procedure_arg = argv[2]; + const char *type_arg = argv[3]; + + void (*operation) (char) = NULL; + int expected_exceptions = 0; + int possible_exceptions = 0; + + /* Preparations (to be executed before we call feenableexcept). */ + infinityf = Infinityf (); + qnanf = NaNf (); + infinityd = Infinityd (); + qnand = NaNd (); + infinityl = Infinityl (); + qnanl = NaNl (); + feclearexcept (FE_ALL_EXCEPT); + + /* Analyze the operation argument. */ + switch (operation_arg[0]) + { + case '1': + operation = raise_invalid_addition; + expected_exceptions = FE_INVALID; + break; + case '2': + operation = raise_invalid_multiplication; + expected_exceptions = FE_INVALID; + break; + case '3': + operation = raise_invalid_division; + expected_exceptions = FE_INVALID; + break; + case '4': + operation = raise_invalid_comparison; + expected_exceptions = FE_INVALID; + break; + case '5': + operation = raise_invalid_snan; + expected_exceptions = FE_INVALID; + break; + case '6': + operation = raise_divbyzero; + expected_exceptions = FE_DIVBYZERO; + break; + case '7': + operation = raise_overflow; + expected_exceptions = FE_OVERFLOW; + possible_exceptions = FE_INEXACT; + break; + case '8': + operation = raise_underflow; + expected_exceptions = FE_UNDERFLOW; + possible_exceptions = FE_INEXACT; + break; + case '9': + operation = raise_inexact; + expected_exceptions = FE_INEXACT; + break; + } + + /* Analyze the procedure argument. */ + switch (procedure_arg[0]) + { + /* These three procedures should lead to a trap. */ + case 'p': + if (feenableexcept (expected_exceptions) == -1) + goto skip; + break; + case 'q': + if (feenableexcept (FE_ALL_EXCEPT) == -1) + goto skip; + break; + case 'r': + if (feenableexcept (FE_ALL_EXCEPT) == -1) + goto skip; + ASSERT (fedisableexcept (FE_ALL_EXCEPT & ~expected_exceptions) + == FE_ALL_EXCEPT); + break; + /* This procedure should *not* lead to a trap. */ + case 's': + if (feenableexcept (FE_ALL_EXCEPT & ~(expected_exceptions | possible_exceptions)) == -1) + goto skip; + break; + } + + /* Avoid known test failures. */ + int known_failure = 0; + /* The '4' tests do not work on Linux/powerpc* and Linux/s390*, as well as + on GNU/kFreeBSD/i386, GNU/kFreeBSD/x86_64, musl libc/i386, + musl libc/powerpc64le, macOS/i386, macOS/x86_64, macOS/arm64, + FreeBSD/i386, FreeBSD/x86_64, NetBSD/i386, NetBSD/x86_64, OpenBSD/i386, + OpenBSD/x86_64, AIX/powerpc, Solaris/i386, Solaris/x86_64, + Cygwin/x86_64, native Windows/i386, + native Windows/x86_64. */ + #if (__GLIBC__ >= 2 && (defined __powerpc__ || (defined __s390__ || defined __s390x__))) \ + || (__GLIBC__ >= 2 && __FreeBSD_kernel__ && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86))) \ + || (defined MUSL_LIBC && ((defined __i386 || defined _M_IX86) || defined __powerpc__)) \ + || ((defined __APPLE__ && defined __MACH__) && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86) || defined __aarch64__)) \ + || ((defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__) && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86))) \ + || (defined _AIX && defined __powerpc__) \ + || (defined __sun && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86))) \ + || (defined __CYGWIN__ && (defined __x86_64__ || defined _M_X64)) \ + || (defined _WIN32 && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86))) + known_failure |= (operation_arg[0] == '4'); + #endif + /* The '7' and '8' tests, with types 'f' and 'd', do not work reliably + on Linux/i386. */ + #if defined __i386 || defined _M_IX86 + known_failure |= (operation_arg[0] == '7' || operation_arg[0] == '8'); + #endif + /* The '9' tests do not work on Linux/alpha, musl libc/powerpc64le, + AIX/powerpc. */ + #if (__GLIBC__ >= 2 && defined __alpha) \ + || ((defined MUSL_LIBC || defined _AIX) && defined __powerpc__) + known_failure |= (operation_arg[0] == '9'); + #endif + /* The 'l' tests do 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) + known_failure |= (type_arg[0] == 'l'); + #endif + if (known_failure) + { + fputs ("Skipping test: known failure on this platform\n", stderr); + return 77; + } + + /* Analyze the type argument. */ + switch (type_arg[0]) + { + case 'f': + case 'd': + case 'l': + operation (type_arg[0]); + break; + } + } + + return 0; + + skip: + fputs ("Skipping test: trapping floating-point exceptions are not supported on this machine.\n", stderr); + return 77; +} diff --git a/tests/test-fenv-except-trapping-2.sh b/tests/test-fenv-except-trapping-2.sh new file mode 100755 index 0000000000..d51197feb9 --- /dev/null +++ b/tests/test-fenv-except-trapping-2.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# Test of turning floating-point exceptions into traps (signals). + +final_rc=0 + +for operation in 1 2 3 4 5 6 7 8 9; do + for procedure in p q r s; do + for type in f d l; do + ${CHECKER} ./test-fenv-except-trapping-2${EXEEXT} $operation $procedure $type + rc=$? + if test $rc = 77; then + if test $final_rc = 0; then + final_rc=77 + fi + else + if test $procedure = s; then + if test $rc != 0; then + echo "Failed (got a trap): ./test-fenv-except-trapping-2 $operation $procedure $type" 1>&2 + final_rc=1 + fi + else + if test $rc = 0; then + echo "Failed (got no trap): ./test-fenv-except-trapping-2 $operation $procedure $type" 1>&2 + final_rc=1 + fi + fi + fi + done + done +done + +exit $final_rc -- 2.34.1