crt: Fix x86 __mingw_setfp() to not mask all exceptions on noop operation
x87 fnstenv instruction saves the current FPU operating environment at the
memory location specified with the destination operand, and then masks all
floating-point exceptions.
So if the x87 fnstenv instruction was called, it is required to call
follow-up x87 fldenv instruction which will restore previous masking state
of floating-point exceptions. Hence x87 fldenv instruction must be always
called, and not only when new cw or sw value differs from the old one.
__mingw_setfp is currently calling x87 fldenv instruction only when the new
cw or sw value differs from the old one. And this behavior cause breaking
of following code:
fedisableexcept(FE_ALL_EXCEPT);
feenableexcept(FE_INVALID);
feenableexcept(FE_INVALID);
The third line disables all exceptions, including the FE_INVALID.
Co-authored-by: LIU Hao <lh_mouse@126.com>
Signed-off-by: LIU Hao <lh_mouse@126.com>
diff --git a/mingw-w64-crt/misc/mingw_setfp.c b/mingw-w64-crt/misc/mingw_setfp.c
index dcd767d..137a555 100644
--- a/mingw-w64-crt/misc/mingw_setfp.c
+++ b/mingw-w64-crt/misc/mingw_setfp.c
@@ -118,9 +118,9 @@
#if defined(__arm64ec__)
__mingw_setfp_sse(cw, cw_mask, sw, sw_mask);
#elif defined(__i386__) || defined(__x86_64__)
- unsigned long oldcw = 0, newcw = 0;
- unsigned long oldsw = 0, newsw = 0;
+ unsigned long newcw = 0, newsw = 0;
unsigned int flags;
+ int use_fnstenv_fldenv;
struct {
WORD control_word;
WORD unused1;
@@ -139,7 +139,9 @@
cw_mask &= _MCW_EM | _MCW_IC | _MCW_RC | _MCW_PC;
sw_mask &= _MCW_EM;
- if ((!sw || sw_mask == 0) && (!cw || cw_mask == 0))
+ use_fnstenv_fldenv = ((sw && sw_mask != 0) || (cw && cw_mask != 0));
+
+ if (!use_fnstenv_fldenv)
{
/* Fast path: when we are not going to change sw/cw which is indicated
* by zero mask then load sw/cw via fast fnstsw/fnstcw instruction.
@@ -151,15 +153,15 @@
{
/* Slow path: when we are going to change sw/cw or we do not know yet then
* load whole x87 env via slow fnstenv as it is needed for changing sw/cw.
+ * Note that fnstenv masks all floating-point exceptions after storing the
+ * x87 env. And after the fnstenv call, it is always required to restore
+ * masking of previous floating-point exceptions via the fldenv call.
*/
__asm__ __volatile__( "fnstenv %0" : "=m" (fenv) );
newsw = fenv.status_word;
newcw = fenv.control_word;
}
- oldsw = newsw;
- oldcw = newcw;
-
if (sw)
{
flags = 0;
@@ -229,8 +231,10 @@
/* For changing sw/cw always use fldenv.
* Do not use fldcw as it can generate pending floating-point exception.
+ * When the fnstenv was called then it is required to call fldenv to
+ * restore previous floating-point exceptions.
*/
- if (oldsw != newsw || oldcw != newcw)
+ if (use_fnstenv_fldenv)
{
fenv.control_word = newcw;
fenv.status_word = newsw;