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 <[email protected]>, 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 <[email protected]>, 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;
}