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