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