i686: Do not raise exception traps on fesetexcept (BZ 30989)

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.  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.

Checked on i686-linux-gnu.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
Adhemerval Zanella 2023-10-24 08:37:15 -03:00
parent ecb1e7220d
commit 47a9eeb9ba
4 changed files with 99 additions and 25 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)
@ -41,12 +42,32 @@ do_test (void)
/* Verify fesetexcept does not cause exception traps. For architectures /* Verify fesetexcept 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 = fesetexcept (FE_ALL_EXCEPT); ret = fesetexcept (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 = fesetexcept (FE_ALL_EXCEPT);
int exc_after = fegetexcept ();
if (exc_before != exc_after)
{
puts ("fesetexcept (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) if (ret == 0)
{ {
@ -72,5 +93,4 @@ do_test (void)
return result; return result;
} }
#define TEST_FUNCTION do_test () #include <support/test-driver.c>
#include "../test-skeleton.c"

View File

@ -17,15 +17,53 @@
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
#include <fenv.h> #include <fenv.h>
#include <ldsodefs.h>
int int
fesetexcept (int excepts) fesetexcept (int excepts)
{ {
fenv_t temp; /* The flags can be set in the 387 unit or in the SSE unit. 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. */
__asm__ ("fnstenv %0" : "=m" (*&temp)); excepts &= FE_ALL_EXCEPT;
temp.__status_word |= excepts & FE_ALL_EXCEPT;
__asm__ ("fldenv %0" : : "m" (*&temp)); if (CPU_FEATURE_USABLE (SSE))
{
/* Get the control word of the SSE unit. */
unsigned int mxcsr;
__asm__ ("stmxcsr %0" : "=m" (*&mxcsr));
/* Set relevant flags. */
mxcsr |= excepts;
/* Put the new data in effect. */
__asm__ ("ldmxcsr %0" : : "m" (*&mxcsr));
}
else
{
fenv_t temp;
/* Note: fnstenv masks all floating-point exceptions until the fldenv
or fldcw below. */
__asm__ ("fnstenv %0" : "=m" (*&temp));
/* Set relevant flags. */
temp.__status_word |= excepts;
if ((~temp.__control_word) & excepts)
{
/* Setting the exception flags may trigger a trap (at the next
floating-point instruction, but that does not matter).
ISO C23 (7.6.4.4) 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));
}
return 0; return 0;
} }

View File

@ -0,0 +1,29 @@
/* Configuration for math tests: support for setting exception flags
without causing enabled traps. i686 version.
Copyright (C) 2023 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#ifndef I386_FPU_MATH_TESTS_TRAP_FORCE_H
#define I386_FPU_MATH_TESTS_TRAP_FORCE_H 1
#include <cpu-features.h>
/* Setting exception flags in FPU Status Register results in enabled traps for
those exceptions being taken. */
#define EXCEPTION_SET_FORCES_TRAP !CPU_FEATURE_USABLE (SSE)
#endif /* math-tests-trap-force.h. */

View File

@ -22,17 +22,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <cpu-features.h>
static bool #include <support/check.h>
have_sse2 (void)
{
unsigned int eax, ebx, ecx, edx;
if (!__get_cpuid (1, &eax, &ebx, &ecx, &edx))
return false;
return (edx & bit_SSE2) != 0;
}
static uint32_t static uint32_t
get_sse_mxcsr (void) get_sse_mxcsr (void)
@ -164,13 +155,9 @@ sse_tests (void)
static int static int
do_test (void) do_test (void)
{ {
if (!have_sse2 ()) if (!CPU_FEATURE_USABLE (SSE2))
{ FAIL_UNSUPPORTED ("CPU does not support SSE2");
puts ("CPU does not support SSE2, cannot test");
return 0;
}
return sse_tests (); return sse_tests ();
} }
#define TEST_FUNCTION do_test () #include <support/test-driver.c>
#include <test-skeleton.c>