xref: /netbsd-src/lib/libm/arch/x86_64/fenv.c (revision fa59e253640d99e261beeb1113b024afbb5133cd)
1 /* $NetBSD: fenv.c,v 1.11 2024/02/20 03:53:48 riastradh 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.11 2024/02/20 03:53:48 riastradh 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 
__init_libm(void)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
feclearexcept(int excepts)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 environment */
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
fegetexceptflag(fexcept_t * flagp,int excepts)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
feraiseexcept(int excepts)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
fesetexceptflag(const fexcept_t * flagp,int excepts)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 environment */
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
fetestexcept(int excepts)260 fetestexcept(int excepts)
261 {
262 	uint32_t mxcsr;
263 	uint16_t status;
264 	int ex;
265 
266 	_DIAGASSERT((excepts & ~FE_ALL_EXCEPT) == 0);
267 
268 	ex = excepts & FE_ALL_EXCEPT;
269 
270 	__fnstsw(&status);
271 	__stmxcsr(&mxcsr);
272 
273 	return ((status | mxcsr) & ex);
274 }
275 
276 /*
277  * The fegetround() function gets the current rounding direction.
278  */
279 int
fegetround(void)280 fegetround(void)
281 {
282 	uint32_t mxcsr;
283 	uint16_t control;
284 
285 	/*
286 	 * We check both the x87 floating-point unit _and_ the SSE unit.
287 	 * Normally, those two must agree with respect to each other. If they
288 	 * don't, it's not our fault and the result is non-determinable, in
289 	 * which case POSIX says that a negative value should be returned.
290 	 */
291 	__fnstcw(&control);
292 	__stmxcsr(&mxcsr);
293 
294 	if ((control & _X87_ROUNDING_MASK)
295 	    != ((mxcsr & _SSE_ROUNDING_MASK) >> 3)) {
296 		return (-1);
297 	}
298 
299 	return (control & _X87_ROUNDING_MASK);
300 }
301 
302 /*
303  * The fesetround() function establishes the rounding direction represented by
304  * its argument `round'. If the argument is not equal to the value of a rounding
305  * direction macro, the rounding direction is not changed.
306  */
307 int
fesetround(int round)308 fesetround(int round)
309 {
310 	uint32_t  mxcsr;
311 	uint16_t control;
312 
313 	/* Check whether requested rounding direction is supported */
314 	if (round & (~_X87_ROUNDING_MASK))
315 		return (-1);
316 
317 	/* Store the current x87 control word register  */
318 	__fnstcw(&control);
319 
320 	/*
321 	 * Set the rounding direction
322 	 * Rounding Control is bits 10-11, so shift appropriately
323 	 */
324 	control &= ~_X87_ROUNDING_MASK;
325 	control |= round;
326 
327 	/* Load the x87 control word register */
328 	__fldcw(control);
329 
330 	/*
331 	 * Same for the SSE environment
332 	 * Rounding Control is bits 13-14, so shift appropriately
333 	 */
334 	__stmxcsr(&mxcsr);
335 	mxcsr &= ~_SSE_ROUNDING_MASK;
336 	mxcsr |= (round << _SSE_ROUND_SHIFT);
337 	__ldmxcsr(mxcsr);
338 
339 	/* Success */
340 	return (0);
341 }
342 
343 /*
344  * The fegetenv() function attempts to store the current floating-point
345  * environment in the object pointed to by envp.
346  */
347 int
fegetenv(fenv_t * envp)348 fegetenv(fenv_t *envp)
349 {
350 	_DIAGASSERT(envp != NULL);
351 
352 	/* Store the current x87 floating-point environment */
353 	__fnstenv(envp);
354 
355 	/* Store the MXCSR register state */
356 	__stmxcsr(&envp->mxcsr);
357 
358      /*
359       * When an FNSTENV instruction is executed, all pending exceptions are
360       * essentially lost (either the x87 FPU status register is cleared or all
361       * exceptions are masked).
362       *
363       * 8.6 X87 FPU EXCEPTION SYNCHRONIZATION -
364       * Intel(R) 64 and IA-32 Architectures Software Developer's Manual - Vol 1
365       *
366       */
367 	__fldcw(envp->x87.control);
368 
369 	/* Success */
370 	return (0);
371 }
372 
373 /*
374  * The feholdexcept() function saves the current floating-point environment
375  * in the object pointed to by envp, clears the floating-point status flags, and
376  * then installs a non-stop (continue on floating-point exceptions) mode, if
377  * available, for all floating-point exceptions.
378  */
379 int
feholdexcept(fenv_t * envp)380 feholdexcept(fenv_t *envp)
381 {
382 	uint32_t mxcsr;
383 
384 	_DIAGASSERT(envp != NULL);
385 
386 	/* Store the current x87 floating-point environment */
387 	__fnstenv(envp);
388 
389 	/* Clear all exception flags in FPU */
390 	__fnclex();
391 
392 	/* Store the MXCSR register state */
393 	__stmxcsr(&envp->mxcsr);
394 
395 	/* Clear exception flags in MXCSR XXX */
396 	mxcsr = envp->mxcsr;
397 	mxcsr &= ~FE_ALL_EXCEPT;
398 
399 	/* Mask all exceptions */
400 	mxcsr |= FE_ALL_EXCEPT << _SSE_EMASK_SHIFT;
401 
402 	__ldmxcsr(mxcsr);
403 
404 	/* Success */
405 	return (0);
406 }
407 
408 /*
409  * The fesetenv() function attempts to establish the floating-point environment
410  * represented by the object pointed to by envp. The argument `envp' points
411  * to an object set by a call to fegetenv() or feholdexcept(), or equal a
412  * floating-point environment macro. The fesetenv() function does not raise
413  * floating-point exceptions, but only installs the state of the floating-point
414  * status flags represented through its argument.
415  */
416 int
fesetenv(const fenv_t * envp)417 fesetenv(const fenv_t *envp)
418 {
419 	fenv_t fenv;
420 
421 	_DIAGASSERT(envp != NULL);
422 
423 	/* Store the x87 floating-point environment */
424 	memset(&fenv, 0, sizeof fenv);
425 	__fnstenv(&fenv);
426 
427 	__fe_dfl_env.x87.control = (fenv.x87.control & 0xffff0000)
428 	    | (__fe_dfl_env.x87.control & 0x0000ffff);
429 	__fe_dfl_env.x87.status = (fenv.x87.status & 0xffff0000)
430 	    | (__fe_dfl_env.x87.status & 0x0000ffff);
431 	__fe_dfl_env.x87.tag = (fenv.x87.tag & 0xffff0000)
432 	    | (__fe_dfl_env.x87.tag & 0x0000ffff);
433 	__fe_dfl_env.x87.others[3] = (fenv.x87.others[3] & 0xffff0000)
434 	    | (__fe_dfl_env.x87.others[3] & 0x0000ffff);
435 	__fldenv(*envp);
436 
437 	/* Store the MXCSR register */
438 	__ldmxcsr(envp->mxcsr);
439 
440 	/* Success */
441 	return (0);
442 }
443 
444 /*
445  * The feupdateenv() function saves the currently raised floating-point
446  * exceptions in its automatic storage, installs the floating-point environment
447  * represented by the object pointed to by `envp', and then raises the saved
448  * floating-point exceptions. The argument `envp' shall point to an object set
449  * by a call to feholdexcept() or fegetenv(), or equal a floating-point
450  * environment macro.
451  */
452 int
feupdateenv(const fenv_t * envp)453 feupdateenv(const fenv_t *envp)
454 {
455 	fenv_t fenv;
456 	uint32_t mxcsr;
457 	uint16_t sw;
458 
459 	_DIAGASSERT(envp != NULL);
460 
461 	/* Store the x87 floating-point environment */
462 	memset(&fenv, 0, sizeof(fenv));
463 	__fnstenv(&fenv);
464 
465 	__fe_dfl_env.x87.control = (fenv.x87.control & 0xffff0000)
466 	    | (__fe_dfl_env.x87.control & 0x0000ffff);
467 	__fe_dfl_env.x87.status = (fenv.x87.status & 0xffff0000)
468 	    | (__fe_dfl_env.x87.status & 0x0000ffff);
469 	__fe_dfl_env.x87.tag = (fenv.x87.tag & 0xffff0000)
470 	    | (__fe_dfl_env.x87.tag & 0x0000ffff);
471 	__fe_dfl_env.x87.others[3] = (fenv.x87.others[3] & 0xffff0000)
472 	    | (__fe_dfl_env.x87.others[3] & 0x0000ffff);
473 
474 	/* Store the x87 status register */
475 	__fnstsw(&sw);
476 
477 	/* Store the MXCSR register */
478 	__stmxcsr(&mxcsr);
479 
480 	/* Install new floating-point environment */
481 	fesetenv(envp);
482 
483 	/* Raise any previously accumulated exceptions */
484 	feraiseexcept((sw | mxcsr) & FE_ALL_EXCEPT);
485 
486 	/* Success */
487 	return (0);
488 }
489 
490 /*
491  * The following functions are extensions to the standard
492  */
493 int
feenableexcept(int mask)494 feenableexcept(int mask)
495 {
496 	uint32_t mxcsr, omask;
497 	uint16_t control;
498 
499 	_DIAGASSERT((mask & ~FE_ALL_EXCEPT) == 0);
500 	mask &= FE_ALL_EXCEPT;
501 
502 	__fnstcw(&control);
503 	__stmxcsr(&mxcsr);
504 
505 	omask = (control | mxcsr >> _SSE_EMASK_SHIFT) & FE_ALL_EXCEPT;
506 	control &= ~mask;
507 	__fldcw(control);
508 
509 	mxcsr &= ~(mask << _SSE_EMASK_SHIFT);
510 	__ldmxcsr(mxcsr);
511 
512 	return (FE_ALL_EXCEPT & ~omask);
513 
514 }
515 
516 int
fedisableexcept(int mask)517 fedisableexcept(int mask)
518 {
519 	uint32_t mxcsr, omask;
520 	uint16_t control;
521 
522 	_DIAGASSERT((mask & ~FE_ALL_EXCEPT) == 0);
523 
524 	__fnstcw(&control);
525 	__stmxcsr(&mxcsr);
526 
527 	omask = (control | mxcsr >> _SSE_EMASK_SHIFT) & FE_ALL_EXCEPT;
528 	control |= mask;
529 	__fldcw(control);
530 
531 	mxcsr |= mask << _SSE_EMASK_SHIFT;
532 	__ldmxcsr(mxcsr);
533 
534 	return (FE_ALL_EXCEPT & ~omask);
535 }
536 
537 int
fegetexcept(void)538 fegetexcept(void)
539 {
540 	uint16_t control;
541 
542 	/*
543 	 * We assume that the masks for the x87 and the SSE unit are
544 	 * the same.
545 	 */
546 	__fnstcw(&control);
547 
548 	return (~control & FE_ALL_EXCEPT);
549 }
550 
551