| 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 | |
| 11 | static 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 | |
| 23 | static 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 | |
| 33 | void __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 | |
| 115 | void __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 | } |