13f0ea6ac41e1ab0db475351fa914baaa518ee77
[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/compose.h"
33 #include "pbd/fpu.h"
34 #include "pbd/error.h"
35
36 #include "i18n.h"
37
38 using namespace PBD;
39 using namespace std;
40
41 FPU* FPU::_instance (0);
42
43 #if ( (defined __x86_64__) || (defined __i386__) || (defined _M_X64) || (defined _M_IX86) ) // ARCH_X86
44 #ifndef PLATFORM_WINDOWS
45
46 /* use __cpuid() as the name to match the MSVC/mingw intrinsic */
47
48 static void
49 __cpuid(int regs[4], int cpuid_leaf)
50 {
51         int eax, ebx, ecx, edx;
52         asm volatile (
53 #if defined(__i386__)
54                 "pushl %%ebx;\n\t"
55 #endif
56                 "movl %4, %%eax;\n\t"
57                 "cpuid;\n\t"
58                 "movl %%eax, %0;\n\t"
59                 "movl %%ebx, %1;\n\t"
60                 "movl %%ecx, %2;\n\t"
61                 "movl %%edx, %3;\n\t"
62 #if defined(__i386__)
63                 "popl %%ebx;\n\t"
64 #endif
65                 :"=m" (eax), "=m" (ebx), "=m" (ecx), "=m" (edx)
66                 :"r" (cpuid_leaf)
67                 :"%eax",
68 #if !defined(__i386__)
69                  "%ebx",
70 #endif
71                  "%ecx", "%edx");
72         
73         regs[0] = eax;
74         regs[1] = ebx;
75         regs[2] = ecx;
76         regs[3] = edx;
77 }
78
79 #endif /* !PLATFORM_WINDOWS */
80
81 #ifndef COMPILER_MSVC
82
83 static uint64_t
84 _xgetbv (uint32_t xcr)
85 {
86 #ifdef __APPLE__
87         /* it would be nice to make this work on OS X but as long we use veclib,
88            we don't really need to know about SSE/AVX on that platform.
89         */
90         return 0;
91 #else
92         uint32_t eax, edx;
93         __asm__ volatile ("xgetbv" : "=a" (eax), "=d" (edx) : "c" (xcr));
94         return (static_cast<uint64_t>(edx) << 32) | eax;
95 #endif
96 }
97
98 #endif /* !COMPILER_MSVC */
99 #endif /* ARCH_X86 */
100
101 #ifndef _XCR_XFEATURE_ENABLED_MASK
102 #define _XCR_XFEATURE_ENABLED_MASK 0
103 #endif
104
105 FPU*
106 FPU::instance()
107 {
108         if (!_instance) {
109                 _instance = new FPU;
110         }
111
112         return _instance;
113 }
114
115 FPU::FPU ()
116         : _flags ((Flags) 0)
117 {
118         if (_instance) {
119                 error << _("FPU object instantiated more than once") << endmsg;
120         }
121
122 #if !( (defined __x86_64__) || (defined __i386__) || (defined _M_X64) || (defined _M_IX86) ) // !ARCH_X86
123         /* Non-Intel architecture, nothing to do here */
124         return;
125 #else
126
127         /* Get the CPU vendor just for kicks */
128
129         // __cpuid with an InfoType argument of 0 returns the number of
130         // valid Ids in CPUInfo[0] and the CPU identification string in
131         // the other three array elements. The CPU identification string is
132         // not in linear order. The code below arranges the information
133         // in a human readable form. The human readable order is CPUInfo[1] |
134         // CPUInfo[3] | CPUInfo[2]. CPUInfo[2] and CPUInfo[3] are swapped
135         // before using memcpy to copy these three array elements to cpu_string.
136
137         int cpu_info[4];
138         char cpu_string[48];
139         string cpu_vendor;
140
141         __cpuid (cpu_info, 0);
142
143         int num_ids = cpu_info[0];
144         std::swap(cpu_info[2], cpu_info[3]);
145         memcpy(cpu_string, &cpu_info[1], 3 * sizeof(cpu_info[1]));
146         cpu_vendor.assign(cpu_string, 3 * sizeof(cpu_info[1]));
147
148         info << string_compose (_("CPU vendor: %1"), cpu_vendor) << endmsg;
149
150         if (num_ids > 0) {
151         
152                 /* Now get CPU/FPU flags */
153         
154                 __cpuid (cpu_info, 1);
155
156                 if ((cpu_info[2] & (1<<27)) /* AVX */ &&
157                     (cpu_info[2] & (1<<28) /* (OS)XSAVE */) &&
158                     (_xgetbv (_XCR_XFEATURE_ENABLED_MASK) & 0x6)) { /* OS really supports XSAVE */
159                         info << _("AVX-capable processor") << endmsg;
160                         _flags = Flags (_flags | (HasAVX) );
161                 }
162
163                 if (cpu_info[3] & (1<<25)) {
164                         _flags = Flags (_flags | (HasSSE|HasFlushToZero));
165                 }
166
167                 if (cpu_info[3] & (1<<26)) {
168                         _flags = Flags (_flags | HasSSE2);
169                 }
170
171                 /* Figure out CPU/FPU denormal handling capabilities */
172         
173                 if (cpu_info[3] & (1 << 24)) {
174                 
175                         char** fxbuf = 0;
176                 
177                         /* DAZ wasn't available in the first version of SSE. Since
178                            setting a reserved bit in MXCSR causes a general protection
179                            fault, we need to be able to check the availability of this
180                            feature without causing problems. To do this, one needs to
181                            set up a 512-byte area of memory to save the SSE state to,
182                            using fxsave, and then one needs to inspect bytes 28 through
183                            31 for the MXCSR_MASK value. If bit 6 is set, DAZ is
184                            supported, otherwise, it isn't.
185                         */
186
187 #ifndef HAVE_POSIX_MEMALIGN
188 #  ifdef PLATFORM_WINDOWS
189                         fxbuf = (char **) _aligned_malloc (sizeof (char *), 16);
190                         assert (fxbuf);
191                         *fxbuf = (char *) _aligned_malloc (512, 16);
192                         assert (*fxbuf);
193 #  else
194 #  warning using default malloc for aligned memory
195                         fxbuf = (char **) malloc (sizeof (char *));
196                         assert (fxbuf);
197                         *fxbuf = (char *) malloc (512);
198                         assert (*fxbuf);
199 #  endif
200 #else
201                         (void) posix_memalign ((void **) &fxbuf, 16, sizeof (char *));
202                         assert (fxbuf);
203                         (void) posix_memalign ((void **) fxbuf, 16, 512);
204                         assert (*fxbuf);
205 #endif                  
206                 
207                         memset (*fxbuf, 0, 512);
208                 
209 #ifdef COMPILER_MSVC
210                         char *buf = *fxbuf;
211                         __asm {
212                                 mov eax, buf
213                                         fxsave   [eax]
214                                         };
215 #else
216                         asm volatile (
217                                 "fxsave (%0)"
218                                 :
219                                 : "r" (*fxbuf)
220                                 : "memory"
221                                 );
222 #endif
223                 
224                         uint32_t mxcsr_mask = *((uint32_t*) &((*fxbuf)[28]));
225                 
226                         /* if the mask is zero, set its default value (from intel specs) */
227                 
228                         if (mxcsr_mask == 0) {
229                                 mxcsr_mask = 0xffbf;
230                         }
231                 
232                         if (mxcsr_mask & (1<<6)) {
233                                 _flags = Flags (_flags | HasDenormalsAreZero);
234                         } 
235                 
236 #if !defined HAVE_POSIX_MEMALIGN && defined PLATFORM_WINDOWS
237                         _aligned_free (*fxbuf);
238                         _aligned_free (fxbuf);
239 #else
240                         free (*fxbuf);
241                         free (fxbuf);
242 #endif
243                 }
244
245                 /* finally get the CPU brand */
246
247                 __cpuid (cpu_info, 0x80000000);
248
249                 const int parameter_end = 0x80000004;
250                 string cpu_brand;
251         
252                 if (cpu_info[0] >= parameter_end) {
253                         char* cpu_string_ptr = cpu_string;
254                 
255                         for (int parameter = 0x80000002; parameter <= parameter_end &&
256                                      cpu_string_ptr < &cpu_string[sizeof(cpu_string)]; parameter++) {
257                                 __cpuid(cpu_info, parameter);
258                                 memcpy(cpu_string_ptr, cpu_info, sizeof(cpu_info));
259                                 cpu_string_ptr += sizeof(cpu_info);
260                         }
261                         cpu_brand.assign(cpu_string, cpu_string_ptr - cpu_string);
262                         info << string_compose (_("CPU brand: %1"), cpu_brand) << endmsg;
263                 } 
264         }
265 #endif /* !ARCH_X86 */
266 }                       
267
268 FPU::~FPU ()
269 {
270 }