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