x86: Do not raises floating-point exception traps on fesetexceptflag (BZ 30990)

According to ISO C23 (7.6.4.4), fesetexcept is supposed to set
floating-point exception flags without raising a trap (unlike
feraiseexcept, which is supposed to raise a trap if feenableexcept
was called with the appropriate argument).

The flags can be set in the 387 unit or in the SSE unit.  When we need
to clear a flag, we need to do so in both units, due to the way
fetestexcept is implemented.

When we need to set a flag, it is sufficient to do it in the SSE unit,
because that is guaranteed to not trap.  However, on i386 CPUs that have
only a 387 unit, set the flags in the 387, as long as this cannot trap.

Co-authored-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
Bruno Haible 2023-10-24 08:37:16 -03:00 committed by Adhemerval Zanella
parent 47a9eeb9ba
commit 787282dede
3 changed files with 78 additions and 32 deletions

View file

@ -19,6 +19,7 @@
#include <fenv.h> #include <fenv.h>
#include <stdio.h> #include <stdio.h>
#include <math-tests.h> #include <math-tests.h>
#include <math-barriers.h>
static int static int
do_test (void) do_test (void)
@ -65,12 +66,32 @@ do_test (void)
/* The test is that this does not cause exception traps. For architectures /* The test is that this does not cause exception traps. For architectures
where setting the exception might result in traps the function should where setting the exception might result in traps the function should
return a nonzero value. */ return a nonzero value.
Also check if the function does not alter the exception mask. */
ret = fesetexceptflag (&saved, FE_ALL_EXCEPT); ret = fesetexceptflag (&saved, FE_ALL_EXCEPT);
_Static_assert (!(EXCEPTION_SET_FORCES_TRAP && !EXCEPTION_TESTS(float)), _Static_assert (!(EXCEPTION_SET_FORCES_TRAP && !EXCEPTION_TESTS(float)),
"EXCEPTION_SET_FORCES_TRAP only makes sense if the " "EXCEPTION_SET_FORCES_TRAP only makes sense if the "
"architecture suports exceptions"); "architecture suports exceptions");
{
int exc_before = fegetexcept ();
ret = fesetexceptflag (&saved, FE_ALL_EXCEPT);
int exc_after = fegetexcept ();
if (exc_before != exc_after)
{
puts ("fesetexceptflag (FE_ALL_EXCEPT) changed the exceptions mask");
return 1;
}
}
/* Execute some floating-point operations, since on some CPUs exceptions
triggers a trap only at the next floating-point instruction. */
volatile double a = 1.0;
volatile double b = a + a;
math_force_eval (b);
volatile long double al = 1.0L;
volatile long double bl = al + al;
math_force_eval (bl);
if (ret != 0 && !EXCEPTION_SET_FORCES_TRAP) if (ret != 0 && !EXCEPTION_SET_FORCES_TRAP)
{ {

View file

@ -17,42 +17,63 @@
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
#include <fenv.h> #include <fenv.h>
#include <math.h>
#include <unistd.h>
#include <ldsodefs.h> #include <ldsodefs.h>
#include <dl-procinfo.h>
int int
__fesetexceptflag (const fexcept_t *flagp, int excepts) __fesetexceptflag (const fexcept_t *flagp, int excepts)
{ {
/* The flags can be set in the 387 unit or in the SSE unit. When we need to
clear a flag, we need to do so in both units, due to the way fetestexcept
is implemented.
When we need to set a flag, it is sufficient to do it in the SSE unit,
because that is guaranteed to not trap. However, on i386 CPUs that have
only a 387 unit, set the flags in the 387, as long as this cannot trap. */
fenv_t temp; fenv_t temp;
/* Get the current environment. We have to do this since we cannot excepts &= FE_ALL_EXCEPT;
separately set the status word. */
/* Get the current x87 FPU environment. We have to do this since we
cannot separately set the status word.
Note: fnstenv masks all floating-point exceptions until the fldenv
or fldcw below. */
__asm__ ("fnstenv %0" : "=m" (*&temp)); __asm__ ("fnstenv %0" : "=m" (*&temp));
temp.__status_word &= ~(excepts & FE_ALL_EXCEPT);
temp.__status_word |= *flagp & excepts & FE_ALL_EXCEPT;
/* Store the new status word (along with the rest of the environment.
Possibly new exceptions are set but they won't get executed unless
the next floating-point instruction. */
__asm__ ("fldenv %0" : : "m" (*&temp));
/* If the CPU supports SSE, we set the MXCSR as well. */
if (CPU_FEATURE_USABLE (SSE)) if (CPU_FEATURE_USABLE (SSE))
{ {
unsigned int xnew_exc; unsigned int mxcsr;
/* Get the current MXCSR. */ /* Clear relevant flags. */
__asm__ ("stmxcsr %0" : "=m" (*&xnew_exc)); temp.__status_word &= ~(excepts & ~ *flagp);
/* Set the relevant bits. */ /* Store the new status word (along with the rest of the environment). */
xnew_exc &= ~(excepts & FE_ALL_EXCEPT); __asm__ ("fldenv %0" : : "m" (*&temp));
xnew_exc |= *flagp & excepts & FE_ALL_EXCEPT;
/* And now similarly for SSE. */
__asm__ ("stmxcsr %0" : "=m" (*&mxcsr));
/* Clear or set relevant flags. */
mxcsr ^= (mxcsr ^ *flagp) & excepts;
/* Put the new data in effect. */ /* Put the new data in effect. */
__asm__ ("ldmxcsr %0" : : "m" (*&xnew_exc)); __asm__ ("ldmxcsr %0" : : "m" (*&mxcsr));
}
else
{
/* Clear or set relevant flags. */
temp.__status_word ^= (temp.__status_word ^ *flagp) & excepts;
if ((~temp.__control_word) & temp.__status_word & excepts)
{
/* Setting the exception flags may trigger a trap (at the next
floating-point instruction, but that does not matter).
ISO C 23 § 7.6.4.5 does not allow it. */
__asm__ volatile ("fldcw %0" : : "m" (*&temp.__control_word));
return -1;
}
/* Store the new status word (along with the rest of the environment). */
__asm__ ("fldenv %0" : : "m" (*&temp));
} }
/* Success. */ /* Success. */

View file

@ -22,30 +22,34 @@
int int
fesetexceptflag (const fexcept_t *flagp, int excepts) fesetexceptflag (const fexcept_t *flagp, int excepts)
{ {
/* The flags can be set in the 387 unit or in the SSE unit.
When we need to clear a flag, we need to do so in both units,
due to the way fetestexcept() is implemented.
When we need to set a flag, it is sufficient to do it in the SSE unit,
because that is guaranteed to not trap. */
fenv_t temp; fenv_t temp;
unsigned int mxcsr; unsigned int mxcsr;
/* XXX: Do we really need to set both the exception in both units? excepts &= FE_ALL_EXCEPT;
Shouldn't it be enough to set only the SSE unit? */
/* Get the current x87 FPU environment. We have to do this since we /* Get the current x87 FPU environment. We have to do this since we
cannot separately set the status word. */ cannot separately set the status word. */
__asm__ ("fnstenv %0" : "=m" (*&temp)); __asm__ ("fnstenv %0" : "=m" (*&temp));
temp.__status_word &= ~(excepts & FE_ALL_EXCEPT); /* Clear relevant flags. */
temp.__status_word |= *flagp & excepts & FE_ALL_EXCEPT; temp.__status_word &= ~(excepts & ~ *flagp);
/* Store the new status word (along with the rest of the environment. /* Store the new status word (along with the rest of the environment). */
Possibly new exceptions are set but they won't get executed unless
the next floating-point instruction. */
__asm__ ("fldenv %0" : : "m" (*&temp)); __asm__ ("fldenv %0" : : "m" (*&temp));
/* And now the same for SSE. */ /* And now similarly for SSE. */
__asm__ ("stmxcsr %0" : "=m" (*&mxcsr)); __asm__ ("stmxcsr %0" : "=m" (*&mxcsr));
mxcsr &= ~(excepts & FE_ALL_EXCEPT); /* Clear or set relevant flags. */
mxcsr |= *flagp & excepts & FE_ALL_EXCEPT; mxcsr ^= (mxcsr ^ *flagp) & excepts;
/* Put the new data in effect. */
__asm__ ("ldmxcsr %0" : : "m" (*&mxcsr)); __asm__ ("ldmxcsr %0" : : "m" (*&mxcsr));
/* Success. */ /* Success. */