From a82e46e521e34fc220667956a9b7d9f43bf73ac2 Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Thu, 14 Jul 2022 19:31:31 +0200 Subject: [PATCH] [CRT_APITEST] Add tests for floating point control and status functions --- modules/rostests/apitests/crt/fpcontrol.c | 285 ++++++++++++++++++ .../apitests/crt/msvcrt_crt_apitest.cmake | 7 +- .../apitests/crt/static_crt_apitest.cmake | 1 + modules/rostests/apitests/crt/testlist.c | 4 + 4 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 modules/rostests/apitests/crt/fpcontrol.c diff --git a/modules/rostests/apitests/crt/fpcontrol.c b/modules/rostests/apitests/crt/fpcontrol.c new file mode 100644 index 00000000000..d86d989bd67 --- /dev/null +++ b/modules/rostests/apitests/crt/fpcontrol.c @@ -0,0 +1,285 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Tests for fp control functions + * COPYRIGHT: Copyright 2022 Timo Kreuzer + */ + +#include +#define WIN32_NO_STATUS +#include + +#include +#include +#include +#include + + +unsigned int get_native_fpcw(void) +{ +#ifdef _M_AMD64 + return _mm_getcsr(); +#elif defined (_M_IX86) + unsigned short fpcw; +#if defined(_MSC_VER) + __asm fstsw[fpcw]; +#else + __asm__ __volatile__("fstsw %0" : "=m" (fpcw) : ); +#endif + return fpcw; +#else + #error "Unsupported architecture" + return 0; +#endif +} + +void set_native_fpcw(unsigned int value) +{ +#ifdef _M_AMD64 + _mm_setcsr(value); +#elif defined (_M_IX86) + unsigned short fpcw = (unsigned short)value; +#if defined(_MSC_VER) + __asm fldcw[fpcw]; +#else + __asm__ __volatile__("fldcw %0" : : "m" (fpcw)); +#endif +#else +#error "Unsupported architecture" +#endif +} + +/* + _clear87 + _clearfp + _controlfp_s + _set_controlfp + _statusfp + __control87_2 +*/ + +#ifdef _M_IX86 +#define ON_IX86(x) x +#else +#define ON_IX86(x) +#endif + +#ifdef _M_AMD64 +#define ON_AMD64(x) x +#else +#define ON_AMD64(x) +#endif + +#ifdef _M_ARM +#define ON_ARM(x) x +#else +#define ON_ARM(x) +#endif + +struct +{ + unsigned int Value; + unsigned int Mask; + unsigned int Result; + unsigned int Native; +} g_controlfp_Testcases[] = +{ + { 0xffffffff, 0xffffffff, ON_IX86(0x30e031f) ON_AMD64(0x308031f) ON_ARM(0), ON_IX86(0) ON_AMD64(0xff80) ON_ARM(0) }, + { 0, 0xffffffff, 0x80000, ON_IX86(0) ON_AMD64(0x100) ON_ARM(0) }, + { 0xffffffff, 0x14, 0x80014, ON_IX86(0) ON_AMD64(0x580) ON_ARM(0) }, + { _EM_INEXACT, 0xffffffff, _EM_INEXACT | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_INEXACT | _MM_MASK_DENORM) ON_ARM(0) }, + { _EM_UNDERFLOW, 0xffffffff, _EM_UNDERFLOW | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_UNDERFLOW | _MM_MASK_DENORM) ON_ARM(0) }, + { _EM_OVERFLOW, 0xffffffff, _EM_OVERFLOW | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_OVERFLOW | _MM_MASK_DENORM) ON_ARM(0) }, + { _EM_ZERODIVIDE, 0xffffffff, _EM_ZERODIVIDE | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_DIV_ZERO | _MM_MASK_DENORM) ON_ARM(0) }, + { _EM_INVALID, 0xffffffff, _EM_INVALID | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_INVALID | _MM_MASK_DENORM) ON_ARM(0) }, + { _RC_NEAR, 0xffffffff, _RC_NEAR | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_NEAREST | _MM_MASK_DENORM) ON_ARM(0) }, + { _RC_DOWN, 0xffffffff, _RC_DOWN | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_DOWN | _MM_MASK_DENORM) ON_ARM(0) }, + { _RC_UP, 0xffffffff, _RC_UP | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_UP | _MM_MASK_DENORM) ON_ARM(0) }, + { _RC_CHOP, 0xffffffff, _RC_CHOP | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_TOWARD_ZERO | _MM_MASK_DENORM) ON_ARM(0) }, + { _IC_AFFINE, 0xffffffff, _EM_DENORMAL ON_IX86(| _IC_AFFINE), ON_IX86(0) ON_AMD64(_MM_MASK_DENORM) ON_ARM(0)}, + { _IC_PROJECTIVE, 0xffffffff, _IC_PROJECTIVE | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_DENORM) ON_ARM(0) }, + { _DN_SAVE, 0xffffffff, _DN_SAVE | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_DENORM) ON_ARM(0) }, + { _DN_FLUSH, 0xffffffff, _DN_FLUSH | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_FLUSH_ZERO_ON | 0x40 | _MM_MASK_DENORM) ON_ARM(0) }, + { _DN_FLUSH_OPERANDS_SAVE_RESULTS, 0xffffffff, _DN_FLUSH_OPERANDS_SAVE_RESULTS | _EM_DENORMAL, ON_IX86(0) ON_AMD64(0x40 | _MM_MASK_DENORM) ON_ARM(0) }, + { _DN_SAVE_OPERANDS_FLUSH_RESULTS, 0xffffffff, _DN_SAVE_OPERANDS_FLUSH_RESULTS | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_FLUSH_ZERO_ON | _MM_MASK_DENORM) ON_ARM(0) }, +}; + +void Test_controlfp(void) +{ + unsigned int i, native_fpcw, fpcw; + + for (i = 0; i < _countof(g_controlfp_Testcases); i++) + { + fpcw = _controlfp(g_controlfp_Testcases[i].Value, g_controlfp_Testcases[i].Mask); + ok(fpcw == g_controlfp_Testcases[i].Result, "[%u] _controlfp failed: expected 0x%x, got 0x%x\n", i, g_controlfp_Testcases[i].Result, fpcw); + native_fpcw = get_native_fpcw(); + ok(native_fpcw == g_controlfp_Testcases[i].Native, "[%u] wrong native_fpcw: expected 0x%x, got 0x%x\n", i, g_controlfp_Testcases[i].Native, native_fpcw); + } + + /* Restore sane state */ + _fpreset(); +} + +#if defined(_M_IX86) || defined(_M_AMD64) +void Test_control87(void) +{ + unsigned int native_fpcw, fpcw; + + fpcw = _control87(0, 0xffffffff); + ok(fpcw == 0, "_control87 failed: expected 0x%x, got 0x%x\n", 0, fpcw); + native_fpcw = get_native_fpcw(); + ok_hex(native_fpcw, ON_IX86(0) ON_AMD64(0)); + + /* Restore sane state */ + _fpreset(); +} +#endif + +typedef enum _FP_OP +{ + OP_Inexact, + OP_Underflow, + OP_Overflow, + OP_ZeroDivide, + OP_Invalid, + OP_Denormal +} FP_OP; + +struct +{ + FP_OP Operation; + unsigned int Fpcw; + unsigned int FpStatus; + unsigned int ExceptionCode; + unsigned int Native; +} g_exception_Testcases[] = +{ + { OP_Inexact, 0xffffffff, _SW_UNDERFLOW | _SW_INEXACT ON_IX86(| _SW_DENORMAL), 0, ON_IX86(0x32) ON_AMD64(0xffb0) ON_ARM(0)}, + { OP_Inexact, ~_EM_INEXACT, _SW_INEXACT ON_AMD64(| _SW_UNDERFLOW), STATUS_FLOAT_INEXACT_RESULT, ON_IX86(0x3800) ON_AMD64(0xefb0) ON_ARM(0)}, + { OP_Inexact, ~_MCW_EM, _SW_INEXACT ON_AMD64(| _SW_UNDERFLOW), ON_IX86(STATUS_FLOAT_INEXACT_RESULT) ON_AMD64(STATUS_FLOAT_UNDERFLOW) ON_ARM(STATUS_FLOAT_UNDERFLOW), ON_IX86(0x3800) ON_AMD64(0xe130) ON_ARM(0) }, + { OP_Underflow, 0xffffffff, _SW_UNDERFLOW | _SW_INEXACT, 0, ON_IX86(0x30) ON_AMD64(0xffb0) ON_ARM(0)}, + { OP_Underflow, ~_EM_UNDERFLOW, _SW_UNDERFLOW | _SW_INEXACT, STATUS_FLOAT_UNDERFLOW, ON_IX86(0x3800) ON_AMD64(0xf7b0) ON_ARM(0) }, + { OP_Underflow, ~_MCW_EM, _SW_INEXACT ON_AMD64(| _SW_UNDERFLOW), ON_IX86(STATUS_FLOAT_INEXACT_RESULT) ON_AMD64(STATUS_FLOAT_UNDERFLOW) ON_ARM(STATUS_FLOAT_UNDERFLOW), ON_IX86(0x3800) ON_AMD64(0xe130) ON_ARM(0) }, + { OP_Overflow, 0xffffffff, _SW_OVERFLOW | _SW_INEXACT, 0, ON_IX86(0x28) ON_AMD64(0xffa8) ON_ARM(0) }, + { OP_Overflow, ~_EM_OVERFLOW, _SW_OVERFLOW | _SW_INEXACT, STATUS_FLOAT_OVERFLOW, ON_IX86(0x3800) ON_AMD64(0xfba8) ON_ARM(0) }, + { OP_Overflow, ~_MCW_EM, _SW_INEXACT ON_AMD64(| _SW_OVERFLOW), ON_IX86(STATUS_FLOAT_INEXACT_RESULT) ON_AMD64(STATUS_FLOAT_OVERFLOW) ON_ARM(STATUS_FLOAT_OVERFLOW), ON_IX86(0x3800) ON_AMD64(0xe128) ON_ARM(0)}, + { OP_ZeroDivide, 0xffffffff, _SW_ZERODIVIDE, 0, ON_IX86(0x4) ON_AMD64(0xff84) ON_ARM(0) }, + { OP_ZeroDivide, ~_EM_ZERODIVIDE, _SW_ZERODIVIDE, STATUS_FLOAT_DIVIDE_BY_ZERO, ON_IX86(0x3000) ON_AMD64(0xfd84) ON_ARM(0) }, + { OP_ZeroDivide, ~_MCW_EM, _SW_ZERODIVIDE, STATUS_FLOAT_DIVIDE_BY_ZERO, ON_IX86(0x3000) ON_AMD64(0xe104) ON_ARM(0) }, + { OP_Invalid, 0xffffffff, _SW_INVALID, 0, ON_IX86(0x1) ON_AMD64(0xff81) ON_ARM(0) }, + { OP_Invalid, ~_EM_INVALID, _SW_INVALID, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0) ON_AMD64(0xff01) ON_ARM(0) }, + { OP_Invalid, ~_MCW_EM, _SW_INVALID, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0) ON_AMD64(0xe101) ON_ARM(0) }, +#if defined(_M_IX86) || defined(_M_AMD64) // || defined(_M_ARM64) ? + { OP_Denormal, 0xffffffff, _SW_DENORMAL | _SW_INEXACT ON_AMD64(| _SW_UNDERFLOW), 0, ON_IX86(0x22) ON_AMD64(0xffb2) ON_ARM(0)}, + { OP_Denormal, ~_EM_DENORMAL, _SW_DENORMAL, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0x3800) ON_AMD64(0xfe82) ON_ARM(0) }, + { OP_Denormal, ~_MCW_EM, _SW_DENORMAL, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0x3800) ON_AMD64(0xe002) ON_ARM(0) }, +#endif +}; + +void Test_exceptions(void) +{ + volatile double a, b; + unsigned long long ull; + volatile long status = 0; + + unsigned int i, exp_fpstatus, native_fpcw, statusfp; + + for (i = 0; i < _countof(g_exception_Testcases); i++) + { + /* Start clean */ + status = 0; + _fpreset(); + _clearfp(); + ok_hex(_statusfp(), 0); + + _controlfp(g_exception_Testcases[i].Fpcw, 0xffffffff); +#if defined(_M_IX86) || defined(_M_AMD64) // || defined(_M_ARM64) ? + if (g_exception_Testcases[i].Operation == OP_Denormal) + _control87(g_exception_Testcases[i].Fpcw, 0xffffffff); +#endif + + _SEH2_TRY + { + switch (g_exception_Testcases[i].Operation) + { + case OP_Inexact: + a = 1e-40; + b = (float)(a + 1e-40); + break; + case OP_Underflow: + a = DBL_MIN; + b = a / 3.0e16; + break; + case OP_Overflow: + a = DBL_MAX; + b = a * 3.0; + break; + case OP_ZeroDivide: + a = 0.0; + b = 1.0 / a; + break; + case OP_Invalid: + ull = 0x7FF0000000000001ull; + a = *(double*)&ull; + b = a * 2; + break; + case OP_Denormal: + a = DBL_MIN; + b = a - 4.9406564584124654e-324; + break; + default: + (void)b; + } + native_fpcw = get_native_fpcw(); + statusfp = _clearfp(); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { +#ifdef _M_IX86 + /* On x86 we need to clear before doing any other fp operations, otherwise it will throw again */ + statusfp = _clearfp(); + native_fpcw = get_native_fpcw(); +#else + native_fpcw = get_native_fpcw(); + statusfp = _clearfp(); +#endif + status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + + exp_fpstatus = g_exception_Testcases[i].FpStatus; + ok(statusfp == exp_fpstatus, "[%u] Wrong value for _statusfp(). Expected 0x%lx, got 0x%lx\n", i, exp_fpstatus, statusfp); + ok(status == g_exception_Testcases[i].ExceptionCode, "[%u] Wrong value for status. Expected 0x%lx, got 0x%lx\n", i, g_exception_Testcases[i].ExceptionCode, status); + ok(native_fpcw == g_exception_Testcases[i].Native, "[%u] wrong native_fpcw: expected 0x%x, got 0x%x\n", i, g_exception_Testcases[i].Native, native_fpcw); + } +} + +START_TEST(fpcontrol) +{ + unsigned int native_fpcw, fpcw, fpstatus; + + /* Test native start fpcw */ + native_fpcw = get_native_fpcw(); + ok_hex(native_fpcw, ON_IX86(0) ON_AMD64(0x1f80) ON_ARM(0) ); + + /* Test start fpcw */ + fpcw = _controlfp(0, 0); + ok_hex(fpcw, ON_IX86(0x9001f) ON_AMD64(0x8001f) ON_ARM(0)); + + /* Test start status */ + fpstatus = _statusfp(); + ok_hex(fpstatus, 0); + + /* Test _fpreset */ + fpcw = _controlfp(0, 0xffffffff); + ok_hex(fpcw, 0x80000); + _fpreset(); + fpcw = _controlfp(0, 0); + ok_hex(fpcw, ON_IX86(0x9001f) ON_AMD64(0x8001f) ON_ARM(0)); + + Test_controlfp(); +#if defined(_M_IX86) || defined(_M_AMD64) + Test_control87(); +#endif + Test_exceptions(); +} diff --git a/modules/rostests/apitests/crt/msvcrt_crt_apitest.cmake b/modules/rostests/apitests/crt/msvcrt_crt_apitest.cmake index 58a598bb6fb..87fe0a611a2 100644 --- a/modules/rostests/apitests/crt/msvcrt_crt_apitest.cmake +++ b/modules/rostests/apitests/crt/msvcrt_crt_apitest.cmake @@ -1,5 +1,6 @@ list(APPEND SOURCE_MSVCRT + fpcontrol.c # _CrtCheckMemory.c # _CrtDbgBreak.c # _CrtDbgReport.c @@ -168,13 +169,9 @@ list(APPEND SOURCE_MSVCRT # _chsize_s # _chvalidator # _chvalidator_l -# _clearfp.c # _close.c # _commit.c # _commode -# _control87.c -# _controlfp.c -# _controlfp_s.c # _copysign.c # _cprintf.c # _cprintf_l @@ -665,7 +662,6 @@ list(APPEND SOURCE_MSVCRT # _searchenv.c # _searchenv_s.c # _set_SSE2_enable -# _set_controlfp # _set_doserrno.c # _set_errno.c # _set_error_mode.c @@ -717,7 +713,6 @@ list(APPEND SOURCE_MSVCRT # _stat.c # _stat64.c # _stati64.c -# _statusfp.c # _strcmpi.c # _strcoll_l # _strdate.c diff --git a/modules/rostests/apitests/crt/static_crt_apitest.cmake b/modules/rostests/apitests/crt/static_crt_apitest.cmake index cb1cfaac48e..a6c905d9396 100644 --- a/modules/rostests/apitests/crt/static_crt_apitest.cmake +++ b/modules/rostests/apitests/crt/static_crt_apitest.cmake @@ -8,6 +8,7 @@ list(APPEND SOURCE_STATIC _vsnwprintf.c atexit.c fabs.c + fpcontrol.c mbstowcs.c mbtowc.c sprintf.c diff --git a/modules/rostests/apitests/crt/testlist.c b/modules/rostests/apitests/crt/testlist.c index 16a3a9d394c..e85222f671c 100644 --- a/modules/rostests/apitests/crt/testlist.c +++ b/modules/rostests/apitests/crt/testlist.c @@ -18,6 +18,7 @@ extern void func___64tof(void); #if defined(TEST_NTDLL) extern void func__vscwprintf(void); #endif +extern void func_fpcontrol(void); extern void func_fabs(void); extern void func_fputc(void); extern void func_fputwc(void); @@ -61,6 +62,9 @@ const struct test winetest_testlist[] = // ... #endif #if defined(TEST_STATIC_CRT) || defined(TEST_MSVCRT) +#ifdef _M_AMD64 // x86 / arm need fixing + { "fpcontrol", func_fpcontrol }, +#endif { "fabs", func_fabs }, #if defined(_M_ARM) { "__rt_div", func___rt_div },