Here comes the module 'fenv-environment', which implements the ISO C 99 functions fegetenv feholdexcept fesetenv feupdateenv and the macro FE_DFL_ENV.
This is the largest module in the fenv-* series, because of the massive amount of platform bugs that need to be worked around. No operating system gets all four functions right on all CPUs! On many platforms, there's even two or three bugs to work around together. Really, it feels like I'm the first person in the world who has written unit tests for these functions. Additionally, it has complexity because the fenv_t type is dependent on OS and architecture. But fortunately, for most architectures, it is merely binary- equivalent to an 'unsigned int' or 'unsigned long'. 2023-11-05 Bruno Haible <br...@clisp.org> fenv-environment: Add tests. * tests/test-fenv-env-1.c: New file. * tests/test-fenv-env-2.c: New file. * tests/test-fenv-env-3.c: New file. * tests/test-fenv-env-4.c: New file. * tests/test-fenv-env-5.sh: New file. * tests/test-fenv-env-5.c: New file. * modules/fenv-environment-tests: New file. fenv-environment: New module. * lib/fenv.in.h (fenv_t) [hppa]: Remove the __exception field. (FE_DFL_ENV): Override if <fenv.h> exists but HAVE_FE_DFL_ENV is not defined. (fegetenv, fesetenv, feupdateenv, feholdexcept): New declarations. * lib/fenv-env.c: New file, based on glibc. * lib/fenv-env-hold.c: New file. * lib/fenv-env-update.c: New file. * m4/fenv-environment.m4: New file. * m4/mathfunc.m4 (gl_MATHFUNC): Handle also the 'fenv_t *' type. * m4/fenv_h.m4 (gl_FENV_H_DEFAULTS): Initialize REPLACE_FEGETENV, REPLACE_FEHOLDEXCEPT, REPLACE_FESETENV, REPLACE_FEUPDATEENV. * modules/fenv (Makefile.am): Substitute REPLACE_FEGETENV, REPLACE_FEHOLDEXCEPT, REPLACE_FESETENV, REPLACE_FEUPDATEENV. * modules/fenv-environment: New file. * doc/posix-functions/fegetenv.texi: Mention the new module and the bugs on glibc, macOS, AIX. * doc/posix-functions/feholdexcept.texi: Mention the new module and the bugs on glibc, musl libc, FreeBSD, AIX, mingw, MSVC. * doc/posix-functions/fesetenv.texi: Mention the new module and the bugs on musl libc, FreeBSD, NetBSD, AIX, Solaris, Cygwin, mingw, MSVC. * doc/posix-functions/feupdateenv.texi: Mention the new module and the bugs on glibc, musl libc, macOS, FreeBSD, AIX, Solaris, mingw, MSVC.
From c19cabc0bf5d8e1afec9fd50fee47104000fa3e4 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sun, 5 Nov 2023 15:00:29 +0100 Subject: [PATCH 1/2] fenv-environment: New module. * lib/fenv.in.h (fenv_t) [hppa]: Remove the __exception field. (FE_DFL_ENV): Override if <fenv.h> exists but HAVE_FE_DFL_ENV is not defined. (fegetenv, fesetenv, feupdateenv, feholdexcept): New declarations. * lib/fenv-env.c: New file, based on glibc. * lib/fenv-env-hold.c: New file. * lib/fenv-env-update.c: New file. * m4/fenv-environment.m4: New file. * m4/mathfunc.m4 (gl_MATHFUNC): Handle also the 'fenv_t *' type. * m4/fenv_h.m4 (gl_FENV_H_DEFAULTS): Initialize REPLACE_FEGETENV, REPLACE_FEHOLDEXCEPT, REPLACE_FESETENV, REPLACE_FEUPDATEENV. * modules/fenv (Makefile.am): Substitute REPLACE_FEGETENV, REPLACE_FEHOLDEXCEPT, REPLACE_FESETENV, REPLACE_FEUPDATEENV. * modules/fenv-environment: New file. * doc/posix-functions/fegetenv.texi: Mention the new module and the bugs on glibc, macOS, AIX. * doc/posix-functions/feholdexcept.texi: Mention the new module and the bugs on glibc, musl libc, FreeBSD, AIX, mingw, MSVC. * doc/posix-functions/fesetenv.texi: Mention the new module and the bugs on musl libc, FreeBSD, NetBSD, AIX, Solaris, Cygwin, mingw, MSVC. * doc/posix-functions/feupdateenv.texi: Mention the new module and the bugs on glibc, musl libc, macOS, FreeBSD, AIX, Solaris, mingw, MSVC. --- ChangeLog | 26 + doc/posix-functions/fegetenv.texi | 16 +- doc/posix-functions/feholdexcept.texi | 25 +- doc/posix-functions/fesetenv.texi | 25 +- doc/posix-functions/feupdateenv.texi | 35 +- lib/fenv-env-hold.c | 32 + lib/fenv-env-update.c | 31 + lib/fenv-env.c | 1165 +++++++++++++++++++++++++ lib/fenv.in.h | 91 +- m4/fenv-environment.m4 | 515 +++++++++++ m4/fenv_h.m4 | 6 +- m4/mathfunc.m4 | 6 +- modules/fenv | 4 + modules/fenv-environment | 56 ++ 14 files changed, 2013 insertions(+), 20 deletions(-) create mode 100644 lib/fenv-env-hold.c create mode 100644 lib/fenv-env-update.c create mode 100644 lib/fenv-env.c create mode 100644 m4/fenv-environment.m4 create mode 100644 modules/fenv-environment diff --git a/ChangeLog b/ChangeLog index 7c2d58913d..18ee7639c2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2023-11-05 Bruno Haible <br...@clisp.org> + + fenv-environment: New module. + * lib/fenv.in.h (fenv_t) [hppa]: Remove the __exception field. + (FE_DFL_ENV): Override if <fenv.h> exists but HAVE_FE_DFL_ENV is not + defined. + (fegetenv, fesetenv, feupdateenv, feholdexcept): New declarations. + * lib/fenv-env.c: New file, based on glibc. + * lib/fenv-env-hold.c: New file. + * lib/fenv-env-update.c: New file. + * m4/fenv-environment.m4: New file. + * m4/mathfunc.m4 (gl_MATHFUNC): Handle also the 'fenv_t *' type. + * m4/fenv_h.m4 (gl_FENV_H_DEFAULTS): Initialize REPLACE_FEGETENV, + REPLACE_FEHOLDEXCEPT, REPLACE_FESETENV, REPLACE_FEUPDATEENV. + * modules/fenv (Makefile.am): Substitute REPLACE_FEGETENV, + REPLACE_FEHOLDEXCEPT, REPLACE_FESETENV, REPLACE_FEUPDATEENV. + * modules/fenv-environment: New file. + * doc/posix-functions/fegetenv.texi: Mention the new module and the bugs + on glibc, macOS, AIX. + * doc/posix-functions/feholdexcept.texi: Mention the new module and the + bugs on glibc, musl libc, FreeBSD, AIX, mingw, MSVC. + * doc/posix-functions/fesetenv.texi: Mention the new module and the bugs + on musl libc, FreeBSD, NetBSD, AIX, Solaris, Cygwin, mingw, MSVC. + * doc/posix-functions/feupdateenv.texi: Mention the new module and the + bugs on glibc, musl libc, macOS, FreeBSD, AIX, Solaris, mingw, MSVC. + 2023-11-05 Bruno Haible <br...@clisp.org> fenv-exceptions-tracking-c99 tests: Enhance tests. diff --git a/doc/posix-functions/fegetenv.texi b/doc/posix-functions/fegetenv.texi index 99d8d5250c..4bde34b5aa 100644 --- a/doc/posix-functions/fegetenv.texi +++ b/doc/posix-functions/fegetenv.texi @@ -4,15 +4,23 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/fegetenv.html} -Gnulib module: --- +Gnulib module: fenv-environment 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 clears all exception trap bits on some platforms: +@c https://sourceware.org/bugzilla/show_bug.cgi?id=16198 +glibc 2.19/x86_64, +Mac OS X 10.5/i386, Mac OS X 10.5/x86_64. +@item +This function does not save the exception trap bits on some platforms: +AIX 7.3. @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 9, Android 4.4. @end itemize diff --git a/doc/posix-functions/feholdexcept.texi b/doc/posix-functions/feholdexcept.texi index 52d8219c22..dcd99a2f92 100644 --- a/doc/posix-functions/feholdexcept.texi +++ b/doc/posix-functions/feholdexcept.texi @@ -4,15 +4,32 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/feholdexcept.html} -Gnulib module: --- +Gnulib module: fenv-environment Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +FreeBSD 5.2.1, NetBSD 5.0, OpenBSD 3.8, AIX 5.1, IRIX 6.5, Solaris 9, Cygwin 1.7.7, MSVC 9, Android 4.4. +@item +This function does not work on some platforms: +@c https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a8c79c4088e8c04e4297936efa0dee6c8e6e974d +glibc 2.5, +@c https://cgit.freebsd.org/src/commit/?id=34cc08e336987a8ebc316595e3f552a4c09f1fd4 +FreeBSD 12.2/arm64. +@item +This function does not save the exception trap bits on some platforms: +AIX 7.3. +@item +This function does not clear the exception trap bits on some platforms: +musl libc, mingw. +@item +This function may fail on some platforms: +MSVC 14. +@item + @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, 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/fesetenv.texi b/doc/posix-functions/fesetenv.texi index 7a74beaad1..f7d45cf543 100644 --- a/doc/posix-functions/fesetenv.texi +++ b/doc/posix-functions/fesetenv.texi @@ -4,15 +4,32 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/fesetenv.html} -Gnulib module: --- +Gnulib module: fenv-environment 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. +@item +This function does not work on some platforms: +@c https://cgit.freebsd.org/src/commit/?id=34cc08e336987a8ebc316595e3f552a4c09f1fd4 +FreeBSD 12.2/arm64. +@item +This function does not restore the floating-point exception trap bits +on some platforms: +musl libc/i386, musl libc/x86_64, AIX 7.3, Solaris 10 and 11, MSVC 14. +@item +The macro @code{FE_DFL_ENV} cannot be used because it leads to a link error +on some platforms: +NetBSD 9.3/hppa, NetBSD 9.3/sparc, Cygwin 2.9.0. +@item +This function, when called with @code{FE_DFL_ENV} argument, has no effect +on the x86 @code{mxcsr} register and thus on floating-point operations +performed in the SSE unit on some platforms: +mingw 10. @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/feupdateenv.texi b/doc/posix-functions/feupdateenv.texi index 4bcbf5cfd8..bb844ec2f1 100644 --- a/doc/posix-functions/feupdateenv.texi +++ b/doc/posix-functions/feupdateenv.texi @@ -4,15 +4,42 @@ POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/feupdateenv.html} -Gnulib module: --- +Gnulib module: fenv-environment 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 14, Android 4.4. +@item +This function crashes when given the argument @code{FE_DFL_ENV} +on some platforms: +@c https://sourceware.org/bugzilla/show_bug.cgi?id=31022 +glibc 2.37/riscv64. +@item +This function does not work on some platforms: +@c https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a8c79c4088e8c04e4297936efa0dee6c8e6e974d +glibc 2.5/ia64, +@c https://cgit.freebsd.org/src/commit/?id=34cc08e336987a8ebc316595e3f552a4c09f1fd4 +FreeBSD 12.2/arm64. +@item +This function forgets about the currently set floating-point exception flags +on some platforms: +Mac OS X 10.5/i386, Mac OS X 10.5/x86_64. +@item +This function does not restore the floating-point exception trap bits +on some platforms: +musl libc/i386, musl libc/x86_64, AIX 7.3, Solaris 10 and 11, mingw 10. +@item +This function does not trigger traps on +@c https://sourceware.org/bugzilla/show_bug.cgi?id=31023 +glibc 2.37/hppa, +musl libc/s390x. +@item +This function may fail on some platforms: +MSVC 14. @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/lib/fenv-env-hold.c b/lib/fenv-env-hold.c new file mode 100644 index 0000000000..ab156d5e74 --- /dev/null +++ b/lib/fenv-env-hold.c @@ -0,0 +1,32 @@ +/* Functions for controlling the floating-point environment as a whole. + 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/>. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +int +feholdexcept (fenv_t *envp) +{ + if (fegetenv (envp)) + return -1; + if (feclearexcept (FE_ALL_EXCEPT)) + return -1; + if (fedisableexcept (FE_ALL_EXCEPT) == -1) + return -1; + return 0; +} diff --git a/lib/fenv-env-update.c b/lib/fenv-env-update.c new file mode 100644 index 0000000000..876c223409 --- /dev/null +++ b/lib/fenv-env-update.c @@ -0,0 +1,31 @@ +/* Functions for controlling the floating-point environment as a whole. + 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/>. */ + +#include <config.h> + +/* Specification. */ +#include <fenv.h> + +int +feupdateenv (fenv_t const *envp) +{ + int previously_flagged_exceptions = fetestexcept (FE_ALL_EXCEPT); + if (fesetenv (envp)) + return -1; + if (feraiseexcept (previously_flagged_exceptions)) + return -1; + return 0; +} diff --git a/lib/fenv-env.c b/lib/fenv-env.c new file mode 100644 index 0000000000..3e97dc3e65 --- /dev/null +++ b/lib/fenv-env.c @@ -0,0 +1,1165 @@ +/* Functions for controlling the floating-point environment as a whole. + 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>/{fegetenv.c,fesetenv.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" +#include "verify.h" + +/* The big problem of the fegetenv/fesetenv API is that it is not well-defined + which parts of the floating-point environment need to be restored by fesetenv + and which don't. For example: + - On x86_64 and i386, some platforms restore all the x86_387_fenv_t, + whereas others restore only the control word and the status words. + - On m68k, musl libc restores the fpiar register value, whereas glibc + doesn't. + Some platforms don't even do this consistently. For example, musl libc on + sh4 restores the precision mode bit (fpscr bit 19) during an fesetenv of a + previously saved environment, but not during an fesetenv (FE_DFL_ENV) + invocation. */ + +#if defined _AIX && defined __powerpc__ /* AIX */ + +/* On AIX, fenv_t is a struct { unsigned short rmode; unsigned int fpstat, trapstate; }. */ + +/* On AIX, the register fpscr is augmented with a 32-bit word named fpscrx + in thread-local storage. Therefore AIX does complex things behind the scene, + and its best to use the system-provided fegetenv() and fesetenv() functions + and just add the missing behaviour, namely saving and restoring the exception + trap bits. */ + +# include <float.h> +# include <fpxcp.h> + +# include <fptrap.h> + +int +fegetenv (fenv_t *envp) +#undef fegetenv +{ + /* Invoke the original fegetenv(), and additionally save the exception trap + bits (fpscr bits 7..3). */ + union { unsigned long long u; double f; } memenv; + _FPU_GETCW_AS_DOUBLE (memenv.f); + fegetenv (envp); + /* AIX's original fegetenv() sets envp->trapstate = 0. */ + envp->trapstate = FE_ALL_EXCEPT & ((unsigned int) memenv.u << 22); + return 0; +} + +int +fesetenv (fenv_t const *envp) +#undef fesetenv +{ + /* Invoke the original fesetenv(), and additionally restore the exception trap + bits (fpscr bits 7..3). */ + if (fesetenv (envp) != 0) + return -1; + union { unsigned long long u; double f; } orig_memenv, memenv; + _FPU_GETCW_AS_DOUBLE (orig_memenv.f); + unsigned int trapbits; + if (envp == FE_DFL_ENV) + trapbits = 0; + else + trapbits = (envp->trapstate & FE_ALL_EXCEPT) >> 22; + memenv.u = (orig_memenv.u & ~0x000000f8) | trapbits; + if (!(memenv.u == orig_memenv.u)) + { + if ((orig_memenv.u & 0x000000f8) == 0 && (memenv.u & 0x000000f8) != 0) + { + /* Put the thread into precise trapping mode. */ + /* Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine> */ + fp_trap (FP_TRAP_SYNC); + } + _FPU_SETCW_AS_DOUBLE (memenv.f); + if ((orig_memenv.u & 0x000000f8) != 0 && (memenv.u & 0x000000f8) == 0) + { + /* Put the thread into no-trapping mode. */ + /* Documentation: <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-trap-subroutine> */ + fp_trap (FP_TRAP_OFF); + } + } + return 0; +} + +#elif defined __GNUC__ || defined __clang__ || defined _MSC_VER + +# if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86) + +/* On all OSes except MSVC, macOS, Solaris, fenv_t is binary-equivalent to + - either a x86_387_fenv_t (7 'unsigned int' words) + where mxcsr is stored: + - for glibc/i386: in __eip = more[1]. + - for musl libc/i386: the lower 6 bits ORed into __status_word. + - for FreeBSD/i386, Haiku/i386, mingw/i386: + the upper 16 bits in __reserved1, the lower 16 bits in __reserved2. + - or a struct { x86_387_fenv_t 387; unsigned int mxcsr; } + On MSVC, it's a + struct { unsigned int _Fe_ctl, _Fe_stat; }, where + _Fe_ctl bits 9..8 are the rounding direction, + _Fe_ctl bits 4..0 are the exception trap bits (inverted), + _Fe_stat bits 4..0 are the exception flags. + On macOS, it's a + struct { unsigned short __control, __status; unsigned int __mxcsr; ... }. + On Solaris, it's a + struct { __fex_handler_t __handlers; unsigned long __fsr; }. */ + +/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL. */ +# define _FE_ALL_EXCEPT (0x3F * FE_INVALID) + +int +fegetenv (fenv_t *envp) +{ +# if defined _MSC_VER + + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + envp->_Fe_ctl = ((mxcsr & 0x6000) >> 5) /* rounding direction */ + /* exception trap bits (inverted): */ + | (mxcsr & (1 << 7) ? FE_INVALID : 0) + | (mxcsr & (1 << 9) ? FE_DIVBYZERO : 0) + | (mxcsr & (1 << 10) ? FE_OVERFLOW : 0) + | (mxcsr & (1 << 11) ? FE_UNDERFLOW : 0) + | (mxcsr & (1 << 12) ? FE_INEXACT : 0); + envp->_Fe_stat = /* exception flags: */ + (mxcsr & (1 << 0) ? FE_INVALID : 0) + | (mxcsr & (1 << 2) ? FE_DIVBYZERO : 0) + | (mxcsr & (1 << 3) ? FE_OVERFLOW : 0) + | (mxcsr & (1 << 4) ? FE_UNDERFLOW : 0) + | (mxcsr & (1 << 5) ? FE_INEXACT : 0); + +# elif defined __APPLE__ && defined __MACH__ /* macOS */ + + _FPU_GETCW (envp->__control); + _FPU_GETSTAT (envp->__status); + _FPU_GETSSECW (envp->__mxcsr); + +# elif defined __sun /* Solaris */ + + fex_getexcepthandler (&envp->__handlers, FEX_ALL); + + unsigned short fctrl; + unsigned short fstat; + unsigned int mxcsr; + _FPU_GETCW (fctrl); + _FPU_GETSTAT (fstat); + _FPU_GETSSECW (mxcsr); + envp->__fsr = + (((unsigned int) (fctrl ^ 0x3F) & ~0xE0C0U) << 16) + | ((unsigned int) fstat | (mxcsr & 0x3F)); + +# else + + verify (sizeof (fenv_t) >= sizeof (x86_387_fenv_t)); + + __asm__ __volatile__ ("fnstenv %0" : "=m" (* (x86_387_fenv_t *) envp)); + /* Note: fnstenv masks all floating-point exceptions until the fldcw + below. */ + __asm__ __volatile__ ("fldcw %0" : : "m" (((x86_387_fenv_t *) envp)->__control_word)); + + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + if (sizeof (fenv_t) > sizeof (x86_387_fenv_t)) + { + /* Store mxcsr in the struct field after the x86_387_fenv_t. */ + * (unsigned int *) ((x86_387_fenv_t *) envp + 1) = mxcsr; + } + else + { +# if defined __GLIBC__ + ((x86_387_fenv_t *) envp)->more[1] = mxcsr; +# elif MUSL_LIBC + ((x86_387_fenv_t *) envp)->__status_word |= mxcsr & 0x3F; +# else + ((x86_387_fenv_t *) envp)->__reserved1 = mxcsr >> 16; + ((x86_387_fenv_t *) envp)->__reserved2 = mxcsr & 0xFFFF; +# endif + } + +# endif + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ +# if defined _MSC_VER + + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + mxcsr &= ~(0x6000 | (0x3F << 7) | 0x3F); + /* On MSVC, FE_DFL_ENV is the address of a global variable. + Let's ignore this variable. */ + if (envp == FE_DFL_ENV) + /* Default: rounding direction = FE_TONEAREST, exception trap bits = 0, + exception flags = 0. */ + mxcsr = mxcsr | (0x3F << 7); + else + mxcsr = mxcsr + | ((envp->_Fe_ctl & 0x300) << 5) /* rounding direction */ + /* exception trap bits (inverted): */ + | (envp->_Fe_ctl & FE_INVALID ? 1 << 7 : 0) + | (envp->_Fe_ctl & FE_DIVBYZERO ? 1 << 9 : 0) + | (envp->_Fe_ctl & FE_OVERFLOW ? 1 << 10 : 0) + | (envp->_Fe_ctl & FE_UNDERFLOW ? 1 << 11 : 0) + | (envp->_Fe_ctl & FE_INEXACT ? 1 << 12 : 0) + /* exception flags: */ + | (envp->_Fe_stat & FE_INVALID ? 1 << 0 : 0) + | (envp->_Fe_stat & FE_DIVBYZERO ? 1 << 2 : 0) + | (envp->_Fe_stat & FE_OVERFLOW ? 1 << 3 : 0) + | (envp->_Fe_stat & FE_UNDERFLOW ? 1 << 4 : 0) + | (envp->_Fe_stat & FE_INEXACT ? 1 << 5 : 0); + _FPU_SETSSECW (mxcsr); + +# elif defined __APPLE__ && defined __MACH__ /* macOS */ + + unsigned short fctrl; + unsigned short fstat; + unsigned int mxcsr; + /* On macOS, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + { + fctrl = 0x3F; + fstat = 0; + mxcsr = 0x3F << 7; + } + else + { + fctrl = envp->__control; + fstat = envp->__status; + mxcsr = envp->__mxcsr; + } + /* In the SSE unit. */ + _FPU_SETSSECW (mxcsr); + + /* In the 387 unit. */ + x86_387_fenv_t env387; + __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env387)); + /* Note: fnstenv masks all floating-point exceptions until the fldenv + below. */ + env387.__control_word = fctrl; + env387.__status_word = fstat; + __asm__ __volatile__ ("fldenv %0" : : "m" (*&env387)); + +# elif defined __sun /* Solaris */ + + unsigned short env_fctrl; + unsigned int env_mxcsr; + /* On Solaris, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + { + env_fctrl = 0x3F; + env_mxcsr = 0x3F << 7; + } + else + { + fex_setexcepthandler (&envp->__handlers, FEX_ALL); + env_fctrl = (envp->__fsr >> 16) ^ 0x3F; + env_mxcsr = envp->__fsr & 0x3F; + } + + /* Store the exception flags in mxcsr, not in env387.__status_word. */ + + /* In the SSE unit. */ + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + mxcsr = (mxcsr & ~0x3F) | env_mxcsr; + _FPU_SETSSECW (mxcsr); + + /* In the 387 unit. */ + x86_387_fenv_t env387; + __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env387)); + /* Note: fnstenv masks all floating-point exceptions until the fldenv + below. */ + env387.__control_word = env_fctrl; + env387.__status_word &= ~0x3F; + __asm__ __volatile__ ("fldenv %0" : : "m" (*&env387)); + +# else + + verify (sizeof (fenv_t) >= sizeof (x86_387_fenv_t)); + + unsigned short env_fctrl; + unsigned short env_fstat; + unsigned int env_mxcsr; + /* On *BSD, Solaris, Cygwin, Android, Haiku, Minix, FE_DFL_ENV is the address + of a global variable; no special code is needed in this case. + But on mingw, FE_DFL_ENV is NULL. */ + if (envp == FE_DFL_ENV) + { + env_fctrl = 0x3F; + env_fstat = 0; + env_mxcsr = 0x3F << 7; + } + else + { + env_fctrl = ((x86_387_fenv_t const *) envp)->__control_word; + env_fstat = ((x86_387_fenv_t const *) envp)->__status_word; + if (sizeof (fenv_t) > sizeof (x86_387_fenv_t)) + env_mxcsr = * (unsigned int const *) ((x86_387_fenv_t const *) envp + 1); + else + { +# if defined __GLIBC__ + env_mxcsr = ((x86_387_fenv_t const *) envp)->more[1]; +# elif MUSL_LIBC + env_mxcsr = env_fstat & 0x3F; +# else + env_mxcsr = (((x86_387_fenv_t const *) envp)->__reserved1 << 16) + | ((x86_387_fenv_t const *) envp)->__reserved2; +# endif + } + } + + /* In the SSE unit. */ + unsigned int mxcsr = env_mxcsr; + _FPU_SETSSECW (mxcsr); + + /* In the 387 unit. */ + x86_387_fenv_t env387; + __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env387)); + /* Note: fnstenv masks all floating-point exceptions until the fldenv + below. */ + env387.__control_word = env_fctrl; + env387.__status_word = env_fstat; + __asm__ __volatile__ ("fldenv %0" : : "m" (*&env387)); + +# endif + + return 0; +} + +# elif defined __aarch64__ /* arm64 */ + +/* On all OSes except FreeBSD and macOS, fenv_t is binary-equivalent to a + struct { unsigned int fpcr, fpsr; }. + On FreeBSD, it's binary-equivalent to a + struct { unsigned int fpsr, fpcr; }, i.e. the fields are swapped. + On macOS, it's binary-equivalent to a + struct { unsigned long fpsr, fpcr; }. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned long fpcr; + unsigned long fpsr; + _FPU_GETCW (fpcr); + _FPU_GETFPSR (fpsr); +# if defined __APPLE__ && defined __MACH__ + ((unsigned long *) envp)[0] = fpsr; + ((unsigned long *) envp)[1] = fpcr; +# elif defined __FreeBSD__ + ((unsigned int *) envp)[0] = fpsr; + ((unsigned int *) envp)[1] = fpcr; +# else + ((unsigned int *) envp)[0] = fpcr; + ((unsigned int *) envp)[1] = fpsr; +# endif + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned long orig_fpcr, fpcr; + unsigned long orig_fpsr, fpsr; + _FPU_GETCW (orig_fpcr); + _FPU_GETFPSR (orig_fpsr); + /* On *BSD and Android, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + { + /* Default: rounding direction = FE_TONEAREST, exceptions trap bits = 0. */ + fpcr = (orig_fpcr & 0xFE0FE0F8U) | 0x00000000U; + /* Default: exception flags = 0. */ + fpsr = (orig_fpsr & 0x0FFFFFE0U) | 0x00000000U; + } + else + { +# if defined __APPLE__ && defined __MACH__ + fpsr = ((unsigned long const *) envp)[0]; + fpcr = ((unsigned long const *) envp)[1]; +# elif defined __FreeBSD__ + fpsr = ((unsigned int const *) envp)[0]; + fpcr = ((unsigned int const *) envp)[1]; +# else + fpcr = ((unsigned int const *) envp)[0]; + fpsr = ((unsigned int const *) envp)[1]; +# endif + } + if (fpcr != orig_fpcr) + _FPU_SETCW (fpcr); + if (fpsr != orig_fpsr) + _FPU_SETFPSR (fpsr); + return 0; +} + +# elif defined __arm__ + +/* On all OSes except OpenBSD, fenv_t is binary-equivalent to an 'unsigned int'. + On OpenBSD, it's a struct { unsigned int __sticky, __mask, __round; }. */ + +int +fegetenv (fenv_t *envp) +{ +# ifdef __SOFTFP__ + return -1; +# else + unsigned int fpscr; + _FPU_GETCW (fpscr); +# if defined __OpenBSD__ + envp->__sticky = fpscr & FE_ALL_EXCEPT; + envp->__mask = (fpscr >> 8) & FE_ALL_EXCEPT; + envp->__round = (fpscr >> 22) & 3; +# else + * (unsigned int *) envp = fpscr; +# endif + return 0; +# endif +} + +int +fesetenv (fenv_t const *envp) +{ +# ifdef __SOFTFP__ + return -1; +# else + unsigned int fpscr, orig_fpscr; + _FPU_GETCW (orig_fpscr); + /* On *BSD and Android, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + /* Default: rounding direction = FE_TONEAREST, exception trap bits = 0, + exception flags = 0. */ + fpscr = (orig_fpscr & 0x00086060) | 0x00000000; + else + /* Here, glibc ignores orig_fpscr entirely... */ + fpscr = (orig_fpscr & ~((3U << 22) | (FE_ALL_EXCEPT << 8) | FE_ALL_EXCEPT)) +# if defined __OpenBSD__ + | ((envp->__round & 3U) << 22) + | ((envp->__mask & FE_ALL_EXCEPT) << 8) + | (envp->__sticky & FE_ALL_EXCEPT) +# else + | (* (unsigned int const *) envp & ((3U << 22) | (FE_ALL_EXCEPT << 8) | FE_ALL_EXCEPT)) +# endif + ; + if (((fpscr ^ orig_fpscr) & ~0xF0000000) != 0) + _FPU_SETCW (fpscr); + return 0; +# endif +} + +# elif defined __alpha + +/* On all OSes except OpenBSD, fenv_t is binary-equivalent to an 'unsigned long'. + On OpenBSD, it's a struct { unsigned int __sticky, __mask, __round; }. */ + +/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL. */ +# define _FE_ALL_EXCEPT (0x3FUL * FE_INVALID) + +int +fegetenv (fenv_t *envp) +{ + unsigned long swcr = __ieee_get_fp_control (); + unsigned long fpcr; + _FPU_GETCW (fpcr); +# if defined __OpenBSD__ + envp->__sticky = (swcr >> 17) & _FE_ALL_EXCEPT; + envp->__mask = (swcr >> 1) & _FE_ALL_EXCEPT; + envp->__round = (fpcr >> 58) & 0x3UL; +# else + * (unsigned long *) envp = + (fpcr & 0x0C00000000000000UL) /* rounding direction */ + | (swcr & 0x007E307EUL); /* exception flags, + mapping of denormal inputs and outputs, + exception trap bits */ +# endif + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned long env; + /* On *BSD, FE_DFL_ENV is the address of a global variable; + on glibc, it is a magic address. */ + if (envp == FE_DFL_ENV) + /* Default: rounding direction = FE_TONEAREST, exception flags = 0, + mapping = 0, exception trap bits = 0. */ + env = 0x0800000000000000UL; + else + { +# if defined __OpenBSD__ + env = ((unsigned long) (envp->__round & 0x3UL) << 58) + | ((envp->__sticky & _FE_ALL_EXCEPT) << 17) + | ((envp->__mask & _FE_ALL_EXCEPT) << 1); +# else + env = * (unsigned long const *) envp; +# endif + } + + /* Set the rounding direction. */ + unsigned long fpcr, orig_fpcr; + _FPU_GETCW (orig_fpcr); + fpcr = (orig_fpcr & ~0x0C00000000000000UL) | (env & 0x0C00000000000000UL); + if (fpcr != orig_fpcr) + _FPU_SETCW (fpcr); + /* Set the exception flags, the mapping of denormal inputs and outputs, and + the exception trap bits. */ + __ieee_set_fp_control (env & 0x007E307EUL); + + return 0; +} + +# elif defined __hppa + +/* On all OSes except glibc, fenv_t is binary-equivalent to an 'unsigned int'. + On glibc, it's a struct { unsigned int __status_word, __exception[7]; }, + of which only the __status_word field is used. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned int fpstatus; + _FPU_GETCW (fpstatus); + * (unsigned int *) envp = fpstatus; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int fpstatus, orig_fpstatus; + _FPU_GETCW (orig_fpstatus); + /* On *BSD, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + /* Default: exception flags = 0, rounding direction = FE_TONEAREST, + exception trap bits = 0. */ + fpstatus = 0x00000000U; + else + { + unsigned int env = * (unsigned int const *) envp; + fpstatus = (orig_fpstatus & ~0xF800061FU) | (env & 0xF800061FU); + } + if (fpstatus != orig_fpstatus) + _FPU_SETCW (fpstatus); + return 0; +} + +# elif defined __ia64__ + +/* On all OSes, fenv_t is binary-equivalent to an 'unsigned long'. */ + +/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL. */ +# define _FE_ALL_EXCEPT (0x3FUL * FE_INVALID) + +int +fegetenv (fenv_t *envp) +{ + unsigned long fpsr; + _FPU_GETCW (fpsr); + * (unsigned long *) envp = fpsr; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned long env; + /* On NetBSD, FE_DFL_ENV is the address of a global variable; + on glibc, it is a magic address. */ + if (envp == FE_DFL_ENV) + /* Default: + sf0 = 0b0000000001100, sf1 = 0b0000001001110, sf2 = sf3 = 0000001001100 + i.e. precision control = 80-bits "extended", + rounding direction = FE_TONEAREST, + exception flags = 0, + exceptions trap bits = all 1, i.e. all masked. */ + env = 0x0009804C0270033FUL; + else + env = * (unsigned long const *) envp; + unsigned long fpsr = env; + _FPU_SETCW (fpsr); + return 0; +} + +# elif defined __m68k__ + +/* On all OSes, fenv_t is binary-equivalent to a struct + { unsigned int control_register, status_register, instruction_address; }. */ + +# define _FPU_GETFPIAR(cw) __asm__ __volatile__ ("fmove%.l %/fpiar, %0" : "=dm" (cw)) +# define _FPU_SETFPIAR(cw) __asm__ __volatile__ ("fmove%.l %0, %/fpiar" : : "dm" (cw)) + +int +fegetenv (fenv_t *envp) +{ + unsigned int fpcr; + unsigned int fpsr; + unsigned int fpiar; + _FPU_GETCW (fpcr); + _FPU_GETFPSR (fpsr); + _FPU_GETFPIAR (fpiar); + ((unsigned int *) envp)[0] = fpcr; + ((unsigned int *) envp)[1] = fpsr; + ((unsigned int *) envp)[2] = fpiar; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int env_fpcr; + unsigned int env_fpsr; + if (envp == FE_DFL_ENV) + { + /* Default: exceptions trap bits = 0, rounding direction = FE_TONEAREST. */ + env_fpcr = 0; + /* Default: exception flags = 0. */ + env_fpsr = 0; + } + else + { + /* No need to restore the instruction address here. */ + env_fpcr = ((unsigned int const *) envp)[0]; + env_fpsr = ((unsigned int const *) envp)[1]; + } + unsigned int fpcr, orig_fpcr; + unsigned int fpsr, orig_fpsr; + unsigned int fpiar, orig_fpiar; + _FPU_GETCW (orig_fpcr); + _FPU_GETFPSR (orig_fpsr); + _FPU_GETFPIAR (orig_fpiar); + /* glibc uses 0x3E30U here. I think 0xFF30U is a better choice. */ + fpcr = (orig_fpcr & ~0xFF30U) | (env_fpcr & 0xFF30U); + fpsr = (orig_fpsr & ~0x00F8U) | (env_fpsr & 0x00F8U); + fpiar = orig_fpiar; + _FPU_SETCW (fpcr); + _FPU_SETFPSR (fpsr); + _FPU_SETFPIAR (fpiar); + return 0; +} + +# elif defined __mips__ + +/* On all OSes, fenv_t is binary-equivalent to an 'unsigned int'. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); + * (unsigned int *) envp = fcsr; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int env; + /* On *BSD, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + /* Default: exceptions trap bits = 0, exception flags = 0, + rounding direction = FE_TONEAREST. */ + env = 0x00000000U; + else + env = * (unsigned int const *) envp; + unsigned int fcsr = env; + _FPU_SETCW (fcsr); + return 0; +} + +# elif defined __loongarch__ + +/* On all OSes, fenv_t is binary-equivalent to an 'unsigned int'. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); + * (unsigned int *) envp = fcsr; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int env; + if (envp == FE_DFL_ENV) + /* Default: exception flags = 0, rounding direction = FE_TONEAREST, + exceptions trap bits = 0. */ + env = 0x00000000U; + else + env = * (unsigned int const *) envp; + unsigned int fcsr = env; + _FPU_SETCW (fcsr); + return 0; +} + +# elif defined __powerpc__ + +/* On all OSes except *BSD and AIX, fenv_t is a 'double'. + On *BSD, it's an 'unsigned int'. + On AIX, it's a struct { unsigned short rmode; unsigned int fpstat, trapstate; }. + But AIX has already been dealt with above. */ + +# if defined __linux__ +# include <sys/prctl.h> +# endif + +int +fegetenv (fenv_t *envp) +{ +# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ + union { unsigned long long u; double f; } memenv; + _FPU_GETCW_AS_DOUBLE (memenv.f); + * (unsigned int *) envp = (unsigned int) memenv.u; +# else + _FPU_GETCW_AS_DOUBLE (*envp); +# endif + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + union { unsigned long long u; double f; } orig_memenv, memenv; + _FPU_GETCW_AS_DOUBLE (orig_memenv.f); + /* On glibc and *BSD, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + memenv.u = 0xFFF8000000000000ULL; + else +# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ + memenv.u = * (unsigned int const *) envp; +# else + memenv.f = *envp; +# endif + if (!(memenv.u == orig_memenv.u)) + { + if ((orig_memenv.u & 0x000000f8) == 0 && (memenv.u & 0x000000f8) != 0) + { + /* Put the thread into precise trapping mode. */ +# if defined __linux__ || defined __NetBSD__ + prctl (PR_SET_FPEXC, PR_FP_EXC_PRECISE); +# endif + } + _FPU_SETCW_AS_DOUBLE (memenv.f); + if ((orig_memenv.u & 0x000000f8) != 0 && (memenv.u & 0x000000f8) == 0) + { + /* Put the thread into no-trapping mode. */ +# if defined __linux__ || defined __NetBSD__ + prctl (PR_SET_FPEXC, PR_FP_EXC_DISABLED); +# endif + } + } + return 0; +} + +# elif defined __riscv + +/* On all OSes except FreeBSD, fenv_t is binary-equivalent to an 'unsigned int'. + On FreeBSD, it's binary-equivalent to an 'unsigned long'. */ + +# define _FPU_GETCW(cw) __asm__ __volatile__ ("frcsr %0" : "=r" (cw)) +# define _FPU_SETCW(cw) __asm__ __volatile__ ("fscsr %z0" : : "rJ" (cw)) + +int +fegetenv (fenv_t *envp) +{ + unsigned int fcsr; + _FPU_GETCW (fcsr); +# if defined __FreeBSD__ + * (unsigned long *) envp = fcsr; +# else + * (unsigned int *) envp = fcsr; +# endif + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int env; + /* On *BSD and Android, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + /* Default: rounding direction = FE_TONEAREST, exception flags = 0. */ + env = 0x00000000U; + else +# if defined __FreeBSD__ + env = * (unsigned long const *) envp; +# else + env = * (unsigned int const *) envp; +# endif + unsigned int fcsr = env; + _FPU_SETCW (fcsr); + return 0; +} + +# elif defined __s390__ || defined __s390x__ + +/* On all OSes, fenv_t is binary-equivalent to a struct whose first (and only + relevant) field is an 'unsigned int'. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned int fpc; + _FPU_GETCW (fpc); + * (unsigned int *) envp = fpc; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int env; + if (envp == FE_DFL_ENV) + /* Default: exceptions trap bits = 0, exception flags = 0, + rounding direction = FE_TONEAREST. */ + env = 0x00000000U; + else + env = * (unsigned int const *) envp; + unsigned int fpc = env; + _FPU_SETCW (fpc); + return 0; +} + +# elif defined __sh__ + +/* On all OSes, fenv_t is binary-equivalent to an 'unsigned int'. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned int fpscr; + _FPU_GETCW (fpscr); + * (unsigned int *) envp = fpscr; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned int env; + /* On OpenBSD, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + /* Default: PR = 1, exceptions trap bits = 0, exception flags = 0, + rounding direction = FE_TONEAREST. */ + env = 0x00080000U; + else + env = * (unsigned int const *) envp; + unsigned int fpscr = env; + _FPU_SETCW (fpscr); + return 0; +} + +# elif defined __sparc + +/* On all OSes except Solaris, fenv_t is binary-equivalent to an 'unsigned long'. + On Solaris, it's a struct { __fex_handler_t __handlers; unsigned long __fsr; }. */ + +int +fegetenv (fenv_t *envp) +{ + unsigned long fsr; + _FPU_GETCW (fsr); +# if defined __sun + fex_getexcepthandler (&envp->__handlers, FEX_ALL); + envp->__fsr = fsr; +# else + * (unsigned long *) envp = fsr; +# endif + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned long fsr; + /* On *BSD and Solaris, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + { + /* Default: rounding direction = FE_TONEAREST, exceptions trap bits = 0, + exception flags = 0. */ + fsr = 0x00000000U; + } + else + { +# if defined __sun + fex_setexcepthandler (&envp->__handlers, FEX_ALL); + fsr = envp->__fsr; +# else + fsr = * (unsigned long const *) envp; +# endif + } + _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 defined __sun && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)) && defined __SUNPRO_C +/* Solaris/i386, Solaris/x86_64. */ + +/* Like FE_ALL_EXCEPT, except that it also includes FE_DENORMAL. */ +# define _FE_ALL_EXCEPT (0x3F * FE_INVALID) + +/* Accessors for the 387 unit's special registers. */ + +static void +getfctrl (unsigned short *fctrl_p) +{ +# if defined __x86_64__ || defined _M_X64 + asm ("fnstcw (%rdi)"); +# else + /* The compiler generates a stack frame. Therefore the first argument is in + 8(%ebp), not in 4(%esp). */ + asm ("movl 8(%ebp),%eax"); + asm ("fnstcw (%eax)"); +# endif +} + +static void +getfstat (unsigned short *fstat_p) +{ +# if defined __x86_64__ || defined _M_X64 + asm ("fnstsw (%rdi)"); +# else + /* The compiler generates a stack frame. Therefore the first argument is in + 8(%ebp), not in 4(%esp). */ + asm ("movl 8(%ebp),%eax"); + asm ("fnstsw (%eax)"); +# endif +} + +static void +getenv387 (x86_387_fenv_t *env387_p) +{ +# if defined __x86_64__ || defined _M_X64 + asm ("fnstenv (%rdi)"); +# else + /* The compiler generates a stack frame. Therefore the first argument is in + 8(%ebp), not in 4(%esp). */ + asm ("movl 8(%ebp),%eax"); + asm ("fnstenv (%eax)"); +# endif +} + +static void +setenv387 (x86_387_fenv_t const *env387_p) +{ +# if defined __x86_64__ || defined _M_X64 + asm ("fldenv (%rdi)"); +# else + /* The compiler generates a stack frame. Therefore the first argument is in + 8(%ebp), not in 4(%esp). */ + asm ("movl 8(%ebp),%eax"); + asm ("fldenv (%eax)"); +# endif +} + +/* Accessors for the mxcsr register. */ + +static void +getssecw (unsigned int *mxcsr_p) +{ +# if defined __x86_64__ || defined _M_X64 + asm ("stmxcsr (%rdi)"); +# else + /* The compiler generates a stack frame. Therefore the first argument is in + 8(%ebp), not in 4(%esp). */ + asm ("movl 8(%ebp),%eax"); + asm ("stmxcsr (%eax)"); +# endif +} + +static void +setssecw (unsigned int const *mxcsr_p) +{ +# if defined __x86_64__ || defined _M_X64 + asm ("ldmxcsr (%rdi)"); +# else + /* The compiler generates a stack frame. Therefore the first argument is in + 8(%ebp), not in 4(%esp). */ + asm ("movl 8(%ebp),%eax"); + asm ("ldmxcsr (%eax)"); +# endif +} + +int +fegetenv (fenv_t *envp) +{ + fex_getexcepthandler (&envp->__handlers, FEX_ALL); + + unsigned short fctrl; + unsigned short fstat; + unsigned int mxcsr; + getfctrl (&fctrl); + getfstat (&fstat); + getssecw (&mxcsr); + envp->__fsr = + (((unsigned int) (fctrl ^ 0x3F) & ~0xE0C0U) << 16) + | ((unsigned int) fstat | (mxcsr & 0x3F)); + + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned short env_fctrl; + unsigned int env_mxcsr; + /* On Solaris, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + { + env_fctrl = 0x3F; + env_mxcsr = 0x3F << 7; + } + else + { + fex_setexcepthandler (&envp->__handlers, FEX_ALL); + env_fctrl = (envp->__fsr >> 16) ^ 0x3F; + env_mxcsr = envp->__fsr & 0x3F; + } + + /* Store the exception flags in mxcsr, not in env387.__status_word. */ + + /* In the SSE unit. */ + unsigned int mxcsr; + getssecw (&mxcsr); + mxcsr = (mxcsr & ~0x3F) | env_mxcsr; + setssecw (&mxcsr); + + /* In the 387 unit. */ + x86_387_fenv_t env387; + getenv387 (&env387); + /* Note: fnstenv masks all floating-point exceptions until the setenv387 + below. */ + env387.__control_word = env_fctrl; + env387.__status_word &= ~0x3F; + setenv387 (&env387); + + return 0; +} + +#elif defined __sun && defined __sparc && defined __SUNPRO_C +/* Solaris/sparc. */ + +/* Accessors for the fsr register. */ + +static void +getfsr (unsigned long *fsr_p) +{ +# if defined __sparcv9 || defined __arch64__ /* sparc64 */ + asm ("stx %fsr,[%i0]"); +# else + asm ("st %fsr,[%i0]"); +# endif +} + +static void +setfsr (unsigned long const *fsr_p) +{ +# if defined __sparcv9 || defined __arch64__ /* sparc64 */ + asm ("ldx [%i0],%fsr"); +# else + asm ("ld [%i0],%fsr"); +# endif +} + +int +fegetenv (fenv_t *envp) +{ + unsigned long fsr; + getfsr (&fsr); + fex_getexcepthandler (&envp->__handlers, FEX_ALL); + envp->__fsr = fsr; + return 0; +} + +int +fesetenv (fenv_t const *envp) +{ + unsigned long fsr; + /* On *BSD and Solaris, FE_DFL_ENV is the address of a global variable; + no special code is needed in this case. */ + if (FE_DFL_ENV == (const fenv_t *) (-1) && envp == FE_DFL_ENV) + { + /* Default: rounding direction = FE_TONEAREST, exceptions trap bits = 0, + exception flags = 0. */ + fsr = 0x00000000U; + } + else + { + fex_setexcepthandler (&envp->__handlers, FEX_ALL); + fsr = envp->__fsr; + } + setfsr (&fsr); + return 0; +} + +# else + +# define NEED_FALLBACK 1 + +# endif + +#endif + +#if NEED_FALLBACK + +/* A dummy fallback. */ + +int +fegetenv (fenv_t *envp) +{ + return -1; +} + +int +fesetenv (fenv_t const *envp) +{ + return -1; +} + +#endif diff --git a/lib/fenv.in.h b/lib/fenv.in.h index b1c52d6e27..6d479124df 100644 --- a/lib/fenv.in.h +++ b/lib/fenv.in.h @@ -101,7 +101,6 @@ typedef unsigned long fenv_t; typedef struct { unsigned int __status_word; /* floating point status register */ - unsigned int __exception[7]; /* floating point exception registers */ } fenv_t; # elif defined __ia64__ @@ -164,6 +163,96 @@ typedef unsigned long fenv_t; # define FE_DFL_ENV ((const fenv_t *) (-1)) +#else + +# if !HAVE_FE_DFL_ENV +# undef FE_DFL_ENV +# define FE_DFL_ENV ((const fenv_t *) (-1)) +# endif + +#endif + +#if @GNULIB_FEGETENV@ +/* Stores the thread's current floating-point environment in *ENVP and + returns zero. Upon failure, it returns non-zero. */ +# if @REPLACE_FEGETENV@ || (!@HAVE_FEGETENV@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef fegetenv +# define fegetenv rpl_fegetenv +# endif +_GL_FUNCDECL_RPL (fegetenv, int, (fenv_t *envp)); +_GL_CXXALIAS_RPL (fegetenv, int, (fenv_t *envp)); +# else +# if !@HAVE_FEGETENV@ +_GL_FUNCDECL_SYS (fegetenv, int, (fenv_t *envp)); +# endif +_GL_CXXALIAS_SYS (fegetenv, int, (fenv_t *envp)); +# endif +_GL_CXXALIASWARN (fegetenv); +#endif + +#if @GNULIB_FESETENV@ +/* Activates the given object *ENVP as the thread's current floating-point + environment, without raising floating-point exceptions, and returns zero. + ENVP may be FE_DFL_ENV. + Upon failure, it returns non-zero. */ +# if @REPLACE_FESETENV@ || (!@HAVE_FESETENV@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef fesetenv +# define fesetenv rpl_fesetenv +# endif +_GL_FUNCDECL_RPL (fesetenv, int, (fenv_t const *envp)); +_GL_CXXALIAS_RPL (fesetenv, int, (fenv_t const *envp)); +# else +# if !@HAVE_FESETENV@ +_GL_FUNCDECL_SYS (fesetenv, int, (fenv_t const *envp)); +# endif +_GL_CXXALIAS_SYS (fesetenv, int, (fenv_t const *envp)); +# endif +_GL_CXXALIASWARN (fesetenv); +#endif + +#if @GNULIB_FEUPDATEENV@ +/* Activates the given object *ENVP as the thread's current floating-point + environment, raising exactly those floating-point exceptions that were + raised before, and returns zero. + ENVP may be FE_DFL_ENV. + Upon failure, it returns non-zero. */ +# if @REPLACE_FEUPDATEENV@ || (!@HAVE_FEUPDATEENV@ && (defined __FreeBSD__ || defined _MSC_VER)) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef feupdateenv +# define feupdateenv rpl_feupdateenv +# endif +_GL_FUNCDECL_RPL (feupdateenv, int, (fenv_t const *envp)); +_GL_CXXALIAS_RPL (feupdateenv, int, (fenv_t const *envp)); +# else +# if !@HAVE_FEUPDATEENV@ +_GL_FUNCDECL_SYS (feupdateenv, int, (fenv_t const *envp)); +# endif +_GL_CXXALIAS_SYS (feupdateenv, int, (fenv_t const *envp)); +# endif +_GL_CXXALIASWARN (feupdateenv); +#endif + +#if @GNULIB_FEHOLDEXCEPT@ +/* Stores the thread's current floating-point environment in *ENVP, clears + the floating-point exception status flags, disables trapping for all + floating-point exceptions, and returns zero. Upon failure, it returns + non-zero. */ +# if @REPLACE_FEHOLDEXCEPT@ || (!@HAVE_FEHOLDEXCEPT@ && defined __FreeBSD__) /* has an inline definition */ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef feholdexcept +# define feholdexcept rpl_feholdexcept +# endif +_GL_FUNCDECL_RPL (feholdexcept, int, (fenv_t *envp)); +_GL_CXXALIAS_RPL (feholdexcept, int, (fenv_t *envp)); +# else +# if !@HAVE_FEHOLDEXCEPT@ +_GL_FUNCDECL_SYS (feholdexcept, int, (fenv_t *envp)); +# endif +_GL_CXXALIAS_SYS (feholdexcept, int, (fenv_t *envp)); +# endif +_GL_CXXALIASWARN (feholdexcept); #endif diff --git a/m4/fenv-environment.m4 b/m4/fenv-environment.m4 new file mode 100644 index 0000000000..572a20f7a7 --- /dev/null +++ b/m4/fenv-environment.m4 @@ -0,0 +1,515 @@ +# fenv-environment.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([gl_FENV_ENVIRONMENT], +[ + AC_REQUIRE([gl_FENV_H_DEFAULTS]) + AC_REQUIRE([AC_CANONICAL_HOST]) + + dnl FE_DFL_ENV references an undefined global variable on + dnl NetBSD 9.3/{hppa,sparc} and Cygwin 2.9.0 (see + dnl <https://sourceware.org/pipermail/cygwin/2018-July/237888.html>). + AC_CACHE_CHECK([whether FE_DFL_ENV is usable], + [gl_cv_var_FE_DFL_ENV_works], + [case "$host_os" in + netbsd* | cygwin*) + save_LIBS="$LIBS" + LIBS="$LIBS -lm" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + fenv_t volatile env; + ]], + [[fenv_t const * volatile fenv_default = FE_DFL_ENV; + env = *fenv_default; + ]]) + ], + [gl_cv_var_FE_DFL_ENV_works=yes], + [gl_cv_var_FE_DFL_ENV_works=no]) + LIBS="$save_LIBS" + ;; + *) + gl_cv_var_FE_DFL_ENV_works="guessing yes" + ;; + esac + ]) + case "$gl_cv_var_FE_DFL_ENV_works" in + *yes) + AC_DEFINE([HAVE_FE_DFL_ENV], [1], + [Define to 1 if FE_DFL_ENV from <fenv.h> is usable.]) + ;; + *) + dnl If FE_DFL_ENV is not usable, we not only need to redefine it, + dnl but also override fesetenv and feupdateenv. + REPLACE_FESETENV=1 + REPLACE_FEUPDATEENV=1 + ;; + esac + gl_MATHFUNC([fesetenv], [int], [(fenv_t const *)], [ + #include <fenv.h> + fenv_t fenv_ret; + #if HAVE_FE_DFL_ENV + /* FreeBSD often has fesetenv inline in <fenv.h>, but needs -lm for + FE_DFL_ENV. */ + fenv_t const *fenv_default = FE_DFL_ENV; + #endif + ]) + if test $gl_cv_func_fesetenv_no_libm = yes \ + || test $gl_cv_func_fesetenv_in_libm = yes; then + dnl It needs linking with -lm on + dnl glibc, FreeBSD, NetBSD, OpenBSD, AIX, HP-UX, IRIX, Solaris, Android. + if test $gl_cv_func_fesetenv_no_libm = yes; then + FENV_ENVIRONMENT_LIBM= + else + FENV_ENVIRONMENT_LIBM=-lm + fi + dnl The feholdexcept function does not work on glibc 2.5 (see + dnl <https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a8c79c4088e8c04e4297936efa0dee6c8e6e974d>) + dnl and on FreeBSD 12.2/arm64. + dnl Also, on musl libc for most CPUs, it does not clear the exception trap + dnl bits. It's mentioned in + dnl <https://wiki.musl-libc.org/functional-differences-from-glibc.html>. + dnl Likewise on mingw: it does not clear the exception trap bits. + dnl Whereas on MSVC, this function may even fail. + AC_CACHE_CHECK([whether feholdexcept works], + [gl_cv_func_feholdexcept_works], + [case "$host_os" in + # Guess no on glibc ≤ 2.5. + *-gnu* | gnu*) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + #include <string.h> /* for __GNU_LIBRARY__ */ + #ifdef __GNU_LIBRARY__ + #include <features.h> + #if __GLIBC__ == 2 && __GLIBC_MINOR__ <= 5 + Unlucky user + #endif + #endif + ]], + [])], + [gl_cv_func_feholdexcept_works="guessing yes"], + [gl_cv_func_feholdexcept_works="guessing no"]) + ;; + *-musl* | midipix*) + dnl This is only needed when the module 'fenv-exceptions-trapping' is + dnl in use. + m4_ifdef([g][l_FENV_EXCEPTIONS_TRAPPING], [ + case "$host_cpu" in + riscv*) + dnl This architecture does not have exception trap bits anyway. + gl_cv_func_feholdexcept_works="guessing yes" + ;; + i?86) + dnl On this architecture, the feholdexcept override happens to + dnl not be needed. + gl_cv_func_feholdexcept_works="guessing yes" + ;; + *) + gl_cv_func_feholdexcept_works="guessing no" + ;; + esac + ], [gl_cv_func_feholdexcept_works="guessing yes"]) + ;; + mingw* | windows*) + gl_cv_func_feholdexcept_works="guessing no" + ;; + *) + dnl This test fails on FreeBSD 12.2/arm64. + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + ]], + [[fenv_t env; + feclearexcept (FE_ALL_EXCEPT); + feraiseexcept (FE_INVALID | FE_OVERFLOW | FE_INEXACT); + return (feholdexcept (&env) == 0 + && fetestexcept (FE_ALL_EXCEPT) != 0); + ]]) + ], + [gl_cv_func_feholdexcept_works="guessing yes"], + [gl_cv_func_feholdexcept_works=no], + [gl_cv_func_feholdexcept_works="guessing yes"]) + LIBS="$save_LIBS" + ;; + esac + ]) + case "$gl_cv_func_feholdexcept_works" in + *yes) ;; + *) REPLACE_FEHOLDEXCEPT=1 ;; + esac + dnl The fegetenv function does not work on glibc 2.19/x86_64 + dnl (see <https://sourceware.org/bugzilla/show_bug.cgi?id=16198>) + dnl and Mac OS X 10.5/{i386,x86_64}. + AC_CACHE_CHECK([whether fegetenv works], + [gl_cv_func_fegetenv_works], + [case "$host" in + # Guess no on glibc ≤ 2.19 / x86_64. + x86_64-*-*-gnu* | x86_64-*-gnu*) + 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_fegetenv_works="guessing yes"], + [gl_cv_func_fegetenv_works="guessing no"]) + ;; + # Guess no on Mac OS X/{i386,x86_64}. + *86*-*-darwin*) + gl_cv_func_fegetenv_works="guessing no" + ;; + *) gl_cv_func_fegetenv_works="guessing yes" ;; + esac + ]) + case "$gl_cv_func_fegetenv_works" in + *yes) ;; + *) REPLACE_FEGETENV=1 ;; + esac + dnl The fesetenv function does not work on FreeBSD 12.2/arm64 (see + dnl <https://cgit.freebsd.org/src/commit/?id=34cc08e336987a8ebc316595e3f552a4c09f1fd4>), + dnl on musl libc/{i386,x86_64} and AIX and Solaris and MSVC 14 (where it + dnl fails to restore the exception trap bits), + dnl on mingw (where calling it with FE_DFL_ENV argument has no effect on + dnl the mxcsr register), + dnl and on NetBSD/m68k. + AC_CACHE_CHECK([whether fesetenv works], + [gl_cv_func_fesetenv_works], + [case "$host" in + i?86-*-*-musl* | x86_64-*-*-musl*) + dnl This is only needed when the module 'fenv-exceptions-trapping' is + dnl in use. + m4_ifdef([g][l_FENV_EXCEPTIONS_TRAPPING], [ + gl_cv_func_fesetenv_works="guessing no" + ], [gl_cv_func_fesetenv_works="guessing yes"]) + ;; + *-*-aix*) + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + #include <fptrap.h> + ]], + [[fenv_t env; + feclearexcept (FE_ALL_EXCEPT); + if (fegetenv (&env) == 0) + { + fp_enable (TRP_DIV_BY_ZERO); + if (!fp_is_enabled (TRP_DIV_BY_ZERO)) + return 1; + if (fesetenv (&env) == 0 + && fp_is_enabled (TRP_DIV_BY_ZERO)) + return 2; + } + return 0; + ]]) + ], + [gl_cv_func_fesetenv_works="guessing yes"], + [gl_cv_func_fesetenv_works=no], + [gl_cv_func_fesetenv_works="guessing no"]) + LIBS="$save_LIBS" + ;; + *-*-solaris*) + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + #include <ieeefp.h> + ]], + [[fenv_t env1, env2; + feclearexcept (FE_ALL_EXCEPT); + if (fegetenv (&env1) == 0) + { + fpsetmask (FP_X_DZ); + if (fegetenv (&env2) == 0) + { + if (fpgetmask () != FP_X_DZ) + return 1; + if (fesetenv (&env1) == 0) + { + if (fpgetmask () != 0) + return 2; + fpsetmask (FP_X_INV); + if (fpgetmask () != FP_X_INV) + return 3; + if (fesetenv (&env2) == 0) + { + if (fpgetmask () != FP_X_DZ) + return 4; + } + } + } + } + return 0; + ]]) + ], + [gl_cv_func_fesetenv_works="guessing yes"], + [gl_cv_func_fesetenv_works=no], + [gl_cv_func_fesetenv_works="guessing no"]) + LIBS="$save_LIBS" + ;; + # Guess no on NetBSD/m68k. + m68*-*-netbsd*) + gl_cv_func_fesetenv_works="guessing no" + ;; + # Guess no on mingw and MSVC. + *86*-*-mingw* | *86*-*-windows*) + gl_cv_func_fesetenv_works="guessing no" + ;; + *) + dnl This test fails on FreeBSD 12.2/arm64. + dnl Here we test only the rounding mode restoration, but the function + dnl is more broken than that. + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + ]], + [[fenv_t env; + return (fesetround (FE_TOWARDZERO) == 0 + && fegetenv (&env) == 0 + && fesetround (FE_TONEAREST) == 0 + && fesetenv (&env) == 0 + && fegetround () != FE_TOWARDZERO); + ]]) + ], + [gl_cv_func_fesetenv_works="guessing yes"], + [gl_cv_func_fesetenv_works=no], + [gl_cv_func_fesetenv_works="guessing yes"]) + LIBS="$save_LIBS" + ;; + esac + ]) + case "$gl_cv_func_fesetenv_works" in + *yes) ;; + *) REPLACE_FESETENV=1 ;; + esac + dnl The feupdateenv function does not work on glibc 2.37/riscv64 + dnl (see <https://sourceware.org/bugzilla/show_bug.cgi?id=31022>) + dnl and on glibc 2.37/hppa + dnl (see <https://sourceware.org/bugzilla/show_bug.cgi?id=31023>) + dnl and on glibc 2.5/{i386,x86_64,ia64} (see + dnl <https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a8c79c4088e8c04e4297936efa0dee6c8e6e974d>) + dnl and on Mac OS X 10.5/{i386,x86_64} (where it forgets about the currently + dnl set floating-point exception flags) + dnl and on musl libc/s390x (where it does not trigger traps) + dnl on musl libc/{i386,x86_64} and AIX and Solaris and mingw 10 (where it + dnl fails to restore the exception trap bits), + dnl and on FreeBSD 12.2/arm64 (see + dnl <https://cgit.freebsd.org/src/commit/?id=34cc08e336987a8ebc316595e3f552a4c09f1fd4>). + dnl On MSVC 14 it may even fail. + AC_CACHE_CHECK([whether feupdateenv works], + [gl_cv_func_feupdateenv_works], + [case "$host" in + # Guess no on glibc/riscv. + riscv*-*-*-gnu*) + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + ]], + [[return feupdateenv (FE_DFL_ENV) != 0; + ]]) + ], + [gl_cv_func_feupdateenv_works=yes], + [gl_cv_func_feupdateenv_works=no], + [gl_cv_func_feupdateenv_works="guessing no"]) + LIBS="$save_LIBS" + ;; + # Guess no on glibc/hppa. + hppa*-*-*-gnu*) + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + ]], + [[fenv_t env; + feclearexcept (FE_ALL_EXCEPT); + feenableexcept (FE_INVALID); + fegetenv (&env); + fesetenv (FE_DFL_ENV); + feraiseexcept (FE_INVALID); + feupdateenv (&env); + return 0; + ]]) + ], + [gl_cv_func_feupdateenv_works=no], + [gl_cv_func_feupdateenv_works="guessing yes"], + [gl_cv_func_feupdateenv_works="guessing no"]) + LIBS="$save_LIBS" + ;; + # Guess no on glibc ≤ 2.5/{i386,x86_64,ia64}. + *86*-*-*-gnu* | *86*-*-gnu* | ia64*-*-*-gnu*) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + #include <string.h> /* for __GNU_LIBRARY__ */ + #ifdef __GNU_LIBRARY__ + #include <features.h> + #if __GLIBC__ == 2 && __GLIBC_MINOR__ <= 5 + Unlucky user + #endif + #endif + ]], + [])], + [gl_cv_func_feupdateenv_works="guessing yes"], + [gl_cv_func_feupdateenv_works="guessing no"]) + ;; + i?86-*-*-musl* | x86_64-*-*-musl* | \ + s390*-*-*-musl*) + dnl This is only needed when the module 'fenv-exceptions-trapping' is + dnl in use. + m4_ifdef([g][l_FENV_EXCEPTIONS_TRAPPING], [ + gl_cv_func_feupdateenv_works="guessing no" + ], [gl_cv_func_feupdateenv_works="guessing yes"]) + ;; + *-*-aix*) + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + #include <fptrap.h> + ]], + [[fenv_t env; + feclearexcept (FE_ALL_EXCEPT); + if (fegetenv (&env) == 0) + { + fp_enable (TRP_DIV_BY_ZERO); + if (!fp_is_enabled (TRP_DIV_BY_ZERO)) + return 1; + if (feupdateenv (&env) == 0 + && fp_is_enabled (TRP_DIV_BY_ZERO)) + return 2; + } + return 0; + ]]) + ], + [gl_cv_func_feupdateenv_works="guessing yes"], + [gl_cv_func_feupdateenv_works=no], + [gl_cv_func_feupdateenv_works="guessing no"]) + LIBS="$save_LIBS" + ;; + *-*-solaris*) + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + #include <ieeefp.h> + ]], + [[fenv_t env1, env2; + feclearexcept (FE_ALL_EXCEPT); + if (fegetenv (&env1) == 0) + { + fpsetmask (FP_X_DZ); + if (fegetenv (&env2) == 0) + { + if (fpgetmask () != FP_X_DZ) + return 1; + if (feupdateenv (&env1) == 0) + { + if (fpgetmask () != 0) + return 2; + fpsetmask (FP_X_INV); + if (fpgetmask () != FP_X_INV) + return 3; + if (feupdateenv (&env2) == 0) + { + if (fpgetmask () != FP_X_DZ) + return 4; + } + } + } + } + return 0; + ]]) + ], + [gl_cv_func_feupdateenv_works="guessing yes"], + [gl_cv_func_feupdateenv_works=no], + [gl_cv_func_feupdateenv_works="guessing no"]) + LIBS="$save_LIBS" + ;; + # Guess no on Mac OS X/{i386,x86_64}. + *86*-*-darwin*) + gl_cv_func_feupdateenv_works="guessing no" + ;; + # Guess no on mingw and MSVC. + *86*-*-mingw* | *86*-*-windows*) + gl_cv_func_feupdateenv_works="guessing no" + ;; + *) + dnl This test fails on FreeBSD 12.2/arm64. + dnl Here we test only the rounding mode restoration, but the function + dnl is more broken than that. + save_LIBS="$LIBS" + LIBS="$LIBS $FENV_ENVIRONMENT_LIBM" + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ + #include <fenv.h> + ]], + [[fenv_t env; + return (fesetround (FE_TOWARDZERO) == 0 + && fegetenv (&env) == 0 + && fesetround (FE_TONEAREST) == 0 + && feupdateenv (&env) == 0 + && fegetround () != FE_TOWARDZERO); + ]]) + ], + [gl_cv_func_feupdateenv_works="guessing yes"], + [gl_cv_func_feupdateenv_works=no], + [gl_cv_func_feupdateenv_works="guessing yes"]) + LIBS="$save_LIBS" + ;; + esac + ]) + case "$gl_cv_func_feupdateenv_works" in + *yes) ;; + *) REPLACE_FEUPDATEENV=1 ;; + esac + dnl Two functions are in the same compilation unit. + if test $REPLACE_FEGETENV = 1 || test $REPLACE_FESETENV = 1; then + REPLACE_FEGETENV=1 + REPLACE_FESETENV=1 + fi + dnl On AIX, feholdexcept, like fegetenv, does not save the exception trap + dnl bits. + case "$host_os" in + aix*) + if test $REPLACE_FEGETENV = 1; then + REPLACE_FEHOLDEXCEPT=1 + fi + ;; + esac + else + HAVE_FEGETENV=0 + HAVE_FESETENV=0 + HAVE_FEUPDATEENV=0 + HAVE_FEHOLDEXCEPT=0 + dnl It needs linking with -lm on platforms which define FE_DFL_ENV as the + dnl address of a global variable. + case "$host" in + powerpc*-*-linux*-gnu* | \ + *-*-freebsd* | *-*-dragonfly* | \ + *-*-netbsd* | \ + *-*-openbsd* | \ + *-*-solaris* ) + FENV_ENVIRONMENT_LIBM=-lm ;; + *) FENV_ENVIRONMENT_LIBM= ;; + esac + fi + AC_SUBST([FENV_ENVIRONMENT_LIBM]) +]) diff --git a/m4/fenv_h.m4 b/m4/fenv_h.m4 index 853f66e5a9..9ad078a9ff 100644 --- a/m4/fenv_h.m4 +++ b/m4/fenv_h.m4 @@ -1,4 +1,4 @@ -# fenv_h.m4 serial 1 +# fenv_h.m4 serial 2 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, @@ -88,12 +88,16 @@ AC_DEFUN([gl_FENV_H_DEFAULTS] REPLACE_FECLEAREXCEPT=0; AC_SUBST([REPLACE_FECLEAREXCEPT]) REPLACE_FEDISABLEEXCEPT=0; AC_SUBST([REPLACE_FEDISABLEEXCEPT]) REPLACE_FEENABLEEXCEPT=0; AC_SUBST([REPLACE_FEENABLEEXCEPT]) + REPLACE_FEGETENV=0; AC_SUBST([REPLACE_FEGETENV]) REPLACE_FEGETEXCEPT=0; AC_SUBST([REPLACE_FEGETEXCEPT]) REPLACE_FEGETEXCEPTFLAG=0; AC_SUBST([REPLACE_FEGETEXCEPTFLAG]) REPLACE_FEGETROUND=0; AC_SUBST([REPLACE_FEGETROUND]) + REPLACE_FEHOLDEXCEPT=0; AC_SUBST([REPLACE_FEHOLDEXCEPT]) REPLACE_FERAISEEXCEPT=0; AC_SUBST([REPLACE_FERAISEEXCEPT]) + REPLACE_FESETENV=0; AC_SUBST([REPLACE_FESETENV]) REPLACE_FESETEXCEPT=0; AC_SUBST([REPLACE_FESETEXCEPT]) REPLACE_FESETEXCEPTFLAG=0; AC_SUBST([REPLACE_FESETEXCEPTFLAG]) REPLACE_FESETROUND=0; AC_SUBST([REPLACE_FESETROUND]) REPLACE_FETESTEXCEPT=0; AC_SUBST([REPLACE_FETESTEXCEPT]) + REPLACE_FEUPDATEENV=0; AC_SUBST([REPLACE_FEUPDATEENV]) ]) diff --git a/m4/mathfunc.m4 b/m4/mathfunc.m4 index bf83bd2b8a..feca041287 100644 --- a/m4/mathfunc.m4 +++ b/m4/mathfunc.m4 @@ -1,4 +1,4 @@ -# mathfunc.m4 serial 16 +# mathfunc.m4 serial 17 dnl Copyright (C) 2010-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, @@ -42,7 +42,9 @@ AC_DEFUN([gl_MATHFUNC] [m4_bpatsubst( [m4_bpatsubst( [m4_bpatsubst( - [$3], + [m4_bpatsubst( + [$3], + [fenv_t\( const\)? \*], [&fenv_ret])], [fexcept_t\( const\)? \*], [&fx_ret])], [int\( const\)? \*], [&i_ret])], diff --git a/modules/fenv b/modules/fenv index 87a9a02d28..6c02818818 100644 --- a/modules/fenv +++ b/modules/fenv @@ -65,14 +65,18 @@ fenv.h: fenv.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(WARN_ON_USE_H) -e 's|@''REPLACE_FECLEAREXCEPT''@|$(REPLACE_FECLEAREXCEPT)|g' \ -e 's|@''REPLACE_FEDISABLEEXCEPT''@|$(REPLACE_FEDISABLEEXCEPT)|g' \ -e 's|@''REPLACE_FEENABLEEXCEPT''@|$(REPLACE_FEENABLEEXCEPT)|g' \ + -e 's|@''REPLACE_FEGETENV''@|$(REPLACE_FEGETENV)|g' \ -e 's|@''REPLACE_FEGETEXCEPT''@|$(REPLACE_FEGETEXCEPT)|g' \ -e 's|@''REPLACE_FEGETEXCEPTFLAG''@|$(REPLACE_FEGETEXCEPTFLAG)|g' \ -e 's|@''REPLACE_FEGETROUND''@|$(REPLACE_FEGETROUND)|g' \ + -e 's|@''REPLACE_FEHOLDEXCEPT''@|$(REPLACE_FEHOLDEXCEPT)|g' \ -e 's|@''REPLACE_FERAISEEXCEPT''@|$(REPLACE_FERAISEEXCEPT)|g' \ + -e 's|@''REPLACE_FESETENV''@|$(REPLACE_FESETENV)|g' \ -e 's|@''REPLACE_FESETEXCEPT''@|$(REPLACE_FESETEXCEPT)|g' \ -e 's|@''REPLACE_FESETEXCEPTFLAG''@|$(REPLACE_FESETEXCEPTFLAG)|g' \ -e 's|@''REPLACE_FESETROUND''@|$(REPLACE_FESETROUND)|g' \ -e 's|@''REPLACE_FETESTEXCEPT''@|$(REPLACE_FETESTEXCEPT)|g' \ + -e 's|@''REPLACE_FEUPDATEENV''@|$(REPLACE_FEUPDATEENV)|g' \ -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \ -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \ $(srcdir)/fenv.in.h > $@-t diff --git a/modules/fenv-environment b/modules/fenv-environment new file mode 100644 index 0000000000..ab5e15bcab --- /dev/null +++ b/modules/fenv-environment @@ -0,0 +1,56 @@ +Description: +Functions for controlling the floating-point environment as a whole: +fegetenv, fesetenv, feupdateenv, feholdexcept. + +Files: +lib/fenv-env.c +lib/fenv-env-update.c +lib/fenv-env-hold.c +lib/fenv-private.h +m4/fenv-environment.m4 +m4/mathfunc.m4 +m4/musl.m4 + +Depends-on: +fenv +extensions +verify +fenv-exceptions-tracking-c99 +fenv-exceptions-trapping + +configure.ac: +gl_FENV_ENVIRONMENT +gl_CONDITIONAL([GL_COND_OBJ_FENV_ENVIRONMENT], + [test $HAVE_FESETENV = 0 || test $REPLACE_FESETENV = 1]) +gl_CONDITIONAL([GL_COND_OBJ_FENV_ENVIRONMENT_UPDATE], + [test $HAVE_FEUPDATEENV = 0 || test $REPLACE_FEUPDATEENV = 1]) +gl_CONDITIONAL([GL_COND_OBJ_FENV_ENVIRONMENT_HOLD], + [test $HAVE_FEHOLDEXCEPT = 0 || test $REPLACE_FEHOLDEXCEPT = 1]) +gl_FENV_MODULE_INDICATOR([fegetenv]) +gl_FENV_MODULE_INDICATOR([fesetenv]) +gl_FENV_MODULE_INDICATOR([feupdateenv]) +gl_FENV_MODULE_INDICATOR([feholdexcept]) +gl_MUSL_LIBC + +Makefile.am: +if GL_COND_OBJ_FENV_ENVIRONMENT +lib_SOURCES += fenv-env.c +endif +if GL_COND_OBJ_FENV_ENVIRONMENT_UPDATE +lib_SOURCES += fenv-env-update.c +endif +if GL_COND_OBJ_FENV_ENVIRONMENT_HOLD +lib_SOURCES += fenv-env-hold.c +endif + +Include: +#include <fenv.h> + +Link: +$(FENV_ENVIRONMENT_LIBM) + +License: +LGPLv2+ + +Maintainer: +all -- 2.34.1
>From e4e615e62b2746ef6a68feaeb7588f1008582e18 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sun, 5 Nov 2023 15:21:06 +0100 Subject: [PATCH 2/2] fenv-environment: Add tests. * tests/test-fenv-env-1.c: New file. * tests/test-fenv-env-2.c: New file. * tests/test-fenv-env-3.c: New file. * tests/test-fenv-env-4.c: New file. * tests/test-fenv-env-5.sh: New file. * tests/test-fenv-env-5.c: New file. * modules/fenv-environment-tests: New file. --- ChangeLog | 9 ++ modules/fenv-environment-tests | 37 +++++++++ tests/test-fenv-env-1.c | 129 +++++++++++++++++++++++++++++ tests/test-fenv-env-2.c | 141 +++++++++++++++++++++++++++++++ tests/test-fenv-env-3.c | 135 ++++++++++++++++++++++++++++++ tests/test-fenv-env-4.c | 147 +++++++++++++++++++++++++++++++++ tests/test-fenv-env-5.c | 75 +++++++++++++++++ tests/test-fenv-env-5.sh | 18 ++++ 8 files changed, 691 insertions(+) create mode 100644 modules/fenv-environment-tests create mode 100644 tests/test-fenv-env-1.c create mode 100644 tests/test-fenv-env-2.c create mode 100644 tests/test-fenv-env-3.c create mode 100644 tests/test-fenv-env-4.c create mode 100644 tests/test-fenv-env-5.c create mode 100755 tests/test-fenv-env-5.sh diff --git a/ChangeLog b/ChangeLog index 18ee7639c2..0ff384ba0c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ 2023-11-05 Bruno Haible <br...@clisp.org> + fenv-environment: Add tests. + * tests/test-fenv-env-1.c: New file. + * tests/test-fenv-env-2.c: New file. + * tests/test-fenv-env-3.c: New file. + * tests/test-fenv-env-4.c: New file. + * tests/test-fenv-env-5.sh: New file. + * tests/test-fenv-env-5.c: New file. + * modules/fenv-environment-tests: New file. + fenv-environment: New module. * lib/fenv.in.h (fenv_t) [hppa]: Remove the __exception field. (FE_DFL_ENV): Override if <fenv.h> exists but HAVE_FE_DFL_ENV is not diff --git a/modules/fenv-environment-tests b/modules/fenv-environment-tests new file mode 100644 index 0000000000..ecb7b327f3 --- /dev/null +++ b/modules/fenv-environment-tests @@ -0,0 +1,37 @@ +Files: +tests/test-fenv-env-1.c +tests/test-fenv-env-2.c +tests/test-fenv-env-3.c +tests/test-fenv-env-4.c +tests/test-fenv-env-5.sh +tests/test-fenv-env-5.c +tests/macros.h +m4/musl.m4 + +Depends-on: +fenv-rounding +fenv-exceptions-tracking-c99 +fenv-exceptions-trapping +fpe-trapping + +configure.ac: +gl_MUSL_LIBC + +Makefile.am: +TESTS += \ + test-fenv-env-1 \ + test-fenv-env-2 \ + test-fenv-env-3 \ + test-fenv-env-4 \ + test-fenv-env-5.sh +check_PROGRAMS += \ + test-fenv-env-1 \ + test-fenv-env-2 \ + test-fenv-env-3 \ + test-fenv-env-4 \ + test-fenv-env-5 +test_fenv_env_1_LDADD = $(LDADD) @FENV_ENVIRONMENT_LIBM@ $(FENV_EXCEPTIONS_TRACKING_LIBM) $(FENV_ROUNDING_LIBM) $(FPE_TRAPPING_LIBM) +test_fenv_env_2_LDADD = $(LDADD) @FENV_ENVIRONMENT_LIBM@ $(FENV_EXCEPTIONS_TRACKING_LIBM) $(FENV_ROUNDING_LIBM) $(FPE_TRAPPING_LIBM) +test_fenv_env_3_LDADD = $(LDADD) @FENV_ENVIRONMENT_LIBM@ $(FENV_EXCEPTIONS_TRACKING_LIBM) $(FENV_ROUNDING_LIBM) $(FPE_TRAPPING_LIBM) +test_fenv_env_4_LDADD = $(LDADD) @FENV_ENVIRONMENT_LIBM@ $(FENV_EXCEPTIONS_TRACKING_LIBM) $(FENV_ROUNDING_LIBM) $(FPE_TRAPPING_LIBM) +test_fenv_env_5_LDADD = $(LDADD) @FENV_ENVIRONMENT_LIBM@ $(FENV_EXCEPTIONS_TRACKING_LIBM) $(FPE_TRAPPING_LIBM) diff --git a/tests/test-fenv-env-1.c b/tests/test-fenv-env-1.c new file mode 100644 index 0000000000..95c37ad6ac --- /dev/null +++ b/tests/test-fenv-env-1.c @@ -0,0 +1,129 @@ +/* Test of controlling the floating-point environment. + 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" + +/* Test the combination of fegetenv() with fesetenv(). */ + +int +main () +{ +#if defined __GLIBC__ && defined __arm__ && defined __SOFTFP__ + fputs ("Skipping test: no floating-point environment exists on this machine\n", stderr); + return 77; +#else + fenv_t env1, env2; + + /* Get to a known initial state. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + + /* Save the current environment in env1. */ + ASSERT (fegetenv (&env1) == 0); + + /* Modify the current environment. */ + fesetround (FE_UPWARD); + int supports_tracking = (feraiseexcept (FE_INVALID | FE_OVERFLOW | FE_INEXACT) == 0); + int supports_trapping = (feenableexcept (FE_DIVBYZERO) != -1); + + /* Save the current environment in env2. */ + ASSERT (fegetenv (&env2) == 0); + + /* Check that the exception flags are unmodified. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits are unmodified. */ + ASSERT (fegetexcept () == (supports_trapping ? FE_DIVBYZERO : 0)); + + /* Go back to env1. */ + ASSERT (fesetenv (&env1) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_TONEAREST); + /* Check that the exception flags have been restored. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == 0); + + /* Modify the rounding direction, the exception flags, and the exception + trap bits again. */ + fesetround (FE_DOWNWARD); + ASSERT (fegetround () == FE_DOWNWARD); + feclearexcept (FE_OVERFLOW); + feraiseexcept (FE_UNDERFLOW | FE_INEXACT); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_UNDERFLOW | FE_INEXACT : 0)); + feenableexcept (FE_INVALID); + ASSERT (fegetexcept () == (supports_trapping ? FE_INVALID : 0)); + + /* Go back to env2. */ + ASSERT (fesetenv (&env2) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_UPWARD); + /* Check that the exception flags have been restored. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == (supports_trapping ? FE_DIVBYZERO : 0)); + + /* ======================================================================== */ + /* FE_DFL_ENV */ + + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_OVERFLOW | FE_INEXACT : 0)); + + /* Go back to the default environment. */ + ASSERT (fesetenv (FE_DFL_ENV) == 0); + + /* Check its contents. */ + ASSERT (fegetround () == FE_TONEAREST); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + + /* Check that it has trapping on FE_INVALID disabled. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + + /* ======================================================================== */ + /* Check that fesetenv restores the trapping behaviour. */ + + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + + /* Go back to env1. */ + ASSERT (fesetenv (&env1) == 0); + + /* Check that it has disabled trapping on FE_INVALID. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + + return 0; +#endif +} diff --git a/tests/test-fenv-env-2.c b/tests/test-fenv-env-2.c new file mode 100644 index 0000000000..fa9dec78af --- /dev/null +++ b/tests/test-fenv-env-2.c @@ -0,0 +1,141 @@ +/* Test of controlling the floating-point environment. + 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" + +/* Test the combination of fegetenv() with feupdateenv(). */ + +int +main () +{ +#if defined __GLIBC__ && defined __arm__ && defined __SOFTFP__ + fputs ("Skipping test: no floating-point environment exists on this machine\n", stderr); + return 77; +#else + fenv_t env1, env2; + + /* Get to a known initial state. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + + /* Save the current environment in env1. */ + ASSERT (fegetenv (&env1) == 0); + + /* Modify the current environment. */ + fesetround (FE_UPWARD); + int supports_tracking = (feraiseexcept (FE_INVALID | FE_OVERFLOW | FE_INEXACT) == 0); + int supports_trapping = (feenableexcept (FE_DIVBYZERO) != -1); + + /* Save the current environment in env2. */ + ASSERT (fegetenv (&env2) == 0); + + /* Check that the exception flags are unmodified. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits are unmodified. */ + ASSERT (fegetexcept () == (supports_trapping ? FE_DIVBYZERO : 0)); + + /* Go back to env1. */ + ASSERT (feupdateenv (&env1) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_TONEAREST); + /* Check that the exception flags are the union of the saved and of the + current exception flags. (The saved exception flags happen to be none + in this case.) */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == 0); + + /* Modify the rounding direction, the exception flags, and the exception + trap bits again. */ + fesetround (FE_DOWNWARD); + ASSERT (fegetround () == FE_DOWNWARD); + feclearexcept (FE_INVALID | FE_OVERFLOW); + feraiseexcept (FE_UNDERFLOW | FE_INEXACT); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_UNDERFLOW | FE_INEXACT : 0)); + feenableexcept (FE_INVALID); + ASSERT (fegetexcept () == (supports_trapping ? FE_INVALID : 0)); + + /* Go back to env2. */ + ASSERT (feupdateenv (&env2) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_UPWARD); + /* Check that the exception flags are the union of the saved and of the + current exception flags. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == (supports_trapping ? FE_DIVBYZERO : 0)); + + /* ======================================================================== */ + /* FE_DFL_ENV */ + + /* Go back to the default environment. */ + ASSERT (feupdateenv (FE_DFL_ENV) == 0); + + /* Check that the rounding direction has been restored, + whereas the exception flags are unmodified. */ + ASSERT (fegetround () == FE_TONEAREST); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + + /* Go back to the default environment. */ + ASSERT (feupdateenv (FE_DFL_ENV) == 0); + + /* Check that the rounding direction has been restored, + whereas the exception flags are unmodified. */ + ASSERT (fegetround () == FE_TONEAREST); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + + /* Check that it has trapping on FE_INVALID disabled. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + + /* ======================================================================== */ + /* Check that feupdateenv restores the trapping behaviour. */ + + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + + /* Go back to env1. */ + ASSERT (feupdateenv (&env1) == 0); + + /* Check that it has disabled trapping on FE_INVALID. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + + return 0; +#endif +} diff --git a/tests/test-fenv-env-3.c b/tests/test-fenv-env-3.c new file mode 100644 index 0000000000..5cd094a616 --- /dev/null +++ b/tests/test-fenv-env-3.c @@ -0,0 +1,135 @@ +/* Test of controlling the floating-point environment. + 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" + +/* Test the combination of feholdexcept() with fesetenv(). */ + +int +main () +{ +#if defined __GLIBC__ && defined __arm__ && defined __SOFTFP__ + fputs ("Skipping test: no floating-point environment exists on this machine\n", stderr); + return 77; +#else + fenv_t env1, env2; + + /* Get to a known initial state. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + + /* Save the current environment in env1. */ + ASSERT (feholdexcept (&env1) == 0); + + /* Modify the current environment. */ + fesetround (FE_UPWARD); + int supports_tracking = (feraiseexcept (FE_INVALID | FE_OVERFLOW | FE_INEXACT) == 0); + int supports_trapping = (feenableexcept (FE_DIVBYZERO) != -1); + + /* Save the current environment in env2. */ + ASSERT (feholdexcept (&env2) == 0); + + /* Check that the exception flags are cleared. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + /* Check that the exception trap bits are cleared. */ + ASSERT (fegetexcept () == 0); + + /* Go back to env1. */ + ASSERT (fesetenv (&env1) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_TONEAREST); + /* Check that the exception flags have been restored. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == 0); + + /* Modify the rounding direction, the exception flags, and the exception + trap bits again. */ + fesetround (FE_DOWNWARD); + ASSERT (fegetround () == FE_DOWNWARD); + feclearexcept (FE_OVERFLOW); + feraiseexcept (FE_UNDERFLOW | FE_INEXACT); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_UNDERFLOW | FE_INEXACT : 0)); + feenableexcept (FE_INVALID); + ASSERT (fegetexcept () == (supports_trapping ? FE_INVALID : 0)); + + /* Go back to env2. */ + ASSERT (fesetenv (&env2) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_UPWARD); + /* Check that the exception flags have been restored. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == (supports_trapping ? FE_DIVBYZERO : 0)); + + /* ======================================================================== */ + /* FE_DFL_ENV */ + + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_OVERFLOW | FE_INEXACT : 0)); + + /* Go back to the default environment. */ + ASSERT (fesetenv (FE_DFL_ENV) == 0); + + /* Check its contents. */ + ASSERT (fegetround () == FE_TONEAREST); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + + /* Check that it has trapping on FE_INVALID disabled. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + + /* ======================================================================== */ + /* Check that feholdexcept, unlike fegetenv, disables trapping. */ + + /* 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 !MUSL_LIBC || GNULIB_FEENABLEEXCEPT + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + if (feenableexcept (FE_INVALID) != -1) + { + /* Call feholdexcept. */ + ASSERT (feholdexcept (&env1) == 0); + + /* Check that it has disabled trapping on FE_INVALID. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + } +# endif + + return 0; +#endif +} diff --git a/tests/test-fenv-env-4.c b/tests/test-fenv-env-4.c new file mode 100644 index 0000000000..9e669f2af5 --- /dev/null +++ b/tests/test-fenv-env-4.c @@ -0,0 +1,147 @@ +/* Test of controlling the floating-point environment. + 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" + +/* Test the combination of feholdexcept() with feupdateenv(). */ + +int +main () +{ +#if defined __GLIBC__ && defined __arm__ && defined __SOFTFP__ + fputs ("Skipping test: no floating-point environment exists on this machine\n", stderr); + return 77; +#else + fenv_t env1, env2; + + /* Get to a known initial state. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + + /* Save the current environment in env1. */ + ASSERT (feholdexcept (&env1) == 0); + + /* Modify the current environment. */ + fesetround (FE_UPWARD); + int supports_tracking = (feraiseexcept (FE_INVALID | FE_OVERFLOW | FE_INEXACT) == 0); + int supports_trapping = (feenableexcept (FE_DIVBYZERO) != -1); + + /* Save the current environment in env2. */ + ASSERT (feholdexcept (&env2) == 0); + + /* Check that the exception flags are cleared. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + /* Check that the exception trap bits are cleared. */ + ASSERT (fegetexcept () == 0); + + /* Go back to env1. */ + ASSERT (feupdateenv (&env1) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_TONEAREST); + /* Check that the exception flags are the union of the saved and of the + current exception flags. (The saved exception flags and the current + exception flags both happen to be none in this case.) */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == 0); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == 0); + + /* Modify the rounding direction, the exception flags, and the exception + trap bits again. */ + fesetround (FE_DOWNWARD); + ASSERT (fegetround () == FE_DOWNWARD); + feclearexcept (FE_OVERFLOW); + feraiseexcept (FE_UNDERFLOW | FE_INEXACT); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_UNDERFLOW | FE_INEXACT : 0)); + feenableexcept (FE_INVALID); + ASSERT (fegetexcept () == (supports_trapping ? FE_INVALID : 0)); + + /* Go back to env2. */ + ASSERT (feupdateenv (&env2) == 0); + + /* Check that the rounding direction has been restored. */ + ASSERT (fegetround () == FE_UPWARD); + /* Check that the exception flags are the union of the saved and of the + current exception flags. */ + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + /* Check that the exception trap bits have been restored. */ + ASSERT (fegetexcept () == (supports_trapping ? FE_DIVBYZERO : 0)); + + /* ======================================================================== */ + /* FE_DFL_ENV */ + + /* Go back to the default environment. */ + ASSERT (feupdateenv (FE_DFL_ENV) == 0); + + /* Check that the rounding direction has been restored, + whereas the exception flags are unmodified. */ + ASSERT (fegetround () == FE_TONEAREST); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + feenableexcept (FE_INVALID); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + + /* Go back to the default environment. */ + ASSERT (feupdateenv (FE_DFL_ENV) == 0); + + /* Check that the rounding direction has been restored, + whereas the exception flags are unmodified. */ + ASSERT (fegetround () == FE_TONEAREST); + ASSERT (fetestexcept (FE_ALL_EXCEPT) == (supports_tracking ? FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT : 0)); + + /* Check that it has trapping on FE_INVALID disabled. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + + /* ======================================================================== */ + /* Check that feholdexcept, unlike fegetenv, disables trapping. */ + + /* 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 !MUSL_LIBC || GNULIB_FEENABLEEXCEPT + /* Enable trapping on FE_INVALID. */ + feclearexcept (FE_INVALID); + if (feenableexcept (FE_INVALID) != -1) + { + /* Call feholdexcept. */ + ASSERT (feholdexcept (&env1) == 0); + + /* Check that it has disabled trapping on FE_INVALID. */ + ASSERT (fegetexcept () == 0); + { + double volatile a; + _GL_UNUSED double volatile b; + a = 0; b = a / a; + } + } +# endif + + return 0; +#endif +} diff --git a/tests/test-fenv-env-5.c b/tests/test-fenv-env-5.c new file mode 100644 index 0000000000..b54db76791 --- /dev/null +++ b/tests/test-fenv-env-5.c @@ -0,0 +1,75 @@ +/* Test of controlling the floating-point environment. + 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 "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) + +/* Test that feupdateenv(), unlike fesetenv(), can trigger traps. */ + +int +main () +{ + fenv_t env1; + + /* Get to a known initial state. */ + ASSERT (feclearexcept (FE_ALL_EXCEPT) == 0); + + /* Enable trapping on FE_INVALID. */ + if (sigfpe_on_invalid () < 0) + { + fputs ("Skipping test: trapping floating-point exceptions are not supported on this machine.\n", stderr); + return 77; + } + + /* Save the current environment in env1. */ + ASSERT (fegetenv (&env1) == 0); + + /* Go back to the default environment. */ + ASSERT (fesetenv (FE_DFL_ENV) == 0); + + /* Modify the current environment. */ + ASSERT (feraiseexcept (FE_INVALID) == 0); + + /* Go back to env1. + Since the exceptions flag FE_INVALID is currently set, and since + env1 has trapping on FE_INVALID enabled, this should trap. */ + ASSERT (feupdateenv (&env1) == 0); + + return 0; +} + +#else + +int +main () +{ + fputs ("Skipping test: feenableexcept not available\n", stderr); + return 77; +} + +#endif diff --git a/tests/test-fenv-env-5.sh b/tests/test-fenv-env-5.sh new file mode 100755 index 0000000000..5f81a49ba9 --- /dev/null +++ b/tests/test-fenv-env-5.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Test that feupdateenv() can trigger a trap. + +final_rc=0 + +${CHECKER} ./test-fenv-env-5${EXEEXT} +rc=$? +if test $rc = 77; then + final_rc=77 +else + if test $rc = 0; then + echo "Failed: ./test-fenv-env-5" 1>&2 + exit 1 + fi +fi + +exit $final_rc -- 2.34.1