9fde6ab133a0524e6e1eea7709824a58a29d6e5c
[ardour.git] / libs / pbd / fpu.cc
1 /*
2     Copyright (C) 2012 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "libpbd-config.h"
21
22 #define _XOPEN_SOURCE 600
23 #include <cstring> // for memset
24 #include <cstdlib>
25 #include <stdint.h>
26 #include <assert.h>
27
28 #ifdef PLATFORM_WINDOWS
29 #include <intrin.h>
30 #endif
31
32 #include "pbd/fpu.h"
33 #include "pbd/error.h"
34
35 #include "i18n.h"
36
37 using namespace PBD;
38 using namespace std;
39
40 /* This function is provided by MSVC are part of the compiler instrinsics. We
41  * don't care about this on OS X (though perhaps we should), but we need to
42  * test AVX support on Linux also. It doesn't hurt that this works on OS X 
43  * also. 
44  */
45
46 #if __GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 4
47 static inline unsigned long long _xgetbv(unsigned int index){
48   unsigned int eax, edx;
49   __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index));
50   return ((unsigned long long)edx << 32) | eax;
51 }
52 #else
53 #define _xgetbv() 0
54 #endif
55
56 FPU::FPU ()
57 {
58         unsigned long cpuflags = 0;
59
60         _flags = Flags (0);
61
62 #if !( (defined __x86_64__) || (defined __i386__) || (defined _M_X64) || (defined _M_IX86) ) // !ARCH_X86
63         return;
64 #else
65
66 #ifdef PLATFORM_WINDOWS
67
68         // Get CPU flags using Microsoft function
69         // It works for both 64 and 32 bit systems
70         // no need to use assembler for getting info from register, this function does this for us
71         int cpuInfo[4];
72         __cpuid (cpuInfo, 1);
73         cpuflags = cpuInfo[3];
74
75 #else   
76
77 #ifndef _LP64 /* *nix; 32 bit version. This odd macro constant is required because we need something that identifies this as a 32 bit
78                  build on Linux and on OS X. Anything that serves this purpose will do, but this is the best thing we've identified
79                  so far.
80               */
81         
82         asm volatile (
83                 "mov $1, %%eax\n"
84                 "pushl %%ebx\n"
85                 "cpuid\n"
86                 "movl %%edx, %0\n"
87                 "popl %%ebx\n"
88                 : "=r" (cpuflags)
89                 : 
90                 : "%eax", "%ecx", "%edx"
91                 );
92         
93 #else /* *nix; 64 bit version */
94         
95         /* asm notes: although we explicitly save&restore rbx, we must tell
96            gcc that ebx,rbx is clobbered so that it doesn't try to use it as an intermediate
97            register when storing rbx. gcc 4.3 didn't make this "mistake", but gcc 4.4
98            does, at least on x86_64.
99         */
100
101         asm volatile (
102                 "pushq %%rbx\n"
103                 "movq $1, %%rax\n"
104                 "cpuid\n"
105                 "movq %%rdx, %0\n"
106                 "popq %%rbx\n"
107                 : "=r" (cpuflags)
108                 : 
109                 : "%rax", "%rbx", "%rcx", "%rdx"
110                 );
111
112 #endif /* _LP64 */
113 #endif /* PLATFORM_WINDOWS */
114
115         if (cpuflags & ((1<<27) /* AVX */ |(1<<28) /* XGETBV */)) {
116
117                 std::cerr << "Looks like AVX\n";
118                 
119                 /* now check if YMM resters state is saved: which means OS does
120                  * know about new YMM registers and saves them during context
121                  * switches it's true for most cases, but we must be sure
122                  *
123                  * giving 0 as the argument to _xgetbv() fetches the 
124                  * XCR_XFEATURE_ENABLED_MASK, which we need to check for 
125                  * the 2nd and 3rd bits, indicating correct register save/restore.
126                  */
127
128                 uint64_t xcrFeatureMask = _xgetbv (0);
129
130                 if (xcrFeatureMask & 0x6) {
131                         std::cerr << "Definitely AVX\n";
132                         _flags = Flags (_flags | (HasAVX) );
133                 }
134         }
135
136         if (cpuflags & (1<<25)) {
137                 _flags = Flags (_flags | (HasSSE|HasFlushToZero));
138         }
139
140         if (cpuflags & (1<<26)) {
141                 _flags = Flags (_flags | HasSSE2);
142         }
143
144         if (cpuflags & (1 << 24)) {
145                 
146                 char** fxbuf = 0;
147                 
148                 /* DAZ wasn't available in the first version of SSE. Since
149                    setting a reserved bit in MXCSR causes a general protection
150                    fault, we need to be able to check the availability of this
151                    feature without causing problems. To do this, one needs to
152                    set up a 512-byte area of memory to save the SSE state to,
153                    using fxsave, and then one needs to inspect bytes 28 through
154                    31 for the MXCSR_MASK value. If bit 6 is set, DAZ is
155                    supported, otherwise, it isn't.
156                 */
157
158 #ifndef HAVE_POSIX_MEMALIGN
159 #  ifdef PLATFORM_WINDOWS
160                 fxbuf = (char **) _aligned_malloc (sizeof (char *), 16);
161                 assert (fxbuf);
162                 *fxbuf = (char *) _aligned_malloc (512, 16);
163                 assert (*fxbuf);
164 #  else
165 #  warning using default malloc for aligned memory
166                 fxbuf = (char **) malloc (sizeof (char *));
167                 assert (fxbuf);
168                 *fxbuf = (char *) malloc (512);
169                 assert (*fxbuf);
170 #  endif
171 #else
172                 (void) posix_memalign ((void **) &fxbuf, 16, sizeof (char *));
173                 assert (fxbuf);
174                 (void) posix_memalign ((void **) fxbuf, 16, 512);
175                 assert (*fxbuf);
176 #endif                  
177                 
178                 memset (*fxbuf, 0, 512);
179                 
180 #ifdef COMPILER_MSVC
181                 char *buf = *fxbuf;
182                 __asm {
183                         mov eax, buf
184                         fxsave   [eax]
185                 };
186 #else
187                 asm volatile (
188                         "fxsave (%0)"
189                         :
190                         : "r" (*fxbuf)
191                         : "memory"
192                         );
193 #endif
194                 
195                 uint32_t mxcsr_mask = *((uint32_t*) &((*fxbuf)[28]));
196                 
197                 /* if the mask is zero, set its default value (from intel specs) */
198                 
199                 if (mxcsr_mask == 0) {
200                         mxcsr_mask = 0xffbf;
201                 }
202                 
203                 if (mxcsr_mask & (1<<6)) {
204                         _flags = Flags (_flags | HasDenormalsAreZero);
205                 } 
206                 
207 #if !defined HAVE_POSIX_MEMALIGN && defined PLATFORM_WINDOWS
208                 _aligned_free (*fxbuf);
209                 _aligned_free (fxbuf);
210 #else
211                 free (*fxbuf);
212                 free (fxbuf);
213 #endif
214         }
215 #endif
216 }                       
217
218 FPU::~FPU ()
219 {
220 }