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   extern fenv_t __fe_dfl_env;
456 
457   /* Reset FPU: extended prec, all exceptions cleared and masked off.  */
458   __asm__ volatile ("fninit");
459   /* The default cw value, 0x37f, is rounding mode zero.  The MXCSR has
460      no precision control, so the only thing to do is set the exception
461      mask bits.  */
462 
463   /* initialize the MXCSR register: mask all exceptions */
464   unsigned int mxcsr = __FE_ALL_EXCEPT_X86 << FE_SSE_EXCEPT_MASK_SHIFT;
465   if (use_sse())
466     __asm__ volatile ("ldmxcsr %0" :: "m" (mxcsr));
467 
468   /* Setup unmasked environment, but leave __FE_DENORM masked.  */
469   feenableexcept (FE_ALL_EXCEPT);
470   fegetenv (&fe_nomask_env);
471 
472   /* Restore default exception masking (all masked).  */
473   fedisableexcept (FE_ALL_EXCEPT);
474 
475   /* Finally cache state as default environment. */
476   fegetenv (&__fe_dfl_env);
477 }
478