/src/php-src/Zend/zend_cpuinfo.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend Engine | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © Zend Technologies Ltd., a subsidiary company of | |
6 | | | Perforce Software, Inc., and Contributors. | |
7 | | +----------------------------------------------------------------------+ |
8 | | | This source file is subject to the Modified BSD License that is | |
9 | | | bundled with this package in the file LICENSE, and is available | |
10 | | | through the World Wide Web at <https://www.php.net/license/>. | |
11 | | | | |
12 | | | SPDX-License-Identifier: BSD-3-Clause | |
13 | | +----------------------------------------------------------------------+ |
14 | | | Authors: Xinchen Hui <xinchen.h@zend.com> | |
15 | | +----------------------------------------------------------------------+ |
16 | | */ |
17 | | |
18 | | #include "zend_cpuinfo.h" |
19 | | |
20 | | typedef struct _zend_cpu_info { |
21 | | uint32_t eax; |
22 | | uint32_t ebx; |
23 | | uint32_t ecx; |
24 | | uint32_t edx; |
25 | | uint32_t initialized; |
26 | | } zend_cpu_info; |
27 | | |
28 | | static zend_cpu_info cpuinfo = {0}; |
29 | | |
30 | | #if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) |
31 | | # if defined(HAVE_CPUID_H) && defined(HAVE_CPUID_COUNT) /* use cpuid.h functions */ |
32 | | # include <cpuid.h> |
33 | 6 | static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) { |
34 | 6 | __cpuid_count(func, subfunc, cpuinfo->eax, cpuinfo->ebx, cpuinfo->ecx, cpuinfo->edx); |
35 | 6 | } |
36 | | # else /* use inline asm */ |
37 | | static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) { |
38 | | # if defined(__i386__) && (defined(__pic__) || defined(__PIC__)) |
39 | | /* PIC on i386 uses %ebx, so preserve it. */ |
40 | | __asm__ __volatile__ ( |
41 | | "pushl %%ebx\n" |
42 | | "cpuid\n" |
43 | | "mov %%ebx,%1\n" |
44 | | "popl %%ebx" |
45 | | : "=a"(cpuinfo->eax), "=r"(cpuinfo->ebx), "=c"(cpuinfo->ecx), "=d"(cpuinfo->edx) |
46 | | : "a"(func), "c"(subfunc) |
47 | | ); |
48 | | # else |
49 | | __asm__ __volatile__ ( |
50 | | "cpuid" |
51 | | : "=a"(cpuinfo->eax), "=b"(cpuinfo->ebx), "=c"(cpuinfo->ecx), "=d"(cpuinfo->edx) |
52 | | : "a"(func), "c"(subfunc) |
53 | | ); |
54 | | # endif |
55 | | } |
56 | | # endif |
57 | | #elif defined(_MSC_VER) && !defined(__clang__) && (defined(_M_X64) || defined(_M_IX86)) /* use MSVC __cpuidex intrin */ |
58 | | # include <intrin.h> |
59 | | static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) { |
60 | | int regs[4]; |
61 | | |
62 | | __cpuidex(regs, func, subfunc); |
63 | | |
64 | | cpuinfo->eax = regs[0]; |
65 | | cpuinfo->ebx = regs[1]; |
66 | | cpuinfo->ecx = regs[2]; |
67 | | cpuinfo->edx = regs[3]; |
68 | | } |
69 | | #else /* fall back to zero */ |
70 | | static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) { |
71 | | cpuinfo->eax = 0; |
72 | | } |
73 | | #endif |
74 | | |
75 | | #if defined(__i386__) || defined(__x86_64__) || defined(_M_X64) || defined(_M_IX86) |
76 | | /* Function based on compiler-rt implementation. */ |
77 | 2 | static unsigned get_xcr0_eax(void) { |
78 | 2 | # if defined(__GNUC__) || defined(__clang__) |
79 | | // Check xgetbv; this uses a .byte sequence instead of the instruction |
80 | | // directly because older assemblers do not include support for xgetbv and |
81 | | // there is no easy way to conditionally compile based on the assembler used. |
82 | 2 | unsigned eax, edx; |
83 | 2 | __asm__(".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(0)); |
84 | 2 | return eax; |
85 | | # elif defined(ZEND_WIN32) && defined(_XCR_XFEATURE_ENABLED_MASK) |
86 | | return _xgetbv(_XCR_XFEATURE_ENABLED_MASK); |
87 | | # else |
88 | | return 0; |
89 | | # endif |
90 | 2 | } |
91 | | |
92 | 2 | static bool is_avx_supported(void) { |
93 | 2 | if (!(cpuinfo.ecx & ZEND_CPU_FEATURE_AVX)) { |
94 | | /* No support for AVX */ |
95 | 0 | return false; |
96 | 0 | } |
97 | 2 | if (!(cpuinfo.ecx & ZEND_CPU_FEATURE_OSXSAVE)) { |
98 | | /* The operating system does not support XSAVE. */ |
99 | 0 | return false; |
100 | 0 | } |
101 | 2 | if ((get_xcr0_eax() & 0x6) != 0x6) { |
102 | | /* XCR0 SSE and AVX bits must be set. */ |
103 | 0 | return false; |
104 | 0 | } |
105 | 2 | return true; |
106 | 2 | } |
107 | | #else |
108 | | static bool is_avx_supported(void) { |
109 | | return false; |
110 | | } |
111 | | #endif |
112 | | |
113 | | void zend_cpu_startup(void) |
114 | 2 | { |
115 | 2 | if (!cpuinfo.initialized) { |
116 | 2 | zend_cpu_info ebx; |
117 | 2 | int max_feature; |
118 | | |
119 | 2 | cpuinfo.initialized = 1; |
120 | 2 | __zend_cpuid(0, 0, &cpuinfo); |
121 | 2 | max_feature = cpuinfo.eax; |
122 | 2 | if (max_feature == 0) { |
123 | 0 | return; |
124 | 0 | } |
125 | | |
126 | 2 | __zend_cpuid(1, 0, &cpuinfo); |
127 | | |
128 | | /* for avx2 */ |
129 | 2 | if (max_feature >= 7) { |
130 | 2 | __zend_cpuid(7, 0, &ebx); |
131 | 2 | cpuinfo.ebx = ebx.ebx; |
132 | 2 | } else { |
133 | 0 | cpuinfo.ebx = 0; |
134 | 0 | } |
135 | | |
136 | 2 | if (!is_avx_supported()) { |
137 | 0 | cpuinfo.edx &= ~ZEND_CPU_FEATURE_AVX; |
138 | 0 | cpuinfo.ebx &= ~(ZEND_CPU_FEATURE_AVX2 & ~ZEND_CPU_EBX_MASK); |
139 | 0 | } |
140 | 2 | } |
141 | 2 | } |
142 | | |
143 | 0 | ZEND_API int zend_cpu_supports(zend_cpu_feature feature) { |
144 | 0 | ZEND_ASSERT(cpuinfo.initialized); |
145 | 0 | if (feature & ZEND_CPU_EDX_MASK) { |
146 | 0 | return (cpuinfo.edx & (feature & ~ZEND_CPU_EDX_MASK)); |
147 | 0 | } else if (feature & ZEND_CPU_EBX_MASK) { |
148 | 0 | return (cpuinfo.ebx & (feature & ~ZEND_CPU_EBX_MASK)); |
149 | 0 | } else { |
150 | 0 | return (cpuinfo.ecx & feature); |
151 | 0 | } |
152 | 0 | } |