[v2,2/3] i686: Do not raise exception traps on fesetexcept (BZ 30989)
Checks
Context |
Check |
Description |
redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Testing passed
|
Commit Message
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.
---
math/test-fesetexcept-traps.c | 11 +++++++
sysdeps/i386/fpu/fesetexcept.c | 41 +++++++++++++++++++++---
sysdeps/i386/fpu/math-tests-trap-force.h | 29 +++++++++++++++++
3 files changed, 77 insertions(+), 4 deletions(-)
create mode 100644 sysdeps/i386/fpu/math-tests-trap-force.h
Comments
Looks all good, except for two comments:
Adhemerval Zanella wrote:
> diff --git a/sysdeps/i386/fpu/fesetexcept.c b/sysdeps/i386/fpu/fesetexcept.c
> index 18949e982a..6eeb5ab5b0 100644
> --- a/sysdeps/i386/fpu/fesetexcept.c
> +++ b/sysdeps/i386/fpu/fesetexcept.c
> @@ -17,15 +17,48 @@
> <https://www.gnu.org/licenses/>. */
>
> #include <fenv.h>
> +#include <ldsodefs.h>
>
> int
> 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));
> - temp.__status_word |= excepts & FE_ALL_EXCEPT;
> - __asm__ ("fldenv %0" : : "m" (*&temp));
> + excepts &= FE_ALL_EXCEPT;
> +
> + if (CPU_FEATURE_USABLE (SSE))
> + {
> + /* And now similarly for SSE. */
> + unsigned int mxcsr;
> + __asm__ ("stmxcsr %0" : "=m" (*&mxcsr));
The comment "And now similarly for SSE." is odd, since there was no similar code
before it. How about
/* Set the flags in the SSE unit. */
> + /* Set relevant flags. */
> + mxcsr |= excepts;
> +
> + /* Put the new data in effect. */
> + __asm__ ("ldmxcsr %0" : : "m" (*&mxcsr));
> + }
> + else
> + {
> + fenv_t temp;
> +
> + __asm__ ("fnstenv %0" : "=m" (*&temp));
> +
> + /* Clear or set relevant flags. */
> + temp.__status_word |= temp.__status_word & excepts;
The comment here should be
/* Set relevant flags. */
since here, unlike in fesetexceptflag(), we don't need to clear any flags.
Bruno
On 24/10/23 10:43, Bruno Haible wrote:
> Looks all good, except for two comments:
>
> Adhemerval Zanella wrote:
>> diff --git a/sysdeps/i386/fpu/fesetexcept.c b/sysdeps/i386/fpu/fesetexcept.c
>> index 18949e982a..6eeb5ab5b0 100644
>> --- a/sysdeps/i386/fpu/fesetexcept.c
>> +++ b/sysdeps/i386/fpu/fesetexcept.c
>> @@ -17,15 +17,48 @@
>> <https://www.gnu.org/licenses/>. */
>>
>> #include <fenv.h>
>> +#include <ldsodefs.h>
>>
>> int
>> 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));
>> - temp.__status_word |= excepts & FE_ALL_EXCEPT;
>> - __asm__ ("fldenv %0" : : "m" (*&temp));
>> + excepts &= FE_ALL_EXCEPT;
>> +
>> + if (CPU_FEATURE_USABLE (SSE))
>> + {
>> + /* And now similarly for SSE. */
>> + unsigned int mxcsr;
>> + __asm__ ("stmxcsr %0" : "=m" (*&mxcsr));
>
> The comment "And now similarly for SSE." is odd, since there was no similar code
> before it. How about
> /* Set the flags in the SSE unit. */
Ack.
>
>> + /* Set relevant flags. */
>> + mxcsr |= excepts;
>> +
>> + /* Put the new data in effect. */
>> + __asm__ ("ldmxcsr %0" : : "m" (*&mxcsr));
>> + }
>> + else
>> + {
>> + fenv_t temp;
>> +
>> + __asm__ ("fnstenv %0" : "=m" (*&temp));
>> +
>> + /* Clear or set relevant flags. */
>> + temp.__status_word |= temp.__status_word & excepts;
>
> The comment here should be
> /* Set relevant flags. */
> since here, unlike in fesetexceptflag(), we don't need to clear any flags.
Ack.
I changed both locally.
Hi Adhemerval,
In the error case, the new code mistakenly masks all floating-point
exceptions (i.e. as if someone had called fedisableexcept (FE_ALL_EXCEPT)).
This is because the FNSTENV instruction is documented as
"Saves the current FPU operating environment at the memory
location specified with the destination operand, and then
masks all floating-point exceptions."
The mistake came from my initial proposed fix of BZ 30990. Sorry about that.
Here's a proposed fix, on top of your patch. I've verified that the sequence
of instructions
__asm__ ("fnstenv %0" : "=m" (*&temp));
__asm__ volatile ("fldcw %0" : : "m" (*&temp.__control_word));
does restore the exceptions trapping bits.
Bruno
On 30/10/23 12:21, Bruno Haible wrote:
> Hi Adhemerval,
>
> In the error case, the new code mistakenly masks all floating-point
> exceptions (i.e. as if someone had called fedisableexcept (FE_ALL_EXCEPT)).
> This is because the FNSTENV instruction is documented as
> "Saves the current FPU operating environment at the memory
> location specified with the destination operand, and then
> masks all floating-point exceptions."
>
> The mistake came from my initial proposed fix of BZ 30990. Sorry about that.
>
> Here's a proposed fix, on top of your patch. I've verified that the sequence
> of instructions
> __asm__ ("fnstenv %0" : "=m" (*&temp));
> __asm__ volatile ("fldcw %0" : : "m" (*&temp.__control_word));
> does restore the exceptions trapping bits.
>
> Bruno
Right, it would be good to have a regression check for this. I will update
the patch.
@@ -19,6 +19,7 @@
#include <fenv.h>
#include <stdio.h>
#include <math-tests.h>
+#include <math-barriers.h>
static int
do_test (void)
@@ -43,6 +44,16 @@ do_test (void)
where setting the exception might result in traps the function should
return a nonzero value. */
ret = fesetexcept (FE_ALL_EXCEPT);
+
+ /* 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)
puts ("fesetexcept (FE_ALL_EXCEPT) succeeded");
else if (!EXCEPTION_SET_FORCES_TRAP)
@@ -17,15 +17,48 @@
<https://www.gnu.org/licenses/>. */
#include <fenv.h>
+#include <ldsodefs.h>
int
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));
- temp.__status_word |= excepts & FE_ALL_EXCEPT;
- __asm__ ("fldenv %0" : : "m" (*&temp));
+ excepts &= FE_ALL_EXCEPT;
+
+ if (CPU_FEATURE_USABLE (SSE))
+ {
+ /* And now similarly for SSE. */
+ 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;
+
+ __asm__ ("fnstenv %0" : "=m" (*&temp));
+
+ /* Clear or set relevant flags. */
+ temp.__status_word |= 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. */
+ return -1;
+
+ /* Store the new status word (along with the rest of the environment). */
+ __asm__ ("fldenv %0" : : "m" (*&temp));
+ }
return 0;
}
new file mode 100644
@@ -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. */