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