1/**
2 * This file has no copyright assigned and is placed in the Public Domain.
3 * This file is part of the mingw-w64 runtime package.
4 * No warranty is given; refer to the file DISCLAIMER.PD within this package.
5 */
6
7#include "internal.h"
8
9#if defined(__i386__) || defined(__x86_64__)
10
11static unsigned int get_mxcsr(void)
12{
13 unsigned int ret;
14#ifdef __arm64ec__
15 extern NTSTATUS (*__os_arm64x_get_x64_information)(ULONG,void*,void*);
16 __os_arm64x_get_x64_information( 0, &ret, NULL );
17#else
18 __asm__ __volatile__( "stmxcsr %0" : "=m" (ret) );
19#endif
20 return ret;
21}
22
23static void set_mxcsr( unsigned int val )
24{
25#ifdef __arm64ec__
26 extern NTSTATUS (*__os_arm64x_set_x64_information)(ULONG,ULONG_PTR,void*);
27 __os_arm64x_set_x64_information( 0, val, NULL );
28#else
29 __asm__ __volatile__( "ldmxcsr %0" : : "m" (val) );
30#endif
31}
32
33void __mingw_setfp_sse( unsigned int *cw, unsigned int cw_mask, unsigned int *sw, unsigned int sw_mask )
34{
35 unsigned int old_fpword, fpword = get_mxcsr();
36 unsigned int flags;
37
38 old_fpword = fpword;
39
40 cw_mask &= _MCW_EM | _MCW_RC | _MCW_DN;
41 sw_mask &= _MCW_EM;
42
43 if (sw)
44 {
45 flags = 0;
46 if (fpword & 0x1) flags |= _SW_INVALID;
47 if (fpword & 0x2) flags |= _SW_DENORMAL;
48 if (fpword & 0x4) flags |= _SW_ZERODIVIDE;
49 if (fpword & 0x8) flags |= _SW_OVERFLOW;
50 if (fpword & 0x10) flags |= _SW_UNDERFLOW;
51 if (fpword & 0x20) flags |= _SW_INEXACT;
52
53 *sw = (flags & ~sw_mask) | (*sw & sw_mask);
54 fpword &= ~0x3f;
55 if (*sw & _SW_INVALID) fpword |= 0x1;
56 if (*sw & _SW_DENORMAL) fpword |= 0x2;
57 if (*sw & _SW_ZERODIVIDE) fpword |= 0x4;
58 if (*sw & _SW_OVERFLOW) fpword |= 0x8;
59 if (*sw & _SW_UNDERFLOW) fpword |= 0x10;
60 if (*sw & _SW_INEXACT) fpword |= 0x20;
61 *sw = flags;
62 }
63
64 if (cw)
65 {
66 flags = 0;
67 if (fpword & 0x80) flags |= _EM_INVALID;
68 if (fpword & 0x100) flags |= _EM_DENORMAL;
69 if (fpword & 0x200) flags |= _EM_ZERODIVIDE;
70 if (fpword & 0x400) flags |= _EM_OVERFLOW;
71 if (fpword & 0x800) flags |= _EM_UNDERFLOW;
72 if (fpword & 0x1000) flags |= _EM_INEXACT;
73 switch (fpword & 0x6000)
74 {
75 case 0x6000: flags |= _RC_UP|_RC_DOWN; break;
76 case 0x4000: flags |= _RC_UP; break;
77 case 0x2000: flags |= _RC_DOWN; break;
78 }
79 switch (fpword & 0x8040)
80 {
81 case 0x0040: flags |= _DN_FLUSH_OPERANDS_SAVE_RESULTS; break;
82 case 0x8000: flags |= _DN_SAVE_OPERANDS_FLUSH_RESULTS; break;
83 case 0x8040: flags |= _DN_FLUSH; break;
84 }
85
86 *cw = (flags & ~cw_mask) | (*cw & cw_mask);
87 fpword &= ~0xffc0;
88 if (*cw & _EM_INVALID) fpword |= 0x80;
89 if (*cw & _EM_DENORMAL) fpword |= 0x100;
90 if (*cw & _EM_ZERODIVIDE) fpword |= 0x200;
91 if (*cw & _EM_OVERFLOW) fpword |= 0x400;
92 if (*cw & _EM_UNDERFLOW) fpword |= 0x800;
93 if (*cw & _EM_INEXACT) fpword |= 0x1000;
94 switch (*cw & _MCW_RC)
95 {
96 case _RC_UP|_RC_DOWN: fpword |= 0x6000; break;
97 case _RC_UP: fpword |= 0x4000; break;
98 case _RC_DOWN: fpword |= 0x2000; break;
99 }
100 switch (*cw & _MCW_DN)
101 {
102 case _DN_FLUSH_OPERANDS_SAVE_RESULTS: fpword |= 0x0040; break;
103 case _DN_SAVE_OPERANDS_FLUSH_RESULTS: fpword |= 0x8000; break;
104 case _DN_FLUSH: fpword |= 0x8040; break;
105 }
106
107 /* clear status word if anything changes */
108 if (fpword != old_fpword && !sw) fpword &= ~0x3f;
109 }
110
111 if (fpword != old_fpword) set_mxcsr( fpword );
112}
113#endif
114
115void __mingw_setfp( unsigned int *cw, unsigned int cw_mask,
116 unsigned int *sw, unsigned int sw_mask )
117{
118#if defined(__arm64ec__)
119 __mingw_setfp_sse(cw, cw_mask, sw, sw_mask);
120#elif defined(__i386__) || defined(__x86_64__)
121 unsigned long newcw = 0, newsw = 0;
122 unsigned int flags;
123 int use_fnstenv_fldenv;
124 struct {
125 WORD control_word;
126 WORD unused1;
127 WORD status_word;
128 WORD unused2;
129 WORD tag_word;
130 WORD unused3;
131 DWORD instruction_pointer;
132 WORD code_segment;
133 WORD unused4;
134 DWORD operand_addr;
135 WORD data_segment;
136 WORD unused5;
137 } fenv;
138
139 cw_mask &= _MCW_EM | _MCW_IC | _MCW_RC | _MCW_PC;
140 sw_mask &= _MCW_EM;
141
142 use_fnstenv_fldenv = ((sw && sw_mask != 0) || (cw && cw_mask != 0));
143
144 if (!use_fnstenv_fldenv)
145 {
146 /* Fast path: when we are not going to change sw/cw which is indicated
147 * by zero mask then load sw/cw via fast fnstsw/fnstcw instruction.
148 */
149 __asm__ __volatile__( "fnstsw %0" : "=m" (newsw) );
150 __asm__ __volatile__( "fnstcw %0" : "=m" (newcw) );
151 }
152 else
153 {
154 /* Slow path: when we are going to change sw/cw or we do not know yet then
155 * load whole x87 env via slow fnstenv as it is needed for changing sw/cw.
156 * Note that fnstenv masks all floating-point exceptions after storing the
157 * x87 env. And after the fnstenv call, it is always required to restore
158 * masking of previous floating-point exceptions via the fldenv call.
159 */
160 __asm__ __volatile__( "fnstenv %0" : "=m" (fenv) );
161 newsw = fenv.status_word;
162 newcw = fenv.control_word;
163 }
164
165 if (sw)
166 {
167 flags = 0;
168 if (newsw & 0x1) flags |= _SW_INVALID;
169 if (newsw & 0x2) flags |= _SW_DENORMAL;
170 if (newsw & 0x4) flags |= _SW_ZERODIVIDE;
171 if (newsw & 0x8) flags |= _SW_OVERFLOW;
172 if (newsw & 0x10) flags |= _SW_UNDERFLOW;
173 if (newsw & 0x20) flags |= _SW_INEXACT;
174
175 *sw = (flags & ~sw_mask) | (*sw & sw_mask);
176 newsw &= ~0x3f;
177 if (*sw & _SW_INVALID) newsw |= 0x1;
178 if (*sw & _SW_DENORMAL) newsw |= 0x2;
179 if (*sw & _SW_ZERODIVIDE) newsw |= 0x4;
180 if (*sw & _SW_OVERFLOW) newsw |= 0x8;
181 if (*sw & _SW_UNDERFLOW) newsw |= 0x10;
182 if (*sw & _SW_INEXACT) newsw |= 0x20;
183 *sw = flags;
184 }
185
186 if (cw)
187 {
188 flags = 0;
189 if (newcw & 0x1) flags |= _EM_INVALID;
190 if (newcw & 0x2) flags |= _EM_DENORMAL;
191 if (newcw & 0x4) flags |= _EM_ZERODIVIDE;
192 if (newcw & 0x8) flags |= _EM_OVERFLOW;
193 if (newcw & 0x10) flags |= _EM_UNDERFLOW;
194 if (newcw & 0x20) flags |= _EM_INEXACT;
195 switch (newcw & 0xc00)
196 {
197 case 0xc00: flags |= _RC_UP|_RC_DOWN; break;
198 case 0x800: flags |= _RC_UP; break;
199 case 0x400: flags |= _RC_DOWN; break;
200 }
201 switch (newcw & 0x300)
202 {
203 case 0x0: flags |= _PC_24; break;
204 case 0x200: flags |= _PC_53; break;
205 case 0x300: flags |= _PC_64; break;
206 }
207 if (newcw & 0x1000) flags |= _IC_AFFINE;
208
209 *cw = (flags & ~cw_mask) | (*cw & cw_mask);
210 newcw &= ~0x1f3f;
211 if (*cw & _EM_INVALID) newcw |= 0x1;
212 if (*cw & _EM_DENORMAL) newcw |= 0x2;
213 if (*cw & _EM_ZERODIVIDE) newcw |= 0x4;
214 if (*cw & _EM_OVERFLOW) newcw |= 0x8;
215 if (*cw & _EM_UNDERFLOW) newcw |= 0x10;
216 if (*cw & _EM_INEXACT) newcw |= 0x20;
217 switch (*cw & _MCW_RC)
218 {
219 case _RC_UP|_RC_DOWN: newcw |= 0xc00; break;
220 case _RC_UP: newcw |= 0x800; break;
221 case _RC_DOWN: newcw |= 0x400; break;
222 }
223 switch (*cw & _MCW_PC)
224 {
225 case _PC_64: newcw |= 0x300; break;
226 case _PC_53: newcw |= 0x200; break;
227 case _PC_24: newcw |= 0x0; break;
228 }
229 if (*cw & _IC_AFFINE) newcw |= 0x1000;
230 }
231
232 /* For changing sw/cw always use fldenv.
233 * Do not use fldcw as it can generate pending floating-point exception.
234 * When the fnstenv was called then it is required to call fldenv to
235 * restore previous floating-point exceptions.
236 */
237 if (use_fnstenv_fldenv)
238 {
239 fenv.control_word = newcw;
240 fenv.status_word = newsw;
241 __asm__ __volatile__( "fldenv %0" : : "m" (fenv) : "st", "st(1)",
242 "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" );
243 }
244#elif defined(__aarch64__)
245 ULONG_PTR old_fpsr = 0, fpsr = 0, old_fpcr = 0, fpcr = 0;
246 unsigned int flags;
247
248 cw_mask &= _MCW_EM | _MCW_RC;
249 sw_mask &= _MCW_EM;
250
251 if (sw)
252 {
253 __asm__ __volatile__( "mrs %0, fpsr" : "=r" (fpsr) );
254 old_fpsr = fpsr;
255
256 flags = 0;
257 if (fpsr & 0x1) flags |= _SW_INVALID;
258 if (fpsr & 0x2) flags |= _SW_ZERODIVIDE;
259 if (fpsr & 0x4) flags |= _SW_OVERFLOW;
260 if (fpsr & 0x8) flags |= _SW_UNDERFLOW;
261 if (fpsr & 0x10) flags |= _SW_INEXACT;
262 if (fpsr & 0x80) flags |= _SW_DENORMAL;
263
264 *sw = (flags & ~sw_mask) | (*sw & sw_mask);
265 fpsr &= ~0x9f;
266 if (*sw & _SW_INVALID) fpsr |= 0x1;
267 if (*sw & _SW_ZERODIVIDE) fpsr |= 0x2;
268 if (*sw & _SW_OVERFLOW) fpsr |= 0x4;
269 if (*sw & _SW_UNDERFLOW) fpsr |= 0x8;
270 if (*sw & _SW_INEXACT) fpsr |= 0x10;
271 if (*sw & _SW_DENORMAL) fpsr |= 0x80;
272 *sw = flags;
273 }
274
275 if (cw)
276 {
277 __asm__ __volatile__( "mrs %0, fpcr" : "=r" (fpcr) );
278 old_fpcr = fpcr;
279
280 flags = 0;
281 if (!(fpcr & 0x100)) flags |= _EM_INVALID;
282 if (!(fpcr & 0x200)) flags |= _EM_ZERODIVIDE;
283 if (!(fpcr & 0x400)) flags |= _EM_OVERFLOW;
284 if (!(fpcr & 0x800)) flags |= _EM_UNDERFLOW;
285 if (!(fpcr & 0x1000)) flags |= _EM_INEXACT;
286 if (!(fpcr & 0x8000)) flags |= _EM_DENORMAL;
287 switch (fpcr & 0xc00000)
288 {
289 case 0x400000: flags |= _RC_UP; break;
290 case 0x800000: flags |= _RC_DOWN; break;
291 case 0xc00000: flags |= _RC_CHOP; break;
292 }
293
294 *cw = (flags & ~cw_mask) | (*cw & cw_mask);
295 fpcr &= ~0xc09f00ul;
296 if (!(*cw & _EM_INVALID)) fpcr |= 0x100;
297 if (!(*cw & _EM_ZERODIVIDE)) fpcr |= 0x200;
298 if (!(*cw & _EM_OVERFLOW)) fpcr |= 0x400;
299 if (!(*cw & _EM_UNDERFLOW)) fpcr |= 0x800;
300 if (!(*cw & _EM_INEXACT)) fpcr |= 0x1000;
301 if (!(*cw & _EM_DENORMAL)) fpcr |= 0x8000;
302 switch (*cw & _MCW_RC)
303 {
304 case _RC_CHOP: fpcr |= 0xc00000; break;
305 case _RC_UP: fpcr |= 0x400000; break;
306 case _RC_DOWN: fpcr |= 0x800000; break;
307 }
308 }
309
310 /* mask exceptions if needed */
311 if (old_fpcr != fpcr && ~(old_fpcr >> 8) & fpsr & 0x9f != fpsr & 0x9f)
312 {
313 ULONG_PTR mask = fpcr & ~0x9f00;
314 __asm__ __volatile__( "msr fpcr, %0" :: "r" (mask) );
315 }
316
317 if (old_fpsr != fpsr)
318 __asm__ __volatile__( "msr fpsr, %0" :: "r" (fpsr) );
319 if (old_fpcr != fpcr)
320 __asm__ __volatile__( "msr fpcr, %0" :: "r" (fpcr) );
321#elif defined(__arm__)
322 DWORD old_fpscr, fpscr;
323 unsigned int flags;
324
325 __asm__ __volatile__( "vmrs %0, fpscr" : "=r" (fpscr) );
326 old_fpscr = fpscr;
327
328 cw_mask &= _MCW_EM | _MCW_RC;
329 sw_mask &= _MCW_EM;
330
331 if (sw)
332 {
333 flags = 0;
334 if (fpscr & 0x1) flags |= _SW_INVALID;
335 if (fpscr & 0x2) flags |= _SW_ZERODIVIDE;
336 if (fpscr & 0x4) flags |= _SW_OVERFLOW;
337 if (fpscr & 0x8) flags |= _SW_UNDERFLOW;
338 if (fpscr & 0x10) flags |= _SW_INEXACT;
339 if (fpscr & 0x80) flags |= _SW_DENORMAL;
340
341 *sw = (flags & ~sw_mask) | (*sw & sw_mask);
342 fpscr &= ~0x9f;
343 if (*sw & _SW_INVALID) fpscr |= 0x1;
344 if (*sw & _SW_ZERODIVIDE) fpscr |= 0x2;
345 if (*sw & _SW_OVERFLOW) fpscr |= 0x4;
346 if (*sw & _SW_UNDERFLOW) fpscr |= 0x8;
347 if (*sw & _SW_INEXACT) fpscr |= 0x10;
348 if (*sw & _SW_DENORMAL) fpscr |= 0x80;
349 *sw = flags;
350 }
351
352 if (cw)
353 {
354 flags = 0;
355 if (!(fpscr & 0x100)) flags |= _EM_INVALID;
356 if (!(fpscr & 0x200)) flags |= _EM_ZERODIVIDE;
357 if (!(fpscr & 0x400)) flags |= _EM_OVERFLOW;
358 if (!(fpscr & 0x800)) flags |= _EM_UNDERFLOW;
359 if (!(fpscr & 0x1000)) flags |= _EM_INEXACT;
360 if (!(fpscr & 0x8000)) flags |= _EM_DENORMAL;
361 switch (fpscr & 0xc00000)
362 {
363 case 0x400000: flags |= _RC_UP; break;
364 case 0x800000: flags |= _RC_DOWN; break;
365 case 0xc00000: flags |= _RC_CHOP; break;
366 }
367
368 *cw = (flags & ~cw_mask) | (*cw & cw_mask);
369 fpscr &= ~0xc09f00ul;
370 if (!(*cw & _EM_INVALID)) fpscr |= 0x100;
371 if (!(*cw & _EM_ZERODIVIDE)) fpscr |= 0x200;
372 if (!(*cw & _EM_OVERFLOW)) fpscr |= 0x400;
373 if (!(*cw & _EM_UNDERFLOW)) fpscr |= 0x800;
374 if (!(*cw & _EM_INEXACT)) fpscr |= 0x1000;
375 if (!(*cw & _EM_DENORMAL)) fpscr |= 0x8000;
376 switch (*cw & _MCW_RC)
377 {
378 case _RC_CHOP: fpscr |= 0xc00000; break;
379 case _RC_UP: fpscr |= 0x400000; break;
380 case _RC_DOWN: fpscr |= 0x800000; break;
381 }
382 }
383
384 if (old_fpscr != fpscr)
385 __asm__ __volatile__( "vmsr fpscr, %0" :: "r" (fpscr) );
386#endif
387}