exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / fenv-except-tracking-set.c
blobaf9bb50e810f845660bb3831463064ae9d41e3a5
1 /* Functions for tracking which floating-point exceptions have occurred.
2 Copyright (C) 1997-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Based on glibc/sysdeps/<cpu>/fesetexcept.c
18 together with glibc/sysdeps/<cpu>/{fpu_control.h,fenv_private.h,fenv_libc.h}. */
20 #include <config.h>
22 /* Specification. */
23 #include <fenv.h>
25 #include "fenv-private.h"
27 #if defined __GNUC__ || defined __clang__ || defined _MSC_VER
29 # if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)
31 int
32 fesetexcept (int exceptions)
34 exceptions &= FE_ALL_EXCEPT;
36 # if defined _MSC_VER
37 exceptions = exceptions_to_x86hardware (exceptions);
39 /* Set the flags in the SSE unit. */
40 unsigned int mxcsr, orig_mxcsr;
41 _FPU_GETSSECW (orig_mxcsr);
42 mxcsr = orig_mxcsr | exceptions;
43 if (mxcsr != orig_mxcsr)
44 _FPU_SETSSECW (mxcsr);
46 # else
48 /* We can set the flags in the 387 unit or in the SSE unit.
49 Either works, due to the way fetestexcept() is implemented.
50 Choose the simplest approach. */
51 # if defined __x86_64__ || defined _M_X64
52 /* Set the flags in the SSE unit. */
53 unsigned int mxcsr, orig_mxcsr;
54 _FPU_GETSSECW (orig_mxcsr);
55 mxcsr = orig_mxcsr | exceptions;
56 if (mxcsr != orig_mxcsr)
57 _FPU_SETSSECW (mxcsr);
58 # else
59 if (CPU_HAS_SSE ())
61 /* Set the flags in the SSE unit. */
62 unsigned int mxcsr, orig_mxcsr;
63 _FPU_GETSSECW (orig_mxcsr);
64 mxcsr = orig_mxcsr | exceptions;
65 if (mxcsr != orig_mxcsr)
66 _FPU_SETSSECW (mxcsr);
68 else
70 /* Set the flags in the 387 unit. */
71 x86_387_fenv_t env;
72 __asm__ __volatile__ ("fnstenv %0" : "=m" (*&env));
73 /* Note: fnstenv masks all floating-point exceptions until the fldenv
74 or fldcw below. */
75 env.__status_word |= exceptions;
76 if ((~env.__control_word) & exceptions)
78 /* Setting the exception flags may trigger a trap (at the next
79 floating-point instruction, but that does not matter).
80 ISO C 23 § 7.6.4.4 does not allow it. */
81 __asm__ __volatile__ ("fldcw %0" : : "m" (*&env.__control_word));
82 return -1;
84 __asm__ __volatile__ ("fldenv %0" : : "m" (*&env));
86 # endif
87 # endif
89 return 0;
92 # elif defined __aarch64__ /* arm64 */
94 int
95 fesetexcept (int exceptions)
97 exceptions &= FE_ALL_EXCEPT;
99 unsigned long fpsr, orig_fpsr;
100 _FPU_GETFPSR (orig_fpsr);
101 fpsr = orig_fpsr | exceptions;
102 if (fpsr != orig_fpsr)
103 _FPU_SETFPSR (fpsr);
105 return 0;
108 # elif defined __arm__
111 fesetexcept (int exceptions)
113 exceptions &= FE_ALL_EXCEPT;
115 # ifdef __SOFTFP__
116 if (exceptions != 0)
117 return -1;
118 # else
119 unsigned int fpscr, orig_fpscr;
120 _FPU_GETCW (orig_fpscr);
121 fpscr = orig_fpscr | exceptions;
122 if (fpscr != orig_fpscr)
123 _FPU_SETCW (fpscr);
124 # endif
125 return 0;
128 # elif defined __alpha
131 fesetexcept (int exceptions)
133 exceptions &= FE_ALL_EXCEPT;
135 unsigned long swcr, orig_swcr;
136 orig_swcr = __ieee_get_fp_control ();
137 swcr = orig_swcr | exceptions;
138 if (swcr != orig_swcr)
139 __ieee_set_fp_control (swcr);
141 return 0;
144 # elif defined __hppa
147 fesetexcept (int exceptions)
149 exceptions &= FE_ALL_EXCEPT;
151 union { unsigned long long fpreg; unsigned int halfreg[2]; } s;
152 /* Get the current status word. */
153 __asm__ __volatile__ ("fstd %%fr0,0(%1)" : "=m" (s.fpreg) : "r" (&s.fpreg) : "%r0");
154 unsigned int old_halfreg0 = s.halfreg[0];
155 /* Clear all the relevant bits. */
156 s.halfreg[0] |= ((unsigned int) exceptions << 27);
157 if (s.halfreg[0] != old_halfreg0)
159 /* Store the new status word. */
160 __asm__ __volatile__ ("fldd 0(%0),%%fr0" : : "r" (&s.fpreg), "m" (s.fpreg) : "%r0");
163 return 0;
166 # elif defined __ia64__
169 fesetexcept (int exceptions)
171 exceptions &= FE_ALL_EXCEPT;
173 unsigned long fpsr, orig_fpsr;
174 _FPU_GETCW (orig_fpsr);
175 fpsr = orig_fpsr | (unsigned long) (exceptions << 13);
176 if (fpsr != orig_fpsr)
177 _FPU_SETCW (fpsr);
179 return 0;
182 # elif defined __m68k__
185 fesetexcept (int exceptions)
187 exceptions &= FE_ALL_EXCEPT;
189 unsigned int fpsr, orig_fpsr;
190 _FPU_GETFPSR (orig_fpsr);
191 fpsr = orig_fpsr | exceptions;
192 if (fpsr != orig_fpsr)
193 _FPU_SETFPSR (fpsr);
195 return 0;
198 # elif defined __mips__
201 fesetexcept (int exceptions)
203 exceptions &= FE_ALL_EXCEPT;
205 unsigned int fcsr, orig_fcsr;
206 _FPU_GETCW (orig_fcsr);
207 fcsr = orig_fcsr | exceptions;
208 if (fcsr != orig_fcsr)
209 _FPU_SETCW (fcsr);
211 return 0;
214 # elif defined __loongarch__
217 fesetexcept (int exceptions)
219 exceptions &= FE_ALL_EXCEPT;
221 unsigned int fcsr, orig_fcsr;
222 _FPU_GETCW (orig_fcsr);
223 fcsr = orig_fcsr | exceptions;
224 if (fcsr != orig_fcsr)
225 _FPU_SETCW (fcsr);
227 return 0;
230 # elif defined __powerpc__
233 fesetexcept (int exceptions)
235 /* The hardware does not support setting an exception flag without triggering
236 a trap, except through the "Ignore Exceptions Mode", bits FE0 and FE1 of
237 the MSR register set to zero, that can be obtained through a system call:
238 - On Linux and NetBSD: prctl (PR_SET_FPEXC, PR_FP_EXC_DISABLED);
239 - On AIX: fp_trap (FP_TRAP_OFF);
240 But that is not what we need here, as it would have a persistent effect on
241 the thread. */
242 exceptions &= FE_ALL_EXCEPT;
244 union { unsigned long long u; double f; } memenv, orig_memenv;
245 _FPU_GETCW_AS_DOUBLE (memenv.f);
246 orig_memenv = memenv;
248 /* Instead of setting FE_INVALID (= bit 29), we need to set one of the
249 individual bits: bit 10 or, if that does not work, bit 24. */
250 memenv.u |= (exceptions & FE_INVALID
251 ? (exceptions & ~FE_INVALID) | (1U << 10)
252 : exceptions);
254 if (!(memenv.u == orig_memenv.u))
256 if (memenv.u & (exceptions >> 22))
258 /* Setting the exception flags may trigger a trap.
259 ISO C 23 § 7.6.4.4 does not allow it. */
260 return -1;
262 _FPU_SETCW_AS_DOUBLE (memenv.f);
263 if (exceptions & FE_INVALID)
265 /* Did it work? */
266 _FPU_GETCW_AS_DOUBLE (memenv.f);
267 if ((memenv.u & FE_INVALID) == 0)
269 memenv.u |= (1U << 24);
270 _FPU_SETCW_AS_DOUBLE (memenv.f);
275 return 0;
278 # elif defined __riscv
281 fesetexcept (int exceptions)
283 /* This is identical to feraiseexcept(), because the hardware does not
284 support trapping on floating-point exceptions. */
285 exceptions &= FE_ALL_EXCEPT;
286 __asm__ __volatile__ ("csrs fflags, %0" : : "r" (exceptions));
287 return 0;
290 # elif defined __s390__ || defined __s390x__
293 fesetexcept (int exceptions)
295 exceptions &= FE_ALL_EXCEPT;
297 unsigned int fpc, orig_fpc;
298 _FPU_GETCW (orig_fpc);
299 # if FE_INEXACT == 8 /* glibc compatible FE_* values */
300 fpc = orig_fpc | (exceptions << 16);
301 # else /* musl libc compatible FE_* values */
302 fpc = orig_fpc | exceptions;
303 # endif
304 if (fpc != orig_fpc)
305 _FPU_SETCW (fpc);
307 return 0;
310 # elif defined __sh__
313 fesetexcept (int exceptions)
315 exceptions &= FE_ALL_EXCEPT;
317 unsigned int fpscr, orig_fpscr;
318 _FPU_GETCW (orig_fpscr);
319 fpscr = orig_fpscr | exceptions;
320 if (fpscr != orig_fpscr)
321 _FPU_SETCW (fpscr);
323 return 0;
326 # elif defined __sparc
329 fesetexcept (int exceptions)
331 exceptions &= FE_ALL_EXCEPT;
333 unsigned long fsr, orig_fsr;
334 _FPU_GETCW (orig_fsr);
335 # if FE_INEXACT == 32 /* glibc compatible FE_* values */
336 fsr = orig_fsr | exceptions;
337 # else /* Solaris compatible FE_* values */
338 fsr = orig_fsr | (exceptions << 5);
339 # endif
340 if (fsr != orig_fsr)
341 _FPU_SETCW (fsr);
343 return 0;
346 # else
348 # if defined __GNUC__ || defined __clang__
349 # warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>."
350 # endif
351 # define NEED_FALLBACK 1
353 # endif
355 #else
357 /* The compiler does not support __asm__ statements or equivalent
358 intrinsics. */
360 # if defined __sun && ((defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)) && defined __SUNPRO_C
361 /* Solaris/i386, Solaris/x86_64. */
363 /* On these platforms, fpsetsticky cannot be used here, because it may generate
364 traps (since fpsetsticky calls _putsw, which modifies the control word of the
365 387 unit). Instead, we need to modify only the flags in the SSE unit. */
367 /* Accessors for the mxcsr register. Fortunately, the Solaris cc supports a
368 poor form of 'asm'. */
370 static void
371 getssecw (unsigned int *mxcsr_p)
373 # if defined __x86_64__ || defined _M_X64
374 asm ("stmxcsr (%rdi)");
375 # else
376 /* The compiler generates a stack frame. Therefore the first argument is in
377 8(%ebp), not in 4(%esp). */
378 asm ("movl 8(%ebp),%eax");
379 asm ("stmxcsr (%eax)");
380 # endif
383 static void
384 setssecw (unsigned int const *mxcsr_p)
386 # if defined __x86_64__ || defined _M_X64
387 asm ("ldmxcsr (%rdi)");
388 # else
389 /* The compiler generates a stack frame. Therefore the first argument is in
390 8(%ebp), not in 4(%esp). */
391 asm ("movl 8(%ebp),%eax");
392 asm ("ldmxcsr (%eax)");
393 # endif
397 fesetexcept (int exceptions)
399 exceptions &= FE_ALL_EXCEPT;
401 /* Set the flags in the SSE unit. */
402 unsigned int mxcsr, orig_mxcsr;
403 getssecw (&orig_mxcsr);
404 mxcsr = orig_mxcsr | exceptions;
405 if (mxcsr != orig_mxcsr)
406 setssecw (&mxcsr);
408 return 0;
411 # elif HAVE_FPSETSTICKY
412 /* FreeBSD ≥ 3.1, NetBSD ≥ 1.1, OpenBSD, IRIX, Solaris, Minix ≥ 3.2. */
414 /* Get fpgetsticky, fpsetsticky. */
415 # include <ieeefp.h>
416 /* The type is called 'fp_except_t' on FreeBSD, but 'fp_except' on
417 all other systems. */
418 # if !defined __FreeBSD__
419 # define fp_except_t fp_except
420 # endif
423 fesetexcept (int exceptions)
425 exceptions &= FE_ALL_EXCEPT;
427 fp_except_t flags, orig_flags;
428 orig_flags = fpgetsticky ();
429 flags = orig_flags | exceptions;
430 if (flags != orig_flags)
431 fpsetsticky (flags);
433 return 0;
436 # elif defined _AIX && defined __powerpc__ /* AIX */
438 # include <float.h>
439 # include <fpxcp.h>
441 # include <fptrap.h>
443 /* Documentation:
444 <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-clr-flag-fp-set-flag-fp-read-flag-fp-swap-flag-subroutine> */
447 fesetexcept (int exceptions)
449 exceptions &= FE_ALL_EXCEPT;
451 /* Instead of setting FE_INVALID (= bit 29), we need to set one of the
452 individual bits: bit 10 or, if that does not work, bit 24. */
453 fpflag_t f_to_set =
454 (exceptions & FE_INVALID
455 ? exceptions_to_fpflag (exceptions & ~FE_INVALID) | (1U << 10)
456 : exceptions_to_fpflag (exceptions));
457 if (f_to_set != 0)
459 if ((fegetexcept_impl () & exceptions) != 0)
461 /* Setting the exception flags may trigger a trap.
462 ISO C 23 § 7.6.4.4 does not allow it. */
463 return -1;
465 fp_set_flag (f_to_set);
466 if (exceptions & FE_INVALID)
468 /* Did it work? */
469 if ((fp_read_flag () & FP_INVALID) == 0)
470 fp_set_flag (1U << 24);
474 return 0;
477 # else
479 # define NEED_FALLBACK 1
481 # endif
483 #endif
485 #if NEED_FALLBACK
487 /* A dummy fallback. */
490 fesetexcept (int exceptions)
492 exceptions &= FE_ALL_EXCEPT;
493 if (exceptions != 0)
494 return -1;
495 return 0;
498 #endif