This patch improves the fenv-rounding code for MSVC: - It works around the fact that the values of FE_UPWARD and FE_DOWNWARD have been swapped. - Since MSVC uses only the SSE unit and delegates no arithmetic operations to the 387 unit (with its excess precision flaws), it's sufficient to set and look up the rounding mode only in the SSE unit. Relying on the closed-source fegetenv() and fesetenv() implementations for this purpose is more fragile than needed.
It thus fixes unit test failures on MSVC 14 or 14.30. 2023-11-04 Bruno Haible <br...@clisp.org> fenv-rounding: Improve code for MSVC. * lib/fenv-round.c (fegetround) [MSVC]: Use the rounding direction from the SSE unit. Don't assume stable values for FE_UPWARD and FE_DOWNWARD. (fesetround) [MSVC]: Set the rounding direction only in the SSE unit. Don't assume stable values for FE_UPWARD and FE_DOWNWARD. diff --git a/lib/fenv-round.c b/lib/fenv-round.c index 14048a5ca7..5489ac98a8 100644 --- a/lib/fenv-round.c +++ b/lib/fenv-round.c @@ -32,11 +32,10 @@ int fegetround (void) { # ifdef _MSC_VER - /* XXX Simplify: access the SSE unit. */ - fenv_t env; - if (fegetenv (&env) != 0) - return FE_TONEAREST; - unsigned int fctrl = env._Fe_ctl; + /* Use the rounding direction from the SSE unit. */ + unsigned int mxcsr; + _FPU_GETSSECW (mxcsr); + unsigned int fctrl = (mxcsr >> 3) & 0x0C00; # else /* Use the rounding direction from the control word of the 387 unit, the so-called fctrl register. @@ -47,12 +46,13 @@ fegetround (void) # endif # ifdef _MSC_VER /* The MSVC header files have different values for the rounding directions - than all the other platforms. Map - 0x0000 -> 0x0000 = FE_TONEAREST - 0x0400 -> 0x0200 = FE_DOWNWARD - 0x0800 -> 0x0100 = FE_UPWARD - 0x0C00 -> 0x0300 = FE_TOWARDZERO */ - return ((fctrl & 0x0400) >> 1) | ((fctrl & 0x0800) >> 3); + than all the other platforms, and the even changed between MSVC 14 and + MSVC 14.30 (!). Map + 0x0000 -> FE_TONEAREST = 0 + 0x0400 -> FE_DOWNWARD + 0x0800 -> FE_UPWARD + 0x0C00 -> FE_TOWARDZERO = FE_DOWNWARD | FE_UPWARD */ + return (fctrl & 0x0800 ? FE_UPWARD : 0) | (fctrl & 0x0400 ? FE_DOWNWARD : 0); # else return fctrl & 0x0C00; # endif @@ -66,39 +66,35 @@ fesetround (int rounding_direction) than all the other platforms. */ if ((rounding_direction & ~0x0300) != 0) return -1; - /* Map - FE_TONEAREST = 0x0000 -> 0x0000 - FE_DOWNWARD = 0x0200 -> 0x0400 - FE_UPWARD = 0x0100 -> 0x0800 - FE_TOWARDZERO = 0x0300 -> 0x0C00 */ + /* The MSVC header files have different values for the rounding directions + than all the other platforms, and the even changed between MSVC 14 and + MSVC 14.30 (!). Map + FE_TONEAREST = 0 -> 0x0000 + FE_DOWNWARD -> 0x0400 + FE_UPWARD -> 0x0800 + FE_TOWARDZERO = FE_DOWNWARD | FE_UPWARD -> 0x0C00 */ rounding_direction = - ((rounding_direction & 0x0200) << 1) | ((rounding_direction & 0x0100) << 3); + (rounding_direction & FE_UPWARD ? 0x0800 : 0) + | (rounding_direction & FE_DOWNWARD ? 0x0400 : 0); # else if ((rounding_direction & ~0x0C00) != 0) return -1; # endif - /* Set it in the 387 unit. */ # ifdef _MSC_VER - /* XXX Simplify: only access the SSE unit. */ - fenv_t env; - unsigned long orig_ctl; - if (fegetenv (&env) != 0) - return -1; - orig_ctl = env._Fe_ctl; - env._Fe_ctl = (env._Fe_ctl & ~0x0C00) | rounding_direction; - if (env._Fe_ctl != orig_ctl) - { - if (fesetenv (&env) != 0) - return -1; - } + /* Set it in the SSE unit. */ + unsigned int mxcsr, orig_mxcsr; + _FPU_GETSSECW (orig_mxcsr); + mxcsr = (orig_mxcsr & ~(0x0C00 << 3)) | (rounding_direction << 3); + if (mxcsr != orig_mxcsr) + _FPU_SETSSECW (mxcsr); # else + /* Set it in the 387 unit. */ unsigned short fctrl, orig_fctrl; _FPU_GETCW (orig_fctrl); fctrl = (orig_fctrl & ~0x0C00) | rounding_direction; if (fctrl != orig_fctrl) _FPU_SETCW (fctrl); -# endif if (CPU_HAS_SSE ()) { @@ -109,6 +105,7 @@ fesetround (int rounding_direction) if (mxcsr != orig_mxcsr) _FPU_SETSSECW (mxcsr); } +# endif return 0; }