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