1 /*
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2010-2019 Red Hat, Inc.
5 */
6
7 #ifdef _SOFT_FLOAT
8 #include "../../fenv/fenv.c"
9 #else
10
11 #define _GNU_SOURCE // for FE_NOMASK_ENV
12
13 #include <fenv.h>
14 #include <errno.h>
15 #include <string.h> // for memcpy
16 #include <stdbool.h>
17
18 /* x87 supports subnormal numbers so we need it below. */
19 #define __FE_DENORM (1 << 1)
20 /* mask (= 0x3f) to disable all exceptions at initialization */
21 #define __FE_ALL_EXCEPT_X86 (FE_ALL_EXCEPT | __FE_DENORM)
22
23 /* Mask and shift amount for rounding bits. */
24 #define FE_CW_ROUND_MASK (0x0c00)
25 #define FE_CW_ROUND_SHIFT (10)
26 /* Same, for SSE MXCSR. */
27 #define FE_MXCSR_ROUND_MASK (0x6000)
28 #define FE_MXCSR_ROUND_SHIFT (13)
29
30 /* Mask and shift amount for precision bits. */
31 #define FE_CW_PREC_MASK (0x0300)
32 #define FE_CW_PREC_SHIFT (8)
33
34 /* In x87, exception status bits and mask bits occupy
35 corresponding bit positions in the status and control
36 registers, respectively. In SSE, they are both located
37 in the control-and-status register, with the status bits
38 corresponding to the x87 positions, and the mask bits
39 shifted by this amount to the left. */
40 #define FE_SSE_EXCEPT_MASK_SHIFT (7)
41
42 /* These are writable so we can initialise them at startup. */
43 static fenv_t fe_nomask_env;
44
45 /* These pointers provide the outside world with read-only access to them. */
46 const fenv_t *_fe_nomask_env = &fe_nomask_env;
47
48 /* Assume i686 or above (hence SSE available) these days, with the
49 compiler feels free to use it (depending on compile- time flags of
50 course), but we should avoid needlessly breaking any purely integer mode
51 apps (or apps compiled with -mno-sse), so we only manage SSE state in this
52 fenv module if we detect that SSE instructions are available at runtime.
53 If we didn't do this, all applications run on older machines would bomb
54 out with an invalid instruction exception right at startup; let's not
55 be *that* WJM! */
use_sse(void)56 static inline bool use_sse(void)
57 {
58 unsigned int edx, eax;
59
60 /* Check for presence of SSE: invoke CPUID #1, check EDX bit 25. */
61 eax = 1;
62 __asm__ volatile ("cpuid" : "=d" (edx), "+a" (eax) :: "%ecx", "%ebx");
63 /* If this flag isn't set we'll avoid trying to execute any SSE. */
64 if ((edx & (1 << 25)) != 0)
65 return true;
66
67 return false;
68 }
69
70 /* forward declaration */
71 static void _feinitialise (void);
72
73 /* This function enables traps for each of the exceptions as indicated
74 by the parameter except. The individual exceptions are described in
75 [ ... glibc manual xref elided ...]. Only the specified exceptions are
76 enabled, the status of the other exceptions is not changed.
77 The function returns the previous enabled exceptions in case the
78 operation was successful, -1 otherwise. */
79 int
feenableexcept(int excepts)80 feenableexcept (int excepts)
81 {
82 unsigned short cw, old_cw;
83 unsigned int mxcsr = 0;
84
85 if (excepts & ~FE_ALL_EXCEPT)
86 return -1;
87
88 /* Get control words. */
89 __asm__ volatile ("fnstcw %0" : "=m" (old_cw) : );
90 if (use_sse())
91 __asm__ volatile ("stmxcsr %0" : "=m" (mxcsr) : );
92
93 /* Enable exceptions by clearing mask bits. */
94 cw = old_cw & ~excepts;
95 mxcsr &= ~(excepts << FE_SSE_EXCEPT_MASK_SHIFT);
96
97 /* Store updated control words. */
98 __asm__ volatile ("fldcw %0" :: "m" (cw));
99 if (use_sse())
100 __asm__ volatile ("ldmxcsr %0" :: "m" (mxcsr));
101
102 /* Return old value. We assume SSE and x87 stay in sync. Note that
103 we are returning a mask of enabled exceptions, which is the opposite
104 of the flags in the register, which are set to disable (mask) their
105 related exceptions. */
106 return (~old_cw) & FE_ALL_EXCEPT;
107 }
108
109 /* This function disables traps for each of the exceptions as indicated
110 by the parameter except. The individual exceptions are described in
111 [ ... glibc manual xref elided ...]. Only the specified exceptions are
112 disabled, the status of the other exceptions is not changed.
113 The function returns the previous enabled exceptions in case the
114 operation was successful, -1 otherwise. */
115 int
fedisableexcept(int excepts)116 fedisableexcept (int excepts)
117 {
118 unsigned short cw, old_cw;
119 unsigned int mxcsr = 0;
120
121 if (excepts & ~FE_ALL_EXCEPT)
122 return -1;
123
124 /* Get control words. */
125 __asm__ volatile ("fnstcw %0" : "=m" (old_cw) : );
126 if (use_sse())
127 __asm__ volatile ("stmxcsr %0" : "=m" (mxcsr) : );
128
129 /* Disable exceptions by setting mask bits. */
130 cw = old_cw | excepts;
131 mxcsr |= (excepts << FE_SSE_EXCEPT_MASK_SHIFT);
132
133 /* Store updated control words. */
134 __asm__ volatile ("fldcw %0" :: "m" (cw));
135 if (use_sse())
136 __asm__ volatile ("ldmxcsr %0" :: "m" (mxcsr));
137
138 /* Return old value. We assume SSE and x87 stay in sync. Note that
139 we are returning a mask of enabled exceptions, which is the opposite
140 of the flags in the register, which are set to disable (mask) their
141 related exceptions. */
142 return (~old_cw) & FE_ALL_EXCEPT;
143 }
144
145 /* This function returns a bitmask of all currently enabled exceptions. It
146 returns -1 in case of failure. */
147 int
fegetexcept(void)148 fegetexcept (void)
149 {
150 unsigned short cw;
151
152 /* Get control word. We assume SSE and x87 stay in sync. */
153 __asm__ volatile ("fnstcw %0" : "=m" (cw) : );
154
155 /* Exception is *dis*abled when mask bit is set. */
156 return (~cw) & FE_ALL_EXCEPT;
157 }
158
159 /* Store the floating-point environment in the variable pointed to by envp.
160 The function returns zero in case the operation was successful, a non-zero
161 value otherwise. */
162 int
fegetenv(fenv_t * envp)163 fegetenv (fenv_t *envp)
164 {
165 /* fnstenv disables all exceptions in the x87 FPU; as this is not what is
166 desired here, reload the cfg saved from the x87 FPU, back to the FPU */
167 __asm__ volatile ("fnstenv %0\n\
168 fldenv %0"
169 : "=m" (envp->_fpu) : );
170 if (use_sse())
171 __asm__ volatile ("stmxcsr %0" : "=m" (envp->_sse_mxcsr) : );
172 return 0;
173 }
174
175 /* Store the current floating-point environment in the object pointed to
176 by envp. Then clear all exception flags, and set the FPU to trap no
177 exceptions. Not all FPUs support trapping no exceptions; if feholdexcept
178 cannot set this mode, it returns nonzero value. If it succeeds, it
179 returns zero. */
180 int
feholdexcept(fenv_t * envp)181 feholdexcept (fenv_t *envp)
182 {
183 unsigned int mxcsr;
184 fegetenv (envp);
185 mxcsr = envp->_sse_mxcsr & ~FE_ALL_EXCEPT;
186 if (use_sse())
187 __asm__ volatile ("ldmxcsr %0" :: "m" (mxcsr));
188 __asm__ volatile ("fnclex");
189 fedisableexcept (FE_ALL_EXCEPT);
190 return 0;
191 }
192
193 /* Set the floating-point environment to that described by envp. The
194 function returns zero in case the operation was successful, a non-zero
195 value otherwise. */
196 int
fesetenv(const fenv_t * envp)197 fesetenv (const fenv_t *envp)
198 {
199 if ((envp == FE_DFL_ENV || envp == FE_NOMASK_ENV) &&
200 envp->_fpu._fpu_cw == 0)
201 _feinitialise ();
202
203 __asm__ volatile ("fldenv %0" :: "m" (envp->_fpu) );
204 if (use_sse())
205 __asm__ volatile ("ldmxcsr %0" :: "m" (envp->_sse_mxcsr));
206 return 0;
207 }
208
209 /* Like fesetenv, this function sets the floating-point environment to
210 that described by envp. However, if any exceptions were flagged in the
211 status word before feupdateenv was called, they remain flagged after
212 the call. In other words, after feupdateenv is called, the status
213 word is the bitwise OR of the previous status word and the one saved
214 in envp. The function returns zero in case the operation was successful,
215 a non-zero value otherwise. */
216 int
feupdateenv(const fenv_t * envp)217 feupdateenv (const fenv_t *envp)
218 {
219 fenv_t envcopy;
220 unsigned int mxcsr = 0;
221 unsigned short sw;
222
223 /* Don't want to modify *envp, but want to update environment atomically,
224 so take a copy and merge the existing exceptions into it. */
225 memcpy (&envcopy, envp, sizeof *envp);
226 __asm__ volatile ("fnstsw %0" : "=m" (sw) : );
227 if (use_sse())
228 __asm__ volatile ("stmxcsr %0" : "=m" (mxcsr) : );
229 envcopy._fpu._fpu_sw |= (sw & FE_ALL_EXCEPT);
230 envcopy._sse_mxcsr |= (mxcsr & FE_ALL_EXCEPT);
231
232 return fesetenv (&envcopy);
233 }
234
235 /* This function clears all of the supported exception flags indicated by
236 excepts. The function returns zero in case the operation was successful,
237 a non-zero value otherwise. */
238 int
feclearexcept(int excepts)239 feclearexcept (int excepts)
240 {
241 fenv_t fenv;
242
243 if (excepts & ~FE_ALL_EXCEPT)
244 return EINVAL;
245
246 /* Need to save/restore whole environment to modify status word. */
247 fegetenv (&fenv);
248
249 /* Mask undesired bits out. */
250 fenv._fpu._fpu_sw &= ~excepts;
251 fenv._sse_mxcsr &= ~excepts;
252
253 /* Set back into FPU state. */
254 return fesetenv (&fenv);
255 }
256
257 /* This function raises the supported exceptions indicated by
258 excepts. If more than one exception bit in excepts is set the order
259 in which the exceptions are raised is undefined except that overflow
260 (FE_OVERFLOW) or underflow (FE_UNDERFLOW) are raised before inexact
261 (FE_INEXACT). Whether for overflow or underflow the inexact exception
262 is also raised is also implementation dependent. The function returns
263 zero in case the operation was successful, a non-zero value otherwise. */
264 int
feraiseexcept(int excepts)265 feraiseexcept (int excepts)
266 {
267 fenv_t fenv;
268
269 if (excepts & ~FE_ALL_EXCEPT)
270 return EINVAL;
271
272 /* Need to save/restore whole environment to modify status word. */
273 __asm__ volatile ("fnstenv %0" : "=m" (fenv) : );
274
275 /* Set desired exception bits. */
276 fenv._fpu._fpu_sw |= excepts;
277
278 /* Set back into FPU state. */
279 __asm__ volatile ("fldenv %0" :: "m" (fenv));
280
281 /* And trigger them - whichever are unmasked. */
282 __asm__ volatile ("fwait");
283
284 return 0;
285 }
286
287 /* This function sets the supported exceptions indicated by
288 excepts. The function returns
289 zero in case the operation was successful, a non-zero value otherwise. */
290 int
fesetexcept(int excepts)291 fesetexcept (int excepts)
292 {
293 fenv_t fenv;
294
295 if (excepts & ~FE_ALL_EXCEPT)
296 return EINVAL;
297
298 /* Need to save/restore whole environment to modify status word. */
299 __asm__ volatile ("fnstenv %0" : "=m" (fenv) : );
300
301 /* Set desired exception bits. */
302 fenv._fpu._fpu_sw |= excepts;
303
304 /* Set back into FPU state. */
305 __asm__ volatile ("fldenv %0" :: "m" (fenv));
306
307 return 0;
308 }
309
310 /* Test whether the exception flags indicated by the parameter except
311 are currently set. If any of them are, a nonzero value is returned
312 which specifies which exceptions are set. Otherwise the result is zero. */
313 int
fetestexcept(int excepts)314 fetestexcept (int excepts)
315 {
316 unsigned short sw;
317 unsigned int mxcsr = 0;
318
319 if (excepts & ~FE_ALL_EXCEPT)
320 return EINVAL;
321
322 /* Get status registers. */
323 __asm__ volatile ("fnstsw %0" : "=m" (sw) : );
324 if (use_sse())
325 __asm__ volatile ("stmxcsr %0" : "=m" (mxcsr) : );
326
327 /* Mask undesired bits out and return result. */
328 return (sw | mxcsr) & excepts;
329 }
330 /* This function stores in the variable pointed to by flagp an
331 implementation-defined value representing the current setting of the
332 exception flags indicated by excepts. The function returns zero in
333 case the operation was successful, a non-zero value otherwise. */
334 int
fegetexceptflag(fexcept_t * flagp,int excepts)335 fegetexceptflag (fexcept_t *flagp, int excepts)
336 {
337 unsigned short sw;
338 unsigned int mxcsr = 0;
339
340 if (excepts & ~FE_ALL_EXCEPT)
341 return EINVAL;
342
343 /* Get status registers. */
344 __asm__ volatile ("fnstsw %0" : "=m" (sw) : );
345 if (use_sse())
346 __asm__ volatile ("stmxcsr %0" : "=m" (mxcsr) : );
347
348 /* Mask undesired bits out and set result. */
349 *flagp = (sw | mxcsr) & excepts;
350
351 return 0;
352 }
353
354 /* This function restores the flags for the exceptions indicated by
355 excepts to the values stored in the variable pointed to by flagp. */
356 int
fesetexceptflag(const fexcept_t * flagp,int excepts)357 fesetexceptflag (const fexcept_t *flagp, int excepts)
358 {
359 fenv_t fenv;
360
361 if (excepts & ~FE_ALL_EXCEPT)
362 return EINVAL;
363
364 /* Need to save/restore whole environment to modify status word. */
365 fegetenv (&fenv);
366
367 /* Set/Clear desired exception bits. */
368 fenv._fpu._fpu_sw &= ~excepts;
369 fenv._fpu._fpu_sw |= excepts & *flagp;
370 fenv._sse_mxcsr &= ~excepts;
371 fenv._sse_mxcsr |= excepts & *flagp;
372
373 /* Set back into FPU state. */
374 return fesetenv (&fenv);
375 }
376
377 /* Returns the currently selected rounding mode, represented by one of the
378 values of the defined rounding mode macros. */
379 int
fegetround(void)380 fegetround (void)
381 {
382 unsigned short cw;
383
384 /* Get control word. We assume SSE and x87 stay in sync. */
385 __asm__ volatile ("fnstcw %0" : "=m" (cw) : );
386
387 return (cw & FE_CW_ROUND_MASK) >> FE_CW_ROUND_SHIFT;
388 }
389
390 /* Changes the currently selected rounding mode to round. If round does
391 not correspond to one of the supported rounding modes nothing is changed.
392 fesetround returns zero if it changed the rounding mode, a nonzero value
393 if the mode is not supported. */
394 int
fesetround(int round)395 fesetround (int round)
396 {
397 unsigned short cw;
398 unsigned int mxcsr = 0;
399
400 /* Will succeed for any valid value of the input parameter. */
401 if (round < FE_TONEAREST || round > FE_TOWARDZERO)
402 return EINVAL;
403
404 /* Get control words. */
405 __asm__ volatile ("fnstcw %0" : "=m" (cw) : );
406 if (use_sse())
407 __asm__ volatile ("stmxcsr %0" : "=m" (mxcsr) : );
408
409 /* Twiddle bits. */
410 cw &= ~FE_CW_ROUND_MASK;
411 cw |= (round << FE_CW_ROUND_SHIFT);
412 mxcsr &= ~FE_MXCSR_ROUND_MASK;
413 mxcsr |= (round << FE_MXCSR_ROUND_SHIFT);
414
415 /* Set back into FPU state. */
416 __asm__ volatile ("fldcw %0" :: "m" (cw));
417 if (use_sse())
418 __asm__ volatile ("ldmxcsr %0" :: "m" (mxcsr));
419
420 /* Indicate success. */
421 return 0;
422 }
423
424 #if defined(__CYGWIN__)
425 /* Returns the currently selected precision, represented by one of the
426 values of the defined precision macros. */
427 int
fegetprec(void)428 fegetprec (void)
429 {
430 unsigned short cw;
431
432 /* Get control word. */
433 __asm__ volatile ("fnstcw %0" : "=m" (cw) : );
434
435 return (cw & FE_CW_PREC_MASK) >> FE_CW_PREC_SHIFT;
436 }
437
438 /* http://www.open-std.org/jtc1/sc22//WG14/www/docs/n752.htm:
439
440 The fesetprec function establishes the precision represented by its
441 argument prec. If the argument does not match a precision macro, the
442 precision is not changed.
443
444 The fesetprec function returns a nonzero value if and only if the
445 argument matches a precision macro (that is, if and only if the requested
446 precision can be established). */
447 int
fesetprec(int prec)448 fesetprec (int prec)
449 {
450 unsigned short cw;
451
452 /* Will succeed for any valid value of the input parameter. */
453 switch (prec)
454 {
455 case FE_FLTPREC:
456 case FE_DBLPREC:
457 case FE_LDBLPREC:
458 break;
459 default:
460 return 0;
461 }
462
463 /* Get control word. */
464 __asm__ volatile ("fnstcw %0" : "=m" (cw) : );
465
466 /* Twiddle bits. */
467 cw &= ~FE_CW_PREC_MASK;
468 cw |= (prec << FE_CW_PREC_SHIFT);
469
470 /* Set back into FPU state. */
471 __asm__ volatile ("fldcw %0" :: "m" (cw));
472
473 /* Indicate success. */
474 return 1;
475 }
476 #endif
477
478 /* Set up the FPU and SSE environment at the start of execution. */
479 static void
_feinitialise(void)480 _feinitialise (void)
481 {
482 /* Reset FPU: extended prec, all exceptions cleared and masked off. */
483 __asm__ volatile ("fninit");
484 /* The default cw value, 0x37f, is rounding mode zero. The MXCSR has
485 no precision control, so the only thing to do is set the exception
486 mask bits. */
487
488 /* initialize the MXCSR register: mask all exceptions */
489 unsigned int mxcsr = __FE_ALL_EXCEPT_X86 << FE_SSE_EXCEPT_MASK_SHIFT;
490 if (use_sse())
491 __asm__ volatile ("ldmxcsr %0" :: "m" (mxcsr));
492
493 /* Setup unmasked environment, but leave __FE_DENORM masked. */
494 feenableexcept (FE_ALL_EXCEPT);
495 fegetenv (&fe_nomask_env);
496
497 /* Restore default exception masking (all masked). */
498 fedisableexcept (FE_ALL_EXCEPT);
499
500 /* Finally cache state as default environment. */
501 fegetenv (&_fe_dfl_env);
502 }
503
504 #endif /* _SOFT_FLOAT */
505