Lasse Collin wrote: > I mentioned this thread on #mingw-w64 on OFTC. I was asked if I > could ask the Gnulib developers to report the bugs with minimal > reproducible examples to the mingw-w64-public mailing list[1].
I usually don't spend much time reporting bugs for packages that are not part of the GNU system, or at least licensed under GPL / LGPL. Anyone is free to use Gnulib's tests as a self-tests for their platform, like for example the glibc people do [1]. But since you are asking in a friendly way, let me try to help. I'm not subscribing to the mingw-w64 mailing list; instead, you can find this mail archived under [2]. 1) The fenv functions are complicated because - the implementation often touches special processor registers, - the specification in ISO C 23 [3] talks not only about the inputs and results of a function, but also about its side effects in terms of state and its side effects in terms of triggered signals, - several functions are supposed to be considered together. A typical average programmer will therefore, when fixing one bug, regress on another part of the specification most of the time. It is therefore *mandatory* to work with a complete test suite, not with a set of 3, 4, or 5 test cases. Gnulib contains this test suite. The way to use it is through a testdir. Step 1: Creating the testdir: $ rm -f ../testdir $ ./gnulib-tool --create-testdir --dir=../testdir --single-configure \ fenv-h fenv-rounding fenv-environment \ fenv-exceptions-state-c99 fenv-exceptions-state-c23 \ fenv-exceptions-tracking-c99 fenv-exceptions-tracking-c23 \ fenv-exceptions-trapping Step 2: Compile this testdir without any gnulib workarounds for broken functions, only workarounds for missing functions: $ mkdir try1; cd try1 $ gl_cv_func_fegetenv_works='guessing yes' \ gl_cv_func_feholdexcept_works='guessing yes' \ gl_cv_func_fesetenv_works='guessing yes' \ gl_cv_func_fesetexcept_works=yes \ gl_cv_func_fesetexceptflag_works1=yes \ gl_cv_func_fesetexceptflag_works2=yes \ gl_cv_func_fesetround_works='guessing yes' \ gl_cv_func_feupdateenv_works='guessing yes' \ ../configure [CONFIGURE_ARGUMENTS] -C $ make $ make check Step 3 (useful for Gnulib contributors): Compile this testdir with all gnulib workarounds. $ mkdir try9; cd try9 $ ../configure [CONFIGURE_ARGUMENTS] -C $ make $ make check The CONFIGURE_ARGUMENTS can be left empty for a native build. For mingw, you *must* pass --host=i686-w64-mingw32 or --host=x86_64-w64-mingw32. If you want to single-step through failing tests later, pass CFLAGS=-ggdb (since the default, CFLAGS="-g -O2", is not suitable for single-stepping). Step 4: When you have a test failure, try to make the test independent of Gnulib. This mostly means removing '#include <config.h>' and '#include "macros.h"' and replacing 'ASSERT' with 'assert'. 2) mingw 13 has a bug: fesetexceptflag triggers a trap where it shouldn't. The failing test is test-fenv-except-state-3.c. Simplified to the attached bug1.c. It triggers SIGFPE, although it shouldn't. 3) mingw 13 has a bug: fesetenv (FE_DFL_ENV) does not reset the rounding direction. The failing test is test-fenv-env-2.c. Simplified to the attached bug2.c. It triggers an assertion failure in line 199. 4) mingw 13 has a bug in feupdateenv(). Visible in test-fenv-env-3 only once the fesetenv bug has been fixed. 5) mingw 13 has a bug in feholdexcept(). Visible in test-fenv-env-4 only once the fesetenv bug has been fixed. Hope this helps. Bruno [1] https://sourceware.org/glibc/wiki/Testing/Gnulib [2] https://lists.gnu.org/archive/html/bug-gnulib/2025-06/threads.html [3] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf
/* Test of saving the floating-point exception status flags. Copyright (C) 2023-2025 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ /* Written by Bruno Haible <br...@clisp.org>, 2023. */ /* Specification. */ #include <fenv.h> #include <assert.h> #include <stdio.h> /* CPU_HAS_SSE () returns true if the CPU has an SSE unit. */ # define CPU_HAS_SSE() 1 /* Macros that access the control word of the 387 unit, the so-called fctrl register. */ # define _FPU_GETCW(cw) __asm__ __volatile__ ("fnstcw %0" : "=m" (*&cw)) # define _FPU_SETCW(cw) __asm__ __volatile__ ("fldcw %0" : : "m" (*&cw)) /* Macros that access the status word of the 387 unit, the so-called fstat register. */ # define _FPU_GETSTAT(cw) __asm__ __volatile__ ("fnstsw %0" : "=m" (*&cw)) /* Macros that access the control and status word of the SSE unit, the mxcsr register. */ # define _FPU_GETSSECW(cw) __asm__ __volatile__ ("stmxcsr %0" : "=m" (*&cw)) # define _FPU_SETSSECW(cw) __asm__ __volatile__ ("ldmxcsr %0" : : "m" (*&cw)) /* The MSVC and mingw ??? 13 header files have different values for the floating-point exceptions than all the other platforms. Define some handy macros for conversion. */ # define exceptions_to_x86hardware(exceptions) \ ( ((exceptions) & FE_INVALID ? 0x01 : 0) \ | ((exceptions) & FE_DIVBYZERO ? 0x04 : 0) \ | ((exceptions) & FE_OVERFLOW ? 0x08 : 0) \ | ((exceptions) & FE_UNDERFLOW ? 0x10 : 0) \ | ((exceptions) & FE_INEXACT ? 0x20 : 0)) # define x86hardware_to_exceptions(fstat) \ ( ((fstat) & 0x01 ? FE_INVALID : 0) \ | ((fstat) & 0x04 ? FE_DIVBYZERO : 0) \ | ((fstat) & 0x08 ? FE_OVERFLOW : 0) \ | ((fstat) & 0x10 ? FE_UNDERFLOW : 0) \ | ((fstat) & 0x20 ? FE_INEXACT : 0)) int feenableexcept (int exceptions) { exceptions &= FE_ALL_EXCEPT; exceptions = exceptions_to_x86hardware (exceptions); /* Enable the traps in the 387 unit. */ unsigned short fctrl, orig_fctrl; _FPU_GETCW (orig_fctrl); fctrl = orig_fctrl & ~exceptions; if (fctrl != orig_fctrl) _FPU_SETCW (fctrl); if (CPU_HAS_SSE ()) { /* Enable the traps in the SSE unit as well. */ unsigned int mxcsr, orig_mxcsr; _FPU_GETSSECW (orig_mxcsr); mxcsr = orig_mxcsr & ~(exceptions << 7); if (mxcsr != orig_mxcsr) _FPU_SETSSECW (mxcsr); } unsigned int trapbits = 0x3f & ~orig_fctrl; return x86hardware_to_exceptions (trapbits); } int fedisableexcept (int exceptions) { exceptions &= FE_ALL_EXCEPT; exceptions = exceptions_to_x86hardware (exceptions); /* Disable the traps in the 387 unit. */ unsigned short fctrl, orig_fctrl; _FPU_GETCW (orig_fctrl); fctrl = orig_fctrl | exceptions; if (fctrl != orig_fctrl) _FPU_SETCW (fctrl); if (CPU_HAS_SSE ()) { /* Disable the traps in the SSE unit as well. */ unsigned int mxcsr, orig_mxcsr; _FPU_GETSSECW (orig_mxcsr); mxcsr = orig_mxcsr | (exceptions << 7); if (mxcsr != orig_mxcsr) _FPU_SETSSECW (mxcsr); } unsigned int trapbits = 0x3f & ~orig_fctrl; return x86hardware_to_exceptions (trapbits); } int fegetexcept (void) { /* Look at the trap bits in the 387 unit. */ unsigned short fctrl; _FPU_GETCW (fctrl); unsigned int trapbits = 0x3f & ~fctrl; return x86hardware_to_exceptions (trapbits); } /* sigfpe_on_invalid Enables a SIGFPE signal when an FE_INVALID exception occurs. A SIGFPE signal by default terminates the program. Returns >= 0 when successful, -1 upon failure. */ static int sigfpe_on_invalid () { /* Clear FE_INVALID exceptions from past operations. */ feclearexcept (FE_INVALID); /* An FE_INVALID exception shall trigger a SIGFPE signal. This call may fail on arm, arm64, riscv64 CPUs. Also, possibly a bug in glibc/sysdeps/m68k/fpu/feenablxcpt.c: it sets only bit 13, but should better set bits 15, 14, 13 of the control register together. See <https://sourceware.org/bugzilla/show_bug.cgi?id=30993>. */ int ret = feenableexcept (FE_INVALID); if (ret == -1) return -1; return 0; } /* Check that fesetexceptflag() does not trigger a trap. */ static volatile double a, b; static volatile long double al, bl; int main () { fexcept_t saved_flags_1; /* Test setting all exception flags. */ if (feraiseexcept (FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT) != 0) { fputs ("Skipping test: floating-point exceptions are not supported on this machine.\n", stderr); return 77; } /* Fill saved_flags_1. */ assert (fegetexceptflag (&saved_flags_1, FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT) == 0); /* Clear exceptions from past operations. */ feclearexcept (FE_ALL_EXCEPT); /* An FE_INVALID exception shall trigger a SIGFPE signal, which by default terminates the program. */ if (sigfpe_on_invalid () < 0) { fputs ("Skipping test: trapping floating-point exceptions are not supported on this machine.\n", stderr); return 77; } /* Attempt to set the FE_INVALID exception flag. */ int rc = fesetexceptflag (&saved_flags_1, FE_INVALID); /* On older i386 and on PowerPC, there is no way to implement fesetexceptflag() such that it does not trigger a trap. fesetexceptflag() is expected to fail in this case. */ # if !((defined __i386 || defined _M_IX86) || defined __powerpc__) assert (rc == 0); # endif /* Do a harmless floating-point operation (since on some CPUs, floating-point exceptions trigger a trap only at the next floating-point operation). */ a = 1.0; b = a + a; al = 1.0L; bl = al + al; return 0; }
/* Test of controlling the floating-point environment. Copyright (C) 2023-2025 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ /* Written by Bruno Haible <br...@clisp.org>, 2023. */ /* Specification. */ #include <fenv.h> #include <assert.h> #include <stdio.h> /* CPU_HAS_SSE () returns true if the CPU has an SSE unit. */ # define CPU_HAS_SSE() 1 /* Macros that access the control word of the 387 unit, the so-called fctrl register. */ # define _FPU_GETCW(cw) __asm__ __volatile__ ("fnstcw %0" : "=m" (*&cw)) # define _FPU_SETCW(cw) __asm__ __volatile__ ("fldcw %0" : : "m" (*&cw)) /* Macros that access the status word of the 387 unit, the so-called fstat register. */ # define _FPU_GETSTAT(cw) __asm__ __volatile__ ("fnstsw %0" : "=m" (*&cw)) /* Macros that access the control and status word of the SSE unit, the mxcsr register. */ # define _FPU_GETSSECW(cw) __asm__ __volatile__ ("stmxcsr %0" : "=m" (*&cw)) # define _FPU_SETSSECW(cw) __asm__ __volatile__ ("ldmxcsr %0" : : "m" (*&cw)) /* The MSVC and mingw ??? 13 header files have different values for the floating-point exceptions than all the other platforms. Define some handy macros for conversion. */ # define exceptions_to_x86hardware(exceptions) \ ( ((exceptions) & FE_INVALID ? 0x01 : 0) \ | ((exceptions) & FE_DIVBYZERO ? 0x04 : 0) \ | ((exceptions) & FE_OVERFLOW ? 0x08 : 0) \ | ((exceptions) & FE_UNDERFLOW ? 0x10 : 0) \ | ((exceptions) & FE_INEXACT ? 0x20 : 0)) # define x86hardware_to_exceptions(fstat) \ ( ((fstat) & 0x01 ? FE_INVALID : 0) \ | ((fstat) & 0x04 ? FE_DIVBYZERO : 0) \ | ((fstat) & 0x08 ? FE_OVERFLOW : 0) \ | ((fstat) & 0x10 ? FE_UNDERFLOW : 0) \ | ((fstat) & 0x20 ? FE_INEXACT : 0)) int feenableexcept (int exceptions) { exceptions &= FE_ALL_EXCEPT; exceptions = exceptions_to_x86hardware (exceptions); /* Enable the traps in the 387 unit. */ unsigned short fctrl, orig_fctrl; _FPU_GETCW (orig_fctrl); fctrl = orig_fctrl & ~exceptions; if (fctrl != orig_fctrl) _FPU_SETCW (fctrl); if (CPU_HAS_SSE ()) { /* Enable the traps in the SSE unit as well. */ unsigned int mxcsr, orig_mxcsr; _FPU_GETSSECW (orig_mxcsr); mxcsr = orig_mxcsr & ~(exceptions << 7); if (mxcsr != orig_mxcsr) _FPU_SETSSECW (mxcsr); } unsigned int trapbits = 0x3f & ~orig_fctrl; return x86hardware_to_exceptions (trapbits); } int fedisableexcept (int exceptions) { exceptions &= FE_ALL_EXCEPT; exceptions = exceptions_to_x86hardware (exceptions); /* Disable the traps in the 387 unit. */ unsigned short fctrl, orig_fctrl; _FPU_GETCW (orig_fctrl); fctrl = orig_fctrl | exceptions; if (fctrl != orig_fctrl) _FPU_SETCW (fctrl); if (CPU_HAS_SSE ()) { /* Disable the traps in the SSE unit as well. */ unsigned int mxcsr, orig_mxcsr; _FPU_GETSSECW (orig_mxcsr); mxcsr = orig_mxcsr | (exceptions << 7); if (mxcsr != orig_mxcsr) _FPU_SETSSECW (mxcsr); } unsigned int trapbits = 0x3f & ~orig_fctrl; return x86hardware_to_exceptions (trapbits); } int fegetexcept (void) { /* Look at the trap bits in the 387 unit. */ unsigned short fctrl; _FPU_GETCW (fctrl); unsigned int trapbits = 0x3f & ~fctrl; return x86hardware_to_exceptions (trapbits); } /* Test the combination of fegetenv() with fesetenv(). */ int main () { 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. */ if (supports_tracking) assert (fetestexcept (FE_ALL_EXCEPT) == (FE_INVALID | FE_OVERFLOW | FE_INEXACT)); else assert (fetestexcept (FE_ALL_EXCEPT) == 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. */ if (supports_tracking) assert (fetestexcept (FE_ALL_EXCEPT) == (FE_INVALID | FE_OVERFLOW | FE_INEXACT)); else assert (fetestexcept (FE_ALL_EXCEPT) == 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; 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; double volatile b; a = 0; b = a / a; } return 0; }