Re: feraiseexcept does not raise traps in newlib on x86/_64 [was Cygwin]

Brian Inglis <[email protected]>
Newsgroups gmane.comp.lib.newlib,gmane.os.cygwin
Organization Inglis
Message-ID <[email protected]>
On 2023-10-25 03:21, Bruno Haible via Cygwin wrote:
> Hi,
> 
> Seen on
>    - Cygwin 3.4.6 or 2.9.0 on x86_64.
>    - Cygwin 2.9.0 on i386.
> 
> According to ISO C 23 § 7.6.4.3
>    "The feraiseexcept function attempts to raise the supported floating-point
>     exceptions represented by its argument. 266)
>     Footnote 266) The effect is intended to be similar to that of floating-point
>     exceptions raised by arithmetic operations. Hence, implementation extensions
>     associated with raising a floating-point exception (for example, enabled
>     traps or IEC 60559 alternate exception handling) should be honored."
> 
> This does not work. How to reproduce:
> =================================== foo.c ===================================
> #define _GNU_SOURCE 1
> #include <fenv.h>
> #include <assert.h>
> 
> int
> main ()
> {
>    /* Clear FE_INVALID exceptions from past operations.  */
>    if (feclearexcept (FE_INVALID))
>      return 1;
> 
>    /* An FE_INVALID exception shall trigger a SIGFPE signal, which by default
>       terminates the program.  */
>    if (feenableexcept (FE_INVALID) == -1)
>      return 2;
> 
>    if (feraiseexcept (FE_INVALID))
>      return 3;
> 
>    return 0;
> }
> =============================================================================
> 
> For x86_64:
> 
> $ x86_64-pc-cygwin-gcc -Wall foo.c
> $ ./a.exe; echo $?
> 
> For i386:
> 
> $ i686-pc-cygwin-gcc -Wall foo.c
> $ ./a.exe; echo $?
> 
> Expected result (like seen e.g. on Linux/glibc):
> Floating-point exception (core dumped)
> 136
> 
> Actual result:
> 0
> 
> The workaround for x86_64 is to redefine feeraiseexcept in the same way
> as glibc does. This modified test program includes the workaround:
> 
> =================================== foo.c ===================================
> #define _GNU_SOURCE 1
> #include <fenv.h>
> #include <assert.h>

   #ifdef REDEFFE

> /* The floating-point environment of the 387 unit.  */
> typedef struct
>    {
>      /* 7 32-bit words:  */
>      unsigned short __control_word;      /* fctrl register */
>      unsigned short __reserved1;
>      unsigned short __status_word;       /* fstat register */
>      unsigned short __reserved2;
>      unsigned int more[5];
>    }
> x86_387_fenv_t;
> 
> int
> feraiseexcept (int exceptions)
> {
>    exceptions &= FE_ALL_EXCEPT;
>    if ((exceptions & ~(FE_INVALID | FE_DIVBYZERO)) == 0 && 0)
>      {
>        /* First: invalid exception.  */
>        if (exceptions & FE_INVALID)
>          {
>            double volatile a;
>            double volatile b;
>            a = 0; b = a / a;
>            (void) b;
>          }
>        /* Next: division by zero.  */
>        if (exceptions & FE_DIVBYZERO)
>          {
>            double volatile a, b;
>            double volatile c;
>            a = 1; b = 0; c = a / b;
>            (void) c;
>          }
>      }
>    else
>      {
>        /* The general case.  */
> 
>        /* Set the bits in the 387 unit.  */
>        x86_387_fenv_t env;
>        unsigned short orig_status_word;
>        __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env));
>        orig_status_word = env.__status_word;
>        env.__status_word |= exceptions;
>        if (env.__status_word != orig_status_word)
>          {
>            __asm__ __volatile__ ("fldenv %0" : : "m" (*&env));
>            /* A trap (if enabled) is triggered only at the next floating-point
>               instruction.  Force it to occur here.  */
>            __asm__ __volatile__ ("fwait");
>          }
>      }
>    return 0;
> }

   #endif /* REDEFFE */

> int
> main ()
> {
>    /* Clear FE_INVALID exceptions from past operations.  */
>    if (feclearexcept (FE_INVALID))
>      return 1;
> 
>    /* An FE_INVALID exception shall trigger a SIGFPE signal, which by default
>       terminates the program.  */
>    if (feenableexcept (FE_INVALID) == -1)
>      return 2;
> 
>    if (feraiseexcept (FE_INVALID))
>      return 3;
> 
>    return 0;
> }
> =============================================================================
> 
> (This workaround *should* also work on i386, but it doesn't. I don't know
> why.)

Thanks for the report Bruno,

Confirmed on Cygwin current stable with #ifdef added for ease of rebuild:

$ uname -srvmo
CYGWIN_NT-10.0-19045 3.4.9-1.x86_64 2023-09-06 11:19 UTC x86_64 Cygwin
$ gcc -UREDEFFE -o feraiseexcept{,.c} && ./feraiseexcept; echo $?
0
$ gcc -DREDEFFE -o feraiseexcept{,.c} && ./feraiseexcept; echo $?
Floating point exception (core dumped)
136

Cygwin no longer supports x86/i686 32 bit.

Anyone have a Cygwin 3.5.0 test release environment available, just in case it 
might be different in some way, and could confirm?

The code is in newlib, so redirecting there:

https://sourceware.org/cgit/newlib-cygwin/tree/newlib/libm/machine/shared_x86/fenv.c#n253

That only has similar common code as in glibc, not the specific exceptions.

Please, could anyone test on a non-Cygwin newlib x86/_64 build?

The BSDs store, add exceptions, then load first the X87 then SSE MXCSR environments:

https://cgit.freebsd.org/src/tree/lib/msun/amd64/fenv.c#n32

https://github.com/NetBSD/src/blob/trunk/lib/libm/arch/x86_64/fenv.c#L195

https://github.com/openbsd/src/blob/master/lib/libm/arch/amd64/fenv.c#L116

which is handled in newlib fenv.c functions by fgetenv/fsetenv *except* in 
feraiseexcept!

-- 
Take care. Thanks, Brian Inglis              Calgary, Alberta, Canada

La perfection est atteinte                   Perfection is achieved
non pas lorsqu'il n'y a plus rien à ajouter  not when there is no more to add
mais lorsqu'il n'y a plus rien à retirer     but when there is no more to cut
                                 -- Antoine de Saint-Exupéry
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.