xref: /netbsd-src/lib/libm/arch/x86_64/fenv.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /* $NetBSD: fenv.c,v 1.7 2017/03/22 23:11:09 chs Exp $ */
2 
3 /*-
4  * Copyright (c) 2004-2005 David Schultz <das (at) FreeBSD.ORG>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __RCSID("$NetBSD: fenv.c,v 1.7 2017/03/22 23:11:09 chs Exp $");
31 
32 #include "namespace.h"
33 
34 #include <assert.h>
35 #include <fenv.h>
36 #include <stddef.h>
37 #include <string.h>
38 
39 #ifdef __weak_alias
40 __weak_alias(feclearexcept,_feclearexcept)
41 __weak_alias(fedisableexcept,_fedisableexcept)
42 __weak_alias(feenableexcept,_feenableexcept)
43 __weak_alias(fegetenv,_fegetenv)
44 __weak_alias(fegetexcept,_fegetexcept)
45 __weak_alias(fegetexceptflag,_fegetexceptflag)
46 __weak_alias(fegetround,_fegetround)
47 __weak_alias(feholdexcept,_feholdexcept)
48 __weak_alias(feraiseexcept,_feraiseexcept)
49 __weak_alias(fesetenv,_fesetenv)
50 __weak_alias(fesetexceptflag,_fesetexceptflag)
51 __weak_alias(fesetround,_fesetround)
52 __weak_alias(fetestexcept,_fetestexcept)
53 __weak_alias(feupdateenv,_feupdateenv)
54 #endif
55 
56 /* Load x87 Control Word */
57 #define	__fldcw(__cw)		__asm__ __volatile__ \
58 	("fldcw %0" : : "m" (__cw))
59 
60 /* No-Wait Store Control Word */
61 #define	__fnstcw(__cw)		__asm__ __volatile__ \
62 	("fnstcw %0" : "=m" (*(__cw)))
63 
64 /* No-Wait Store Status Word */
65 #define	__fnstsw(__sw)		__asm__ __volatile__ \
66 	("fnstsw %0" : "=am" (*(__sw)))
67 
68 /* No-Wait Clear Exception Flags */
69 #define	__fnclex()		__asm__ __volatile__ \
70 	("fnclex")
71 
72 /* Load x87 Environment */
73 #define	__fldenv(__env)		__asm__ __volatile__ \
74 	("fldenv %0" : : "m" (__env))
75 
76 /* No-Wait Store x87 environment */
77 #define	__fnstenv(__env)	__asm__ __volatile__ \
78 	("fnstenv %0" : "=m" (*(__env)))
79 
80 /* Check for and handle pending unmasked x87 pending FPU exceptions */
81 #define	__fwait(__env)		__asm__	__volatile__	\
82 	("fwait")
83 
84 /* Load the MXCSR register */
85 #define	__ldmxcsr(__mxcsr)	__asm__ __volatile__ \
86 	("ldmxcsr %0" : : "m" (__mxcsr))
87 
88 /* Store the MXCSR register state */
89 #define	__stmxcsr(__mxcsr)	__asm__ __volatile__ \
90 	("stmxcsr %0" : "=m" (*(__mxcsr)))
91 
92 /*
93  * The following constant represents the default floating-point environment
94  * (that is, the one installed at program startup) and has type pointer to
95  * const-qualified fenv_t.
96  *
97  * It can be used as an argument to the functions within the <fenv.h> header
98  * that manage the floating-point environment, namely fesetenv() and
99  * feupdateenv().
100  *
101  * x87 fpu registers are 16bit wide. The upper bits, 31-16, are marked as
102  * RESERVED. We provide a partial floating-point environment, where we
103  * define only the lower bits. The reserved bits are extracted and set by
104  * the consumers of FE_DFL_ENV, during runtime.
105  */
106 fenv_t __fe_dfl_env = {
107 	{
108 		__NetBSD_NPXCW__,	/* Control word register */
109 		0x00000000,		/* Status word register */
110 		0x0000ffff,		/* Tag word register */
111 		{
112 			0x00000000,
113 			0x00000000,
114 			0x00000000,
115 			0x00000000,
116 		},
117 	},
118 	__INITIAL_MXCSR__       /* MXCSR register */
119 };
120 #define FE_DFL_ENV      ((const fenv_t *) &__fe_dfl_env)
121 
122 static void __init_libm(void) __attribute__ ((constructor, used));
123 
124 static void __init_libm(void)
125 {
126 	uint16_t control;
127 
128 	__fnstcw(&control);
129 	__fe_dfl_env.x87.control = control;
130 }
131 
132 
133 /*
134  * The feclearexcept() function clears the supported floating-point exceptions
135  * represented by `excepts'.
136  */
137 int
138 feclearexcept(int excepts)
139 {
140 	fenv_t fenv;
141 	int ex;
142 
143 	_DIAGASSERT((excepts & ~FE_ALL_EXCEPT) == 0);
144 
145 	ex = excepts & FE_ALL_EXCEPT;
146 
147 	/* Store the current x87 floating-point environment */
148 	__fnstenv(&fenv);
149 
150 	/* Clear the requested floating-point exceptions */
151 	fenv.x87.status &= ~ex;
152 
153 	/* Load the x87 floating-point environent */
154 	__fldenv(fenv);
155 
156 	/* Same for SSE environment */
157 	__stmxcsr(&fenv.mxcsr);
158 	fenv.mxcsr &= ~ex;
159 	__ldmxcsr(fenv.mxcsr);
160 
161 	/* Success */
162 	return (0);
163 }
164 
165 /*
166  * The fegetexceptflag() function stores an implementation-defined
167  * representation of the states of the floating-point status flags indicated by
168  * the argument excepts in the object pointed to by the argument flagp.
169  */
170 int
171 fegetexceptflag(fexcept_t *flagp, int excepts)
172 {
173 	uint32_t mxcsr;
174 	uint16_t x87_status;
175 	int ex;
176 
177 	_DIAGASSERT(flagp != NULL);
178 	_DIAGASSERT((excepts & ~FE_ALL_EXCEPT) == 0);
179 
180 	ex = excepts & FE_ALL_EXCEPT;
181 
182 	/* Store the current x87 status register */
183 	__fnstsw(&x87_status);
184 
185 	/* Store the MXCSR register */
186 	__stmxcsr(&mxcsr);
187 
188 	/* Store the results in flagp */
189 	*flagp = (x87_status | mxcsr) & ex;
190 
191 	/* Success */
192 	return (0);
193 }
194 
195 /*
196  * The feraiseexcept() function raises the supported floating-point exceptions
197  * represented by the argument `excepts'.
198  *
199  * The standard explicitly allows us to execute an instruction that has the
200  * exception as a side effect, but we choose to manipulate the status register
201  * directly.
202  *
203  * The validation of input is being deferred to fesetexceptflag().
204  */
205 int
206 feraiseexcept(int excepts)
207 {
208 	int ex;
209 
210 	_DIAGASSERT((excepts & ~FE_ALL_EXCEPT) == 0);
211 
212 	ex = excepts & FE_ALL_EXCEPT;
213 	fesetexceptflag((unsigned int *)&ex, ex);
214 	__fwait();
215 
216 	/* Success */
217 	return (0);
218 }
219 
220 /*
221  * This function sets the floating-point status flags indicated by the argument
222  * `excepts' to the states stored in the object pointed to by `flagp'. It does
223  * NOT raise any floating-point exceptions, but only sets the state of the flags.
224  */
225 int
226 fesetexceptflag(const fexcept_t *flagp, int excepts)
227 {
228 	fenv_t fenv;
229 	int ex;
230 
231 	_DIAGASSERT(flagp != NULL);
232 	_DIAGASSERT((excepts & ~FE_ALL_EXCEPT) == 0);
233 
234 	ex = excepts & FE_ALL_EXCEPT;
235 
236 	/* Store the current x87 floating-point environment */
237 	__fnstenv(&fenv);
238 
239 	/* Set the requested status flags */
240 	fenv.x87.status |= *flagp & ex;
241 
242 	/* Load the x87 floating-point environent */
243 	__fldenv(fenv);
244 
245 	/* Same for SSE environment */
246 	__stmxcsr(&fenv.mxcsr);
247 	fenv.mxcsr |= *flagp & ex;
248 	__ldmxcsr(fenv.mxcsr);
249 
250 	/* Success */
251 	return (0);
252 }
253 
254 /*
255  * The fetestexcept() function determines which of a specified subset of the
256  * floating-point exception flags are currently set. The `excepts' argument
257  * specifies the floating-point status flags to be queried.
258  */
259 int
260 fetestexcept(int excepts)
261 {
262 	fenv_t fenv;
263 	uint32_t mxcsr;
264 	uint16_t status;
265 	int ex;
266 
267 	_DIAGASSERT((excepts & ~FE_ALL_EXCEPT) == 0);
268 
269 	ex = excepts & FE_ALL_EXCEPT;
270 
271 	/* Store the current x87 floating-point environment */
272 	memset(&fenv, 0, sizeof(fenv));
273 
274 	__fnstenv(&fenv);
275 	__fnstsw(&status);
276 
277 	/* Store the MXCSR register state */
278 	__stmxcsr(&fenv.mxcsr);
279 	__stmxcsr(&mxcsr);
280 
281 	return ((fenv.x87.status | fenv.mxcsr) & ex);
282 }
283 
284 /*
285  * The fegetround() function gets the current rounding direction.
286  */
287 int
288 fegetround(void)
289 {
290 	uint32_t mxcsr;
291 	uint16_t control;
292 
293 	/*
294 	 * We check both the x87 floating-point unit _and_ the SSE unit.
295 	 * Normally, those two must agree with respect to each other. If they
296 	 * don't, it's not our fault and the result is non-determinable, in
297 	 * which case POSIX says that a negative value should be returned.
298 	 */
299 	__fnstcw(&control);
300 	__stmxcsr(&mxcsr);
301 
302 	if ((control & _X87_ROUNDING_MASK)
303 	    != ((mxcsr & _SSE_ROUNDING_MASK) >> 3)) {
304 		return (-1);
305 	}
306 
307 	return (control & _X87_ROUNDING_MASK);
308 }
309 
310 /*
311  * The fesetround() function establishes the rounding direction represented by
312  * its argument `round'. If the argument is not equal to the value of a rounding
313  * direction macro, the rounding direction is not changed.
314  */
315 int
316 fesetround(int round)
317 {
318 	uint32_t  mxcsr;
319 	uint16_t control;
320 
321 	/* Check whether requested rounding direction is supported */
322 	if (round & (~_X87_ROUNDING_MASK))
323 		return (-1);
324 
325 	/* Store the current x87 control word register  */
326 	__fnstcw(&control);
327 
328 	/*
329 	 * Set the rounding direction
330 	 * Rounding Control is bits 10-11, so shift appropriately
331 	 */
332 	control &= ~_X87_ROUNDING_MASK;
333 	control |= round;
334 
335 	/* Load the x87 control word register */
336 	__fldcw(control);
337 
338 	/*
339 	 * Same for the SSE environment
340 	 * Rounding Control is bits 13-14, so shift appropriately
341 	 */
342 	__stmxcsr(&mxcsr);
343 	mxcsr &= ~_SSE_ROUNDING_MASK;
344 	mxcsr |= (round << _SSE_ROUND_SHIFT);
345 	__ldmxcsr(mxcsr);
346 
347 	/* Success */
348 	return (0);
349 }
350 
351 /*
352  * The fegetenv() function attempts to store the current floating-point
353  * environment in the object pointed to by envp.
354  */
355 int
356 fegetenv(fenv_t *envp)
357 {
358 	_DIAGASSERT(envp != NULL);
359 
360 	/* Store the current x87 floating-point environment */
361 	__fnstenv(envp);
362 
363 	/* Store the MXCSR register state */
364 	__stmxcsr(&envp->mxcsr);
365 
366      /*
367       * When an FNSTENV instruction is executed, all pending exceptions are
368       * essentially lost (either the x87 FPU status register is cleared or all
369       * exceptions are masked).
370       *
371       * 8.6 X87 FPU EXCEPTION SYNCHRONIZATION -
372       * Intel(R) 64 and IA-32 Architectures Softare Developer's Manual - Vol 1
373       *
374       */
375 	__fldcw(envp->x87.control);
376 
377 	/* Success */
378 	return (0);
379 }
380 
381 /*
382  * The feholdexcept() function saves the current floating-point environment
383  * in the object pointed to by envp, clears the floating-point status flags, and
384  * then installs a non-stop (continue on floating-point exceptions) mode, if
385  * available, for all floating-point exceptions.
386  */
387 int
388 feholdexcept(fenv_t *envp)
389 {
390 	uint32_t mxcsr;
391 
392 	_DIAGASSERT(envp != NULL);
393 
394 	/* Store the current x87 floating-point environment */
395 	__fnstenv(envp);
396 
397 	/* Clear all exception flags in FPU */
398 	__fnclex();
399 
400 	/* Store the MXCSR register state */
401 	__stmxcsr(&envp->mxcsr);
402 
403 	/* Clear exception flags in MXCSR XXX */
404 	mxcsr = envp->mxcsr;
405 	mxcsr &= ~FE_ALL_EXCEPT;
406 
407 	/* Mask all exceptions */
408 	mxcsr |= FE_ALL_EXCEPT << _SSE_EMASK_SHIFT;
409 
410 	__ldmxcsr(mxcsr);
411 
412 	/* Success */
413 	return (0);
414 }
415 
416 /*
417  * The fesetenv() function attempts to establish the floating-point environment
418  * represented by the object pointed to by envp. The argument `envp' points
419  * to an object set by a call to fegetenv() or feholdexcept(), or equal a
420  * floating-point environment macro. The fesetenv() function does not raise
421  * floating-point exceptions, but only installs the state of the floating-point
422  * status flags represented through its argument.
423  */
424 int
425 fesetenv(const fenv_t *envp)
426 {
427 	fenv_t fenv;
428 
429 	_DIAGASSERT(envp != NULL);
430 
431 	/* Store the x87 floating-point environment */
432 	memset(&fenv, 0, sizeof fenv);
433 	__fnstenv(&fenv);
434 
435 	__fe_dfl_env.x87.control = (fenv.x87.control & 0xffff0000)
436 	    | (__fe_dfl_env.x87.control & 0x0000ffff);
437 	__fe_dfl_env.x87.status = (fenv.x87.status & 0xffff0000)
438 	    | (__fe_dfl_env.x87.status & 0x0000ffff);
439 	__fe_dfl_env.x87.tag = (fenv.x87.tag & 0xffff0000)
440 	    | (__fe_dfl_env.x87.tag & 0x0000ffff);
441 	__fe_dfl_env.x87.others[3] = (fenv.x87.others[3] & 0xffff0000)
442 	    | (__fe_dfl_env.x87.others[3] & 0x0000ffff);
443 	__fldenv(*envp);
444 
445 	/* Store the MXCSR register */
446 	__ldmxcsr(envp->mxcsr);
447 
448 	/* Success */
449 	return (0);
450 }
451 
452 /*
453  * The feupdateenv() function saves the currently raised floating-point
454  * exceptions in its automatic storage, installs the floating-point environment
455  * represented by the object pointed to by `envp', and then raises the saved
456  * floating-point exceptions. The argument `envp' shall point to an object set
457  * by a call to feholdexcept() or fegetenv(), or equal a floating-point
458  * environment macro.
459  */
460 int
461 feupdateenv(const fenv_t *envp)
462 {
463 	fenv_t fenv;
464 	uint32_t mxcsr;
465 	uint16_t sw;
466 
467 	_DIAGASSERT(envp != NULL);
468 
469 	/* Store the x87 floating-point environment */
470 	memset(&fenv, 0, sizeof(fenv));
471 	__fnstenv(&fenv);
472 
473 	__fe_dfl_env.x87.control = (fenv.x87.control & 0xffff0000)
474 	    | (__fe_dfl_env.x87.control & 0x0000ffff);
475 	__fe_dfl_env.x87.status = (fenv.x87.status & 0xffff0000)
476 	    | (__fe_dfl_env.x87.status & 0x0000ffff);
477 	__fe_dfl_env.x87.tag = (fenv.x87.tag & 0xffff0000)
478 	    | (__fe_dfl_env.x87.tag & 0x0000ffff);
479 	__fe_dfl_env.x87.others[3] = (fenv.x87.others[3] & 0xffff0000)
480 	    | (__fe_dfl_env.x87.others[3] & 0x0000ffff);
481 
482 	/* Store the x87 status register */
483 	__fnstsw(&sw);
484 
485 	/* Store the MXCSR register */
486 	__stmxcsr(&mxcsr);
487 
488 	/* Install new floating-point environment */
489 	fesetenv(envp);
490 
491 	/* Raise any previously accumulated exceptions */
492 	feraiseexcept((sw | mxcsr) & FE_ALL_EXCEPT);
493 
494 	/* Success */
495 	return (0);
496 }
497 
498 /*
499  * The following functions are extentions to the standard
500  */
501 int
502 feenableexcept(int mask)
503 {
504 	uint32_t mxcsr, omask;
505 	uint16_t control;
506 
507 	_DIAGASSERT((mask & ~FE_ALL_EXCEPT) == 0);
508 	mask &= FE_ALL_EXCEPT;
509 
510 	__fnstcw(&control);
511 	__stmxcsr(&mxcsr);
512 
513 	omask = (control | mxcsr >> _SSE_EMASK_SHIFT) & FE_ALL_EXCEPT;
514 	control &= ~mask;
515 	__fldcw(control);
516 
517 	mxcsr &= ~(mask << _SSE_EMASK_SHIFT);
518 	__ldmxcsr(mxcsr);
519 
520 	return (FE_ALL_EXCEPT & ~omask);
521 
522 }
523 
524 int
525 fedisableexcept(int mask)
526 {
527 	uint32_t mxcsr, omask;
528 	uint16_t control;
529 
530 	_DIAGASSERT((mask & ~FE_ALL_EXCEPT) == 0);
531 
532 	__fnstcw(&control);
533 	__stmxcsr(&mxcsr);
534 
535 	omask = (control | mxcsr >> _SSE_EMASK_SHIFT) & FE_ALL_EXCEPT;
536 	control |= mask;
537 	__fldcw(control);
538 
539 	mxcsr |= mask << _SSE_EMASK_SHIFT;
540 	__ldmxcsr(mxcsr);
541 
542 	return (FE_ALL_EXCEPT & ~omask);
543 }
544 
545 int
546 fegetexcept(void)
547 {
548 	uint16_t control;
549 
550 	/*
551 	 * We assume that the masks for the x87 and the SSE unit are
552 	 * the same.
553 	 */
554 	__fnstcw(&control);
555 
556 	return (~control & FE_ALL_EXCEPT);
557 }
558 
559