1 | /* Manipulating the FPU control word. -*- coding: utf-8 -*- |
2 | Copyright (C) 2007-2019 Free Software Foundation, Inc. |
3 | Written by Bruno Haible <bruno@clisp.org>, 2007. |
4 | |
5 | This program is free software: you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published by |
7 | the Free Software Foundation; either version 3 of the License, or |
8 | (at your option) any later version. |
9 | |
10 | This program is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | GNU General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU General Public License |
16 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
17 | |
18 | #ifndef _FPUCW_H |
19 | #define _FPUCW_H |
20 | |
21 | /* The i386 floating point hardware (the 387 compatible FPU, not the modern |
22 | SSE/SSE2 hardware) has a controllable rounding precision. It is specified |
23 | through the 'PC' bits in the FPU control word ('fctrl' register). (See |
24 | the GNU libc i386 <fpu_control.h> header for details.) |
25 | |
26 | On some platforms, such as Linux or Solaris, the default precision setting |
27 | is set to "extended precision". This means that 'long double' instructions |
28 | operate correctly, but 'double' computations often produce slightly |
29 | different results as on strictly IEEE 754 conforming systems. |
30 | |
31 | On some platforms, such as NetBSD, the default precision is set to |
32 | "double precision". This means that 'long double' instructions will operate |
33 | only as 'double', i.e. lead to wrong results. Similarly on FreeBSD 6.4, at |
34 | least for the division of 'long double' numbers. |
35 | |
36 | The FPU control word is under control of the application, i.e. it is |
37 | not required to be set either way by the ABI. (In fact, the i386 ABI |
38 | https://www.linux-mips.org/pub/linux/mips/doc/ABI/abi386-4.pdf page 3-12 = page 38 |
39 | is not clear about it. But in any case, gcc treats the control word |
40 | like a "preserved" register: it emits code that assumes that the control |
41 | word is preserved across calls, and it restores the control word at the |
42 | end of functions that modify it.) |
43 | |
44 | See Vincent Lefèvre's page https://www.vinc17.net/research/extended.en.html |
45 | for a good explanation. |
46 | See http://www.uwsg.iu.edu/hypermail/linux/kernel/0103.0/0453.html for |
47 | some argumentation which setting should be the default. */ |
48 | |
49 | /* This header file provides the following facilities: |
50 | fpucw_t integral type holding the value of 'fctrl' |
51 | FPU_PC_MASK bit mask denoting the precision control |
52 | FPU_PC_DOUBLE precision control for 53 bits mantissa |
53 | FPU_PC_EXTENDED precision control for 64 bits mantissa |
54 | GET_FPUCW () yields the current FPU control word |
55 | SET_FPUCW (word) sets the FPU control word |
56 | DECL_LONG_DOUBLE_ROUNDING variable declaration for |
57 | BEGIN/END_LONG_DOUBLE_ROUNDING |
58 | BEGIN_LONG_DOUBLE_ROUNDING () starts a sequence of instructions with |
59 | 'long double' safe operation precision |
60 | END_LONG_DOUBLE_ROUNDING () ends a sequence of instructions with |
61 | 'long double' safe operation precision |
62 | */ |
63 | |
64 | /* Inline assembler like this works only with GNU C. */ |
65 | #if (defined __i386__ || defined __x86_64__) && defined __GNUC__ |
66 | |
67 | typedef unsigned short fpucw_t; /* glibc calls this fpu_control_t */ |
68 | |
69 | # define FPU_PC_MASK 0x0300 |
70 | # define FPU_PC_DOUBLE 0x200 /* glibc calls this _FPU_DOUBLE */ |
71 | # define FPU_PC_EXTENDED 0x300 /* glibc calls this _FPU_EXTENDED */ |
72 | |
73 | # define GET_FPUCW() __extension__ \ |
74 | ({ fpucw_t _cw; \ |
75 | __asm__ __volatile__ ("fnstcw %0" : "=m" (*&_cw)); \ |
76 | _cw; \ |
77 | }) |
78 | # define SET_FPUCW(word) __extension__ \ |
79 | (void)({ fpucw_t _ncw = (word); \ |
80 | __asm__ __volatile__ ("fldcw %0" : : "m" (*&_ncw)); \ |
81 | }) |
82 | |
83 | # define DECL_LONG_DOUBLE_ROUNDING \ |
84 | fpucw_t oldcw; |
85 | # define BEGIN_LONG_DOUBLE_ROUNDING() \ |
86 | (void)(oldcw = GET_FPUCW (), \ |
87 | SET_FPUCW ((oldcw & ~FPU_PC_MASK) | FPU_PC_EXTENDED)) |
88 | # define END_LONG_DOUBLE_ROUNDING() \ |
89 | SET_FPUCW (oldcw) |
90 | |
91 | #else |
92 | |
93 | typedef unsigned int fpucw_t; |
94 | |
95 | # define FPU_PC_MASK 0 |
96 | # define FPU_PC_DOUBLE 0 |
97 | # define FPU_PC_EXTENDED 0 |
98 | |
99 | # define GET_FPUCW() 0 |
100 | # define SET_FPUCW(word) (void)(word) |
101 | |
102 | # define DECL_LONG_DOUBLE_ROUNDING |
103 | # define BEGIN_LONG_DOUBLE_ROUNDING() |
104 | # define END_LONG_DOUBLE_ROUNDING() |
105 | |
106 | #endif |
107 | |
108 | #endif /* _FPUCW_H */ |
109 | |