1 /*
2  * PROJECT:     ReactOS api tests
3  * LICENSE:     MIT (https://spdx.org/licenses/MIT)
4  * PURPOSE:     Tests for fp control functions
5  * COPYRIGHT:   Copyright 2022 Timo Kreuzer <timo.kreuzer@reactos.org>
6  */
7 
8 #include <ntstatus.h>
9 #define WIN32_NO_STATUS
10 #include <windows.h>
11 
12 #include <apitest.h>
13 #include <xmmintrin.h>
14 #include <float.h>
15 #include <pseh/pseh2.h>
16 
17 
18 unsigned int get_native_fpcw(void)
19 {
20 #ifdef _M_AMD64
21     return _mm_getcsr();
22 #elif defined (_M_IX86)
23     unsigned short fpcw;
24 #if defined(_MSC_VER)
25     __asm fstsw[fpcw];
26 #else
27     __asm__ __volatile__("fstsw %0" : "=m" (fpcw) : );
28 #endif
29     return fpcw;
30 #else
31     #error "Unsupported architecture"
32     return 0;
33 #endif
34 }
35 
36 void set_native_fpcw(unsigned int value)
37 {
38 #ifdef _M_AMD64
39     _mm_setcsr(value);
40 #elif defined (_M_IX86)
41     unsigned short fpcw = (unsigned short)value;
42 #if defined(_MSC_VER)
43     __asm fldcw[fpcw];
44 #else
45     __asm__ __volatile__("fldcw %0" : : "m" (fpcw));
46 #endif
47 #else
48 #error "Unsupported architecture"
49 #endif
50 }
51 
52 /*
53     _clear87
54     _clearfp
55     _controlfp_s
56     _set_controlfp
57     _statusfp
58     __control87_2
59 */
60 
61 #ifdef _M_IX86
62 #define ON_IX86(x) x
63 #else
64 #define ON_IX86(x)
65 #endif
66 
67 #ifdef _M_AMD64
68 #define ON_AMD64(x) x
69 #else
70 #define ON_AMD64(x)
71 #endif
72 
73 #ifdef _M_ARM
74 #define ON_ARM(x) x
75 #else
76 #define ON_ARM(x)
77 #endif
78 
79 struct
80 {
81     unsigned int Value;
82     unsigned int Mask;
83     unsigned int Result;
84     unsigned int Native;
85 } g_controlfp_Testcases[] =
86 {
87     { 0xffffffff, 0xffffffff, ON_IX86(0x30e031f) ON_AMD64(0x308031f) ON_ARM(0), ON_IX86(0) ON_AMD64(0xff80) ON_ARM(0) },
88     { 0, 0xffffffff, 0x80000, ON_IX86(0) ON_AMD64(0x100) ON_ARM(0) },
89     { 0xffffffff, 0x14, 0x80014, ON_IX86(0) ON_AMD64(0x580) ON_ARM(0) },
90     { _EM_INEXACT, 0xffffffff, _EM_INEXACT | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_INEXACT | _MM_MASK_DENORM) ON_ARM(0) },
91     { _EM_UNDERFLOW, 0xffffffff, _EM_UNDERFLOW | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_UNDERFLOW | _MM_MASK_DENORM) ON_ARM(0) },
92     { _EM_OVERFLOW, 0xffffffff, _EM_OVERFLOW | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_OVERFLOW | _MM_MASK_DENORM) ON_ARM(0) },
93     { _EM_ZERODIVIDE, 0xffffffff, _EM_ZERODIVIDE | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_DIV_ZERO | _MM_MASK_DENORM) ON_ARM(0) },
94     { _EM_INVALID, 0xffffffff, _EM_INVALID | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_INVALID | _MM_MASK_DENORM) ON_ARM(0) },
95     { _RC_NEAR, 0xffffffff, _RC_NEAR | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_NEAREST | _MM_MASK_DENORM) ON_ARM(0) },
96     { _RC_DOWN, 0xffffffff, _RC_DOWN | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_DOWN | _MM_MASK_DENORM) ON_ARM(0) },
97     { _RC_UP, 0xffffffff, _RC_UP | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_UP | _MM_MASK_DENORM) ON_ARM(0) },
98     { _RC_CHOP, 0xffffffff, _RC_CHOP | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_ROUND_TOWARD_ZERO | _MM_MASK_DENORM) ON_ARM(0) },
99     { _IC_AFFINE, 0xffffffff, _EM_DENORMAL ON_IX86(| _IC_AFFINE), ON_IX86(0) ON_AMD64(_MM_MASK_DENORM) ON_ARM(0)},
100     { _IC_PROJECTIVE, 0xffffffff, _IC_PROJECTIVE | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_DENORM) ON_ARM(0) },
101     { _DN_SAVE, 0xffffffff, _DN_SAVE | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_MASK_DENORM) ON_ARM(0) },
102     { _DN_FLUSH, 0xffffffff, _DN_FLUSH | _EM_DENORMAL, ON_IX86(0) ON_AMD64(_MM_FLUSH_ZERO_ON | 0x40 | _MM_MASK_DENORM) ON_ARM(0) },
103     { _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) },
104     { _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) },
105 };
106 
107 void Test_controlfp(void)
108 {
109     unsigned int i, native_fpcw, fpcw;
110 
111     for (i = 0; i < _countof(g_controlfp_Testcases); i++)
112     {
113         fpcw = _controlfp(g_controlfp_Testcases[i].Value, g_controlfp_Testcases[i].Mask);
114         ok(fpcw == g_controlfp_Testcases[i].Result, "[%u] _controlfp failed: expected 0x%x, got 0x%x\n", i, g_controlfp_Testcases[i].Result, fpcw);
115         native_fpcw = get_native_fpcw();
116         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);
117     }
118 
119     /* Restore sane state */
120     _fpreset();
121 }
122 
123 #if defined(_M_IX86) || defined(_M_AMD64)
124 void Test_control87(void)
125 {
126     unsigned int native_fpcw, fpcw;
127 
128     fpcw = _control87(0, 0xffffffff);
129     ok(fpcw == 0, "_control87 failed: expected 0x%x, got 0x%x\n", 0, fpcw);
130     native_fpcw = get_native_fpcw();
131     ok_hex(native_fpcw, ON_IX86(0) ON_AMD64(0));
132 
133     /* Restore sane state */
134     _fpreset();
135 }
136 #endif
137 
138 typedef enum _FP_OP
139 {
140     OP_Inexact,
141     OP_Underflow,
142     OP_Overflow,
143     OP_ZeroDivide,
144     OP_Invalid,
145     OP_Denormal
146 } FP_OP;
147 
148 struct
149 {
150     FP_OP Operation;
151     unsigned int Fpcw;
152     unsigned int FpStatus;
153     unsigned int ExceptionCode;
154     unsigned int Native;
155 } g_exception_Testcases[] =
156 {
157     { OP_Inexact, 0xffffffff, _SW_UNDERFLOW | _SW_INEXACT ON_IX86(| _SW_DENORMAL), 0, ON_IX86(0x32) ON_AMD64(0xffb0) ON_ARM(0)},
158     { OP_Inexact, ~_EM_INEXACT, _SW_INEXACT ON_AMD64(| _SW_UNDERFLOW), STATUS_FLOAT_INEXACT_RESULT, ON_IX86(0x3800) ON_AMD64(0xefb0) ON_ARM(0)},
159     { 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) },
160     { OP_Underflow, 0xffffffff, _SW_UNDERFLOW | _SW_INEXACT, 0, ON_IX86(0x30) ON_AMD64(0xffb0) ON_ARM(0)},
161     { OP_Underflow, ~_EM_UNDERFLOW, _SW_UNDERFLOW | _SW_INEXACT, STATUS_FLOAT_UNDERFLOW, ON_IX86(0x3800) ON_AMD64(0xf7b0) ON_ARM(0) },
162     { 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) },
163     { OP_Overflow, 0xffffffff, _SW_OVERFLOW | _SW_INEXACT, 0, ON_IX86(0x28) ON_AMD64(0xffa8) ON_ARM(0) },
164     { OP_Overflow, ~_EM_OVERFLOW, _SW_OVERFLOW | _SW_INEXACT, STATUS_FLOAT_OVERFLOW, ON_IX86(0x3800) ON_AMD64(0xfba8) ON_ARM(0) },
165     { 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)},
166     { OP_ZeroDivide, 0xffffffff, _SW_ZERODIVIDE, 0, ON_IX86(0x4) ON_AMD64(0xff84) ON_ARM(0) },
167     { OP_ZeroDivide, ~_EM_ZERODIVIDE, _SW_ZERODIVIDE, STATUS_FLOAT_DIVIDE_BY_ZERO, ON_IX86(0x3000) ON_AMD64(0xfd84) ON_ARM(0) },
168     { OP_ZeroDivide, ~_MCW_EM, _SW_ZERODIVIDE, STATUS_FLOAT_DIVIDE_BY_ZERO, ON_IX86(0x3000) ON_AMD64(0xe104) ON_ARM(0) },
169     { OP_Invalid, 0xffffffff, _SW_INVALID, 0, ON_IX86(0x1) ON_AMD64(0xff81) ON_ARM(0) },
170     { OP_Invalid, ~_EM_INVALID, _SW_INVALID, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0) ON_AMD64(0xff01) ON_ARM(0) },
171     { OP_Invalid, ~_MCW_EM, _SW_INVALID, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0) ON_AMD64(0xe101) ON_ARM(0) },
172 #if defined(_M_IX86) || defined(_M_AMD64) // || defined(_M_ARM64) ?
173     { OP_Denormal, 0xffffffff, _SW_DENORMAL | _SW_INEXACT ON_AMD64(| _SW_UNDERFLOW), 0, ON_IX86(0x22) ON_AMD64(0xffb2) ON_ARM(0)},
174     { OP_Denormal, ~_EM_DENORMAL, _SW_DENORMAL, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0x3800) ON_AMD64(0xfe82) ON_ARM(0) },
175     { OP_Denormal, ~_MCW_EM, _SW_DENORMAL, STATUS_FLOAT_INVALID_OPERATION, ON_IX86(0x3800) ON_AMD64(0xe002) ON_ARM(0) },
176 #endif
177 };
178 
179 void Test_exceptions(void)
180 {
181     volatile double a, b;
182     unsigned long long ull;
183     volatile long status = 0;
184 
185     unsigned int i, exp_fpstatus, native_fpcw, statusfp;
186 
187     for (i = 0; i < _countof(g_exception_Testcases); i++)
188     {
189         /* Start clean */
190         status = 0;
191         _fpreset();
192         _clearfp();
193         ok_hex(_statusfp(), 0);
194 
195         _controlfp(g_exception_Testcases[i].Fpcw, 0xffffffff);
196 #if defined(_M_IX86) || defined(_M_AMD64) // || defined(_M_ARM64) ?
197         if (g_exception_Testcases[i].Operation == OP_Denormal)
198             _control87(g_exception_Testcases[i].Fpcw, 0xffffffff);
199 #endif
200 
201         _SEH2_TRY
202         {
203             switch (g_exception_Testcases[i].Operation)
204             {
205                 case OP_Inexact:
206                     a = 1e-40;
207                     b = (float)(a + 1e-40);
208                     break;
209                 case OP_Underflow:
210                     a = DBL_MIN;
211                     b = a / 3.0e16;
212                     break;
213                 case OP_Overflow:
214                     a = DBL_MAX;
215                     b = a * 3.0;
216                     break;
217                 case OP_ZeroDivide:
218                     a = 0.0;
219                     b = 1.0 / a;
220                     break;
221                 case OP_Invalid:
222                     ull = 0x7FF0000000000001ull;
223                     a = *(double*)&ull;
224                     b = a * 2;
225                     break;
226                 case OP_Denormal:
227                     a = DBL_MIN;
228                     b = a - 4.9406564584124654e-324;
229                     break;
230                 default:
231                     (void)b;
232             }
233             native_fpcw = get_native_fpcw();
234             statusfp = _clearfp();
235         }
236         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
237         {
238 #ifdef _M_IX86
239             /* On x86 we need to clear before doing any other fp operations, otherwise it will throw again */
240             statusfp = _clearfp();
241             native_fpcw = get_native_fpcw();
242 #else
243             native_fpcw = get_native_fpcw();
244             statusfp = _clearfp();
245 #endif
246             status = _SEH2_GetExceptionCode();
247         }
248         _SEH2_END;
249 
250         exp_fpstatus = g_exception_Testcases[i].FpStatus;
251         ok(statusfp == exp_fpstatus, "[%u] Wrong value for _statusfp(). Expected 0x%lx, got 0x%lx\n", i, exp_fpstatus, statusfp);
252         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);
253         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);
254     }
255 }
256 
257 START_TEST(fpcontrol)
258 {
259     unsigned int native_fpcw, fpcw, fpstatus;
260 
261     /* Test native start fpcw */
262     native_fpcw = get_native_fpcw();
263     ok_hex(native_fpcw, ON_IX86(0) ON_AMD64(0x1f80) ON_ARM(0) );
264 
265     /* Test start fpcw */
266     fpcw = _controlfp(0, 0);
267     ok_hex(fpcw, ON_IX86(0x9001f) ON_AMD64(0x8001f) ON_ARM(0));
268 
269     /* Test start status */
270     fpstatus = _statusfp();
271     ok_hex(fpstatus, 0);
272 
273     /* Test _fpreset */
274     fpcw = _controlfp(0, 0xffffffff);
275     ok_hex(fpcw, 0x80000);
276     _fpreset();
277     fpcw = _controlfp(0, 0);
278     ok_hex(fpcw, ON_IX86(0x9001f) ON_AMD64(0x8001f) ON_ARM(0));
279 
280     Test_controlfp();
281 #if defined(_M_IX86) || defined(_M_AMD64)
282     Test_control87();
283 #endif
284     Test_exceptions();
285 }
286