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;
}

Reply via email to