core/cpuid.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Avisynth v1.0 beta. Copyright 2000 Ben Rudiak-Gould. | ||
| 2 | // http://www.math.berkeley.edu/~benrg/avisynth.html | ||
| 3 | |||
| 4 | // VirtualDub - Video processing and capture application | ||
| 5 | // Copyright (C) 1998-2000 Avery Lee | ||
| 6 | // | ||
| 7 | // This program is free software; you can redistribute it and/or modify | ||
| 8 | // it under the terms of the GNU General Public License as published by | ||
| 9 | // the Free Software Foundation; either version 2 of the License, or | ||
| 10 | // (at your option) any later version. | ||
| 11 | // | ||
| 12 | // This program is distributed in the hope that it will be useful, | ||
| 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | // GNU General Public License for more details. | ||
| 16 | // | ||
| 17 | // You should have received a copy of the GNU General Public License | ||
| 18 | // along with this program; if not, write to the Free Software | ||
| 19 | // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 20 | |||
| 21 | #include <avs/cpuid.h> | ||
| 22 | #include <avs/config.h> | ||
| 23 | #include <cstdint> | ||
| 24 | #include <cstddef> | ||
| 25 | |||
| 26 | // needed for linux and bsd l2cache size query | ||
| 27 | #if defined(AVS_LINUX) || defined(AVS_BSD) | ||
| 28 | #include <cstring> | ||
| 29 | #include <cstdio> | ||
| 30 | #include <cstdlib> | ||
| 31 | #endif | ||
| 32 | |||
| 33 | // Neon/Dotprod request in WinAPI ... | ||
| 34 | #if defined(ARM64) && defined(AVS_WINDOWS) | ||
| 35 | #include <windows.h> | ||
| 36 | #include <processthreadsapi.h> | ||
| 37 | #endif | ||
| 38 | |||
| 39 | // --- Platform-specific headers for x86/x64 --- | ||
| 40 | #if defined(X86_32) || defined(X86_64) | ||
| 41 | |||
| 42 | #ifdef AVS_WINDOWS | ||
| 43 | #include <intrin.h> // MSVC/Clang-CL | ||
| 44 | #else | ||
| 45 | // Non-Windows (GCC, Clang-GNU, etc.) | ||
| 46 | #include <x86intrin.h> | ||
| 47 | #include <cpuid.h> | ||
| 48 | #undef __cpuid | ||
| 49 | |||
| 50 | static inline void __cpuid(int cpuinfo[4], int leaf) { | ||
| 51 | unsigned int eax, ebx, ecx, edx; | ||
| 52 | // for deeper leaves __get_cpuid is not enough | ||
| 53 | __get_cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx); | ||
| 54 | cpuinfo[0] = eax; | ||
| 55 | cpuinfo[1] = ebx; | ||
| 56 | cpuinfo[2] = ecx; | ||
| 57 | cpuinfo[3] = edx; | ||
| 58 | } | ||
| 59 | #endif // AVS_WINDOWS | ||
| 60 | |||
| 61 | #endif // defined(X86_32) || defined(X86_64) | ||
| 62 | |||
| 63 | // --- Platform-specific headers for ARM64 --- | ||
| 64 | #if defined(ARM64) | ||
| 65 | #if defined(AVS_LINUX) || defined(AVS_BSD) | ||
| 66 | // HWCAP values are needed for Linux/BSD | ||
| 67 | #include <sys/auxv.h> | ||
| 68 | // Note: <asm/hwcap.h> may be required on some systems, | ||
| 69 | // but AT_HWCAP and values like HWCAP_DOTPROD are often found in sys/auxv.h or defined by toolchain. | ||
| 70 | // We assume standard GNU/Clang behavior where flags like HWCAP_DOTPROD are available. | ||
| 71 | #include <asm/hwcap.h> | ||
| 72 | #elif defined(AVS_MACOS) | ||
| 73 | // macOS/Apple Silicon uses sysctl for features | ||
| 74 | #include <sys/types.h> | ||
| 75 | #include <sys/sysctl.h> | ||
| 76 | #endif | ||
| 77 | #endif | ||
| 78 | |||
| 79 | #define IS_BIT_SET(bitfield, bit) ((bitfield) & (1<<(bit)) ? true : false) | ||
| 80 | |||
| 81 | #if defined(X86_32) || defined(X86_64) | ||
| 82 | 452 | static uint32_t get_xcr0() | |
| 83 | { | ||
| 84 | uint32_t xcr0; | ||
| 85 | // _XCR_XFEATURE_ENABLED_MASK: 0 | ||
| 86 | #if defined(GCC) || defined(CLANG) | ||
| 87 | 452 | __asm__("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx"); | |
| 88 | #else | ||
| 89 | xcr0 = (uint32_t)_xgetbv(0); | ||
| 90 | #endif | ||
| 91 | 452 | return xcr0; | |
| 92 | } | ||
| 93 | |||
| 94 | // --- Helper for __cpuid_count/ex (required for Leaf 4 sub-leaves) --- | ||
| 95 | static void __cpuid_count_wrapper(int info[4], int leaf, int subleaf) { | ||
| 96 | #ifdef AVS_WINDOWS | ||
| 97 | // MSVC uses __cpuidex | ||
| 98 | __cpuidex(info, leaf, subleaf); | ||
| 99 | #elif defined(GCC) || defined(CLANG) | ||
| 100 | // GCC/Clang uses __cpuid_count | ||
| 101 | __cpuid_count(leaf, subleaf, info[0], info[1], info[2], info[3]); | ||
| 102 | #else | ||
| 103 | // Fallback or error if no intrinsic is available | ||
| 104 | info[0] = info[1] = info[2] = info[3] = 0; | ||
| 105 | #endif | ||
| 106 | } | ||
| 107 | |||
| 108 | // Helper function to determine the AVX10 version (e.g., 10.2) | ||
| 109 | // Returns the minor version (0 for none, 1 for 10.1, 2 for 10.2, etc.) | ||
| 110 | ✗ | static int get_avx10_minor_version() { | |
| 111 | int info[4]; | ||
| 112 | int max_sub_leaf_7; | ||
| 113 | |||
| 114 | // 1. Check max CPUID Leaf 7 sub-leaf to ensure Sub-leaf 1 is available. | ||
| 115 | // Call Leaf 7, Sub-leaf 0. Max Sub-leaf is returned in EAX. | ||
| 116 | ✗ | __cpuid_count_wrapper(info, 7, 0); | |
| 117 | ✗ | max_sub_leaf_7 = info[0]; // EAX holds the Max Sub-leaf | |
| 118 | |||
| 119 | // If max sub-leaf is less than 1, we cannot query the AVX10 version. | ||
| 120 | ✗ | if (max_sub_leaf_7 < 1) { | |
| 121 | ✗ | return 0; // AVX10 version not supported or enumerable | |
| 122 | } | ||
| 123 | |||
| 124 | // 2. Query CPUID Leaf 7, Sub-leaf 1 for the AVX10 Version (Major/Minor) | ||
| 125 | ✗ | __cpuid_count_wrapper(info, 7, 1); | |
| 126 | |||
| 127 | // EAX[31:24] = Major Version | ||
| 128 | ✗ | int major_version = (info[0] >> 24) & 0xFF; | |
| 129 | |||
| 130 | // EAX[23:16] = Minor Version | ||
| 131 | ✗ | int minor_version = (info[0] >> 16) & 0xFF; | |
| 132 | |||
| 133 | // AVX10 has a major version of 10. | ||
| 134 | // This check confirms the version returned is for AVX10. | ||
| 135 | ✗ | if (major_version == 10) { | |
| 136 | ✗ | return minor_version; | |
| 137 | } | ||
| 138 | |||
| 139 | ✗ | return 0; // Not an AVX10 major version | |
| 140 | } | ||
| 141 | #endif // defined(X86_32) || defined(X86_64) | ||
| 142 | |||
| 143 | // ------------------------------------------------------------------ | ||
| 144 | // Core aarch64 (ARMv8/v9) Feature Detection Function | ||
| 145 | // ------------------------------------------------------------------ | ||
| 146 | #if defined(ARM64) | ||
| 147 | static int64_t ARMCheckForExtensions() | ||
| 148 | { | ||
| 149 | int64_t result = 0; | ||
| 150 | |||
| 151 | // Tier 1: CPUF_ARM_NEON (Mandatory for AArch64) | ||
| 152 | // We can assume NEON for any successful ARM64 build. | ||
| 153 | result |= CPUF_ARM_NEON; | ||
| 154 | |||
| 155 | #if defined(AVS_LINUX) || defined(AVS_BSD) | ||
| 156 | |||
| 157 | // Linux/BSD HWCAP detection (uses AT_HWCAP/AT_HWCAP2) | ||
| 158 | // aarch64 implies -march=armv8-a | ||
| 159 | // HWCAP_NEON (Basic NEON) is covered by the assumption above. | ||
| 160 | unsigned long hwcap = getauxval(AT_HWCAP); | ||
| 161 | unsigned long hwcap2 = getauxval(AT_HWCAP2); | ||
| 162 | |||
| 163 | // When DOTPROD exists, we have at least Armv8.1-a | ||
| 164 | // optional in v8.1-a, mandatory in v8.4-a | ||
| 165 | // Safe gcc/clang flags: -march=armv8.1-a+dotprod | ||
| 166 | if ((hwcap & HWCAP_ASIMDDP)) { | ||
| 167 | result |= CPUF_ARM_DOTPROD; | ||
| 168 | } | ||
| 169 | |||
| 170 | // SVE2 (Scalable Vector Extension version 2) optional in v8.5-a, mandatory in v9.0-a | ||
| 171 | // Safe flags: -march=armv8.5-a+sve2 | ||
| 172 | if (hwcap2 & HWCAP2_SVE2) { | ||
| 173 | result |= CPUF_ARM_SVE2; | ||
| 174 | } | ||
| 175 | |||
| 176 | |||
| 177 | // CPUF_ARM_SVE2_1 (incremental SVE2 part 1) | ||
| 178 | // optional in v9.1-a, mandatory in v9.2-a | ||
| 179 | #ifdef HWCAP2_SVE2P1 | ||
| 180 | #ifdef CPUF_ARM_SVE2_1 | ||
| 181 | if (hwcap2 & HWCAP2_SVE2P1) { | ||
| 182 | result |= CPUF_ARM_SVE2_1; | ||
| 183 | } | ||
| 184 | #endif | ||
| 185 | #endif | ||
| 186 | |||
| 187 | // CPUF_ARM_I8MM (AdvSIMD Int8 matrix multiply, Armv8.2-I8MM) | ||
| 188 | // optional in v8.2-a, mandatory in v8.6-a | ||
| 189 | // Safe flags: -march=armv8.2-a+i8mm | ||
| 190 | if (hwcap2 & HWCAP2_I8MM) { | ||
| 191 | result |= CPUF_ARM_I8MM; | ||
| 192 | } | ||
| 193 | |||
| 194 | |||
| 195 | #elif defined(AVS_MACOS) | ||
| 196 | |||
| 197 | // macOS (Apple Silicon) detection via sysctlbyname. | ||
| 198 | // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics | ||
| 199 | int value = 0; | ||
| 200 | size_t len = sizeof(value); | ||
| 201 | |||
| 202 | // Check for the DOTPROD feature, which is guaranteed on modern Apple Silicon. | ||
| 203 | // All Apple Silicon devices (M1, M2, M3, etc.) are based on ARMv8.6-a or later. | ||
| 204 | // The sysctl function returns 0 on success and populates 'value'. | ||
| 205 | if (sysctlbyname("hw.optional.arm.FEAT_DotProd", &value, &len, NULL, 0) == 0 && value) { | ||
| 206 | result |= CPUF_ARM_DOTPROD; | ||
| 207 | } | ||
| 208 | if (sysctlbyname("hw.optional.arm.FEAT_I8MM", &value, &len, NULL, 0) == 0 && value) { | ||
| 209 | result |= CPUF_ARM_I8MM; | ||
| 210 | } | ||
| 211 | |||
| 212 | // SVE2 is mandatory for general ARMv9.0-a compliance, Apple has chosen not to | ||
| 213 | // implement SVE or SVE2 in its M-series CPUs to date (2025). | ||
| 214 | // No check required here as the hardware does not support it. | ||
| 215 | |||
| 216 | #elif defined(AVS_WINDOWS) | ||
| 217 | |||
| 218 | // Windows ARM64 detection using IsProcessorFeaturePresent | ||
| 219 | // Windows ARM SVE/SVE2 features via WinAPI flags are available since SDK 26100 (Windows 11 24H2). | ||
| 220 | |||
| 221 | // CPUF_ARM_DOTPROD (Dot Product) | ||
| 222 | #if defined(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) | ||
| 223 | if (IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE)) { | ||
| 224 | result |= CPUF_ARM_DOTPROD; | ||
| 225 | } | ||
| 226 | #endif | ||
| 227 | |||
| 228 | #if defined(PF_ARM_V8_DOTPROD_INSTRUCTIONS_AVAILABLE) | ||
| 229 | if (IsProcessorFeaturePresent(PF_ARM_V8_DOTPROD_INSTRUCTIONS_AVAILABLE)) { | ||
| 230 | result |= CPUF_ARM_DOTPROD; | ||
| 231 | } | ||
| 232 | #endif | ||
| 233 | |||
| 234 | #if defined(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) | ||
| 235 | if (IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)) { | ||
| 236 | result |= CPUF_ARM_SVE2; | ||
| 237 | } | ||
| 238 | #endif | ||
| 239 | |||
| 240 | #if defined(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE) | ||
| 241 | if (IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE)) { | ||
| 242 | result |= CPUF_ARM_I8MM; | ||
| 243 | } | ||
| 244 | #endif | ||
| 245 | |||
| 246 | #if defined(PF_ARM_SVE2_1_INSTRUCTIONS_AVAILABLE) | ||
| 247 | if (IsProcessorFeaturePresent(PF_ARM_SVE2_1_INSTRUCTIONS_AVAILABLE)) { | ||
| 248 | result |= CPUF_ARM_SVE2_1; | ||
| 249 | } | ||
| 250 | #endif | ||
| 251 | |||
| 252 | #endif // Platform-specific ARM64 feature detection | ||
| 253 | |||
| 254 | return result; | ||
| 255 | } | ||
| 256 | #endif // defined(ARM64) | ||
| 257 | |||
| 258 | |||
| 259 | // ------------------------------------------------------------------ | ||
| 260 | // Core x86/x64 Feature Detection Function | ||
| 261 | // (Refactored for cleaner architecture switch) | ||
| 262 | // ------------------------------------------------------------------ | ||
| 263 | #if defined(X86_32) || defined(X86_64) | ||
| 264 | 452 | static int64_t X86CheckForExtensions() | |
| 265 | { | ||
| 266 | 452 | int64_t result = 0; | |
| 267 | int cpuinfo[4]; | ||
| 268 | |||
| 269 | #if defined(X86_32) || defined(X86_64) | ||
| 270 | // Check CPUID Leaf 1 | ||
| 271 | 452 | __cpuid(cpuinfo, 1); | |
| 272 |
1/2✓ Branch 3 → 4 taken 452 times.
✗ Branch 3 → 5 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[3], 0)) |
| 273 | 452 | result |= CPUF_FPU; | |
| 274 |
1/2✓ Branch 5 → 6 taken 452 times.
✗ Branch 5 → 7 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[3], 23)) |
| 275 | 452 | result |= CPUF_MMX; | |
| 276 |
1/2✓ Branch 7 → 8 taken 452 times.
✗ Branch 7 → 9 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[3], 25)) |
| 277 | 452 | result |= CPUF_SSE | CPUF_INTEGER_SSE; | |
| 278 |
1/2✓ Branch 9 → 10 taken 452 times.
✗ Branch 9 → 11 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[3], 26)) |
| 279 | 452 | result |= CPUF_SSE2; | |
| 280 |
1/2✓ Branch 11 → 12 taken 452 times.
✗ Branch 11 → 13 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 0)) |
| 281 | 452 | result |= CPUF_SSE3; | |
| 282 |
1/2✓ Branch 13 → 14 taken 452 times.
✗ Branch 13 → 15 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 9)) |
| 283 | 452 | result |= CPUF_SSSE3; | |
| 284 |
1/2✓ Branch 15 → 16 taken 452 times.
✗ Branch 15 → 17 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 19)) |
| 285 | 452 | result |= CPUF_SSE4_1; | |
| 286 |
1/2✓ Branch 17 → 18 taken 452 times.
✗ Branch 17 → 19 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 20)) |
| 287 | 452 | result |= CPUF_SSE4_2; | |
| 288 |
1/2✓ Branch 19 → 20 taken 452 times.
✗ Branch 19 → 21 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 22)) |
| 289 | 452 | result |= CPUF_MOVBE; | |
| 290 |
1/2✓ Branch 21 → 22 taken 452 times.
✗ Branch 21 → 23 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 23)) |
| 291 | 452 | result |= CPUF_POPCNT; | |
| 292 |
1/2✓ Branch 23 → 24 taken 452 times.
✗ Branch 23 → 25 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 25)) |
| 293 | 452 | result |= CPUF_AES; | |
| 294 |
1/2✓ Branch 25 → 26 taken 452 times.
✗ Branch 25 → 27 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 29)) |
| 295 | 452 | result |= CPUF_F16C; | |
| 296 | |||
| 297 | // AVX and XCR0 Check (Needed for AVX2 and AVX-512) | ||
| 298 | 452 | bool xgetbv_supported = IS_BIT_SET(cpuinfo[2], 27); | |
| 299 | 452 | bool avx_supported = IS_BIT_SET(cpuinfo[2], 28); | |
| 300 |
2/4✓ Branch 27 → 28 taken 452 times.
✗ Branch 27 → 83 not taken.
✓ Branch 28 → 29 taken 452 times.
✗ Branch 28 → 83 not taken.
|
452 | if (xgetbv_supported && avx_supported) |
| 301 | { | ||
| 302 | 452 | uint32_t xgetbv0_32 = get_xcr0(); | |
| 303 | |||
| 304 | // Check OS support for AVX (XMM and YMM state) | ||
| 305 |
1/2✓ Branch 30 → 31 taken 452 times.
✗ Branch 30 → 36 not taken.
|
452 | if ((xgetbv0_32 & 0x6u) == 0x6u) { |
| 306 | 452 | result |= CPUF_AVX; | |
| 307 |
1/2✓ Branch 31 → 32 taken 452 times.
✗ Branch 31 → 33 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[2], 12)) result |= CPUF_FMA3; |
| 308 | |||
| 309 | 452 | __cpuid_count_wrapper(cpuinfo, 7, 0); | |
| 310 |
1/2✓ Branch 34 → 35 taken 452 times.
✗ Branch 34 → 36 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[1], 5)) result |= CPUF_AVX2; |
| 311 | } | ||
| 312 | |||
| 313 | // Check OS support for AVX-512 (OPMASK, ZMM0-ZMM15, ZMM16-ZMM31 states) | ||
| 314 |
1/4✗ Branch 36 → 37 not taken.
✓ Branch 36 → 83 taken 452 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 83 not taken.
|
452 | if ((xgetbv0_32 & (0x7u << 5)) && (xgetbv0_32 & (0x3u << 1))) |
| 315 | { | ||
| 316 | // Leaf 7, Sub-leaf 0 results are already in cpuinfo (from AVX2 check) | ||
| 317 | |||
| 318 | // --- EBX: Core Base Features & Specialized Features --- | ||
| 319 | ✗ | if (IS_BIT_SET(cpuinfo[1], 16)) result |= CPUF_AVX512F; | |
| 320 | ✗ | if (IS_BIT_SET(cpuinfo[1], 17)) result |= CPUF_AVX512DQ; | |
| 321 | ✗ | if (IS_BIT_SET(cpuinfo[1], 21)) result |= CPUF_AVX512IFMA; | |
| 322 | ✗ | if (IS_BIT_SET(cpuinfo[1], 26)) result |= CPUF_AVX512PF; | |
| 323 | ✗ | if (IS_BIT_SET(cpuinfo[1], 27)) result |= CPUF_AVX512ER; | |
| 324 | ✗ | if (IS_BIT_SET(cpuinfo[1], 28)) result |= CPUF_AVX512CD; | |
| 325 | ✗ | if (IS_BIT_SET(cpuinfo[1], 30)) result |= CPUF_AVX512BW; | |
| 326 | ✗ | if (IS_BIT_SET(cpuinfo[1], 31)) result |= CPUF_AVX512VL; | |
| 327 | |||
| 328 | // --- ECX: ICL/RKL Features (VBMI, VNNI, Cryptography) --- | ||
| 329 | ✗ | if (IS_BIT_SET(cpuinfo[2], 1)) result |= CPUF_AVX512VBMI; | |
| 330 | ✗ | if (IS_BIT_SET(cpuinfo[2], 6)) result |= CPUF_AVX512VBMI2; | |
| 331 | |||
| 332 | /* Deprecated: | ||
| 333 | * AVX-512 Vector Pair to Two Intersect (VP2INTERSECT) instruction set | ||
| 334 | * has been found to be slower than alternative implementations using | ||
| 335 | * existing instructions. Newer CPUs may not implement this feature. | ||
| 336 | if (IS_BIT_SET(cpuinfo[2], 2)) result |= CPUF_AVX512VP2INTERSECT; | ||
| 337 | */ | ||
| 338 | |||
| 339 | ✗ | const bool has_avx512_crypto = | |
| 340 | ✗ | IS_BIT_SET(cpuinfo[2], 7) || | |
| 341 | ✗ | IS_BIT_SET(cpuinfo[2], 8) || | |
| 342 | ✗ | IS_BIT_SET(cpuinfo[2], 9); | |
| 343 | /* Avisynth don't use crypto features. | ||
| 344 | if (IS_BIT_SET(cpuinfo[2], 8)) result |= CPUF_AVX512GFNI; | ||
| 345 | if (IS_BIT_SET(cpuinfo[2], 9)) result |= CPUF_AVX512VAES; | ||
| 346 | if (IS_BIT_SET(cpuinfo[2], 10)) result |= CPUF_AVX512VPCLMULQDQ; | ||
| 347 | */ | ||
| 348 | ✗ | if (IS_BIT_SET(cpuinfo[2], 11)) result |= CPUF_AVX512VNNI; | |
| 349 | ✗ | if (IS_BIT_SET(cpuinfo[2], 12)) result |= CPUF_AVX512BITALG; | |
| 350 | ✗ | if (IS_BIT_SET(cpuinfo[2], 14)) result |= CPUF_AVX512VPOPCNTDQ; | |
| 351 | |||
| 352 | // --- AVX-512 (Leaf 7, Sub-leaf 1) --- | ||
| 353 | ✗ | __cpuid_count_wrapper(cpuinfo, 7, 1); | |
| 354 | |||
| 355 | // EAX: | ||
| 356 | |||
| 357 | /* Deprecated: | ||
| 358 | * AVX-512 4-way VNNI with Word Granularity (4VNNIW) and | ||
| 359 | * AVX-512 4-way Fused Multiply-Add Single precision (4FMAPS) | ||
| 360 | * have been deprecated and replaced by more versatile instructions | ||
| 361 | * in AVX10. Newer CPUs may not implement these features. | ||
| 362 | if (IS_BIT_SET(cpuinfo[0], 4)) result |= CPUF_AVX5124VNNIW; | ||
| 363 | if (IS_BIT_SET(cpuinfo[0], 5)) result |= CPUF_AVX5124FMAPS; | ||
| 364 | */ | ||
| 365 | |||
| 366 | // EDX: | ||
| 367 | ✗ | if (IS_BIT_SET(cpuinfo[3], 16)) result |= CPUF_AVX512FP16; | |
| 368 | ✗ | if (IS_BIT_SET(cpuinfo[3], 17)) result |= CPUF_AVX512BF16; | |
| 369 | |||
| 370 | ✗ | int avx10_minor = get_avx10_minor_version(); // 0 if no AVX10 | |
| 371 | |||
| 372 | // --- Composite Feature Flags and AVX10 --- | ||
| 373 | |||
| 374 | ✗ | constexpr int64_t avx512_base_mask = | |
| 375 | CPUF_AVX512F | | ||
| 376 | CPUF_AVX512CD | | ||
| 377 | CPUF_AVX512BW | | ||
| 378 | CPUF_AVX512DQ | | ||
| 379 | CPUF_AVX512VL; | ||
| 380 | |||
| 381 | ✗ | constexpr int64_t avx512_fast_core_mask = | |
| 382 | CPUF_AVX512VBMI | | ||
| 383 | CPUF_AVX512VNNI | | ||
| 384 | CPUF_AVX512VBMI2 | | ||
| 385 | CPUF_AVX512BITALG | | ||
| 386 | CPUF_AVX512VPOPCNTDQ; | ||
| 387 | |||
| 388 | // 1. Check for Base AVX-512 | ||
| 389 | ✗ | if ((result & avx512_base_mask) == avx512_base_mask) { | |
| 390 | |||
| 391 | // Check for AVX512_FAST (Pre-AVX10 minimum Ice Lake) | ||
| 392 | // Base + Core ICL + Crypto (to distinguish from older server CPUs) | ||
| 393 | ✗ | const bool has_avx512_fast = | |
| 394 | ✗ | ((result & avx512_fast_core_mask) == avx512_fast_core_mask) && has_avx512_crypto; | |
| 395 | |||
| 396 | ✗ | const bool has_avx10 = (avx10_minor >= 1); | |
| 397 | |||
| 398 | // The "Base" group feature flag is set only if FAST is present! | ||
| 399 | // Since only-Base means a very old throttling CPU, we disable AVX512 group flag by default. | ||
| 400 | // User can later re-enable it via SetMaxCPU("AVX512base+") | ||
| 401 | ✗ | if (has_avx512_fast || has_avx10) { | |
| 402 | ✗ | result |= CPUF_AVX512_BASE; // fulfilling base mask is not enough | |
| 403 | ✗ | result |= CPUF_AVX512_FAST; | |
| 404 | } | ||
| 405 | |||
| 406 | // Check for AVX10, no AVS flags atm. | ||
| 407 | if (has_avx10) { | ||
| 408 | //result |= CPUF_AVX10; // not yet, RFU | ||
| 409 | } | ||
| 410 | } | ||
| 411 | |||
| 412 | // GCC/clang compiler flags for matching CPUF_AVX512_BASE | ||
| 413 | // " -mfma -mbmi2 -mavx512f -mavx512cd -mavx512bw -mavx512dq -mavx512vl " | ||
| 414 | // for matching CPUF_AVX512_FAST: | ||
| 415 | //" -mfma -mbmi2 -mavx512f -mavx512cd -mavx512bw -mavx512dq -mavx512vl -mavx512vnni -mavx512vbmi -mavx512vbmi2 -mavx512bitalg -mavx512vpopcntdq " | ||
| 416 | } | ||
| 417 | } | ||
| 418 | #else | ||
| 419 | result |= CPUF_FORCE; | ||
| 420 | |||
| 421 | return result; | ||
| 422 | #endif | ||
| 423 | |||
| 424 | #if defined(X86_32) || defined(X86_64) | ||
| 425 | // Check CPUID Extended Leaf 0x80000001 (for 3DNow! and FMA4) | ||
| 426 | 452 | __cpuid(cpuinfo, 0x80000000); | |
| 427 |
1/2✓ Branch 84 → 85 taken 452 times.
✗ Branch 84 → 95 not taken.
|
452 | if (cpuinfo[0] >= 0x80000001) |
| 428 | { | ||
| 429 | 452 | __cpuid(cpuinfo, 0x80000001); | |
| 430 | |||
| 431 |
1/2✗ Branch 86 → 87 not taken.
✓ Branch 86 → 88 taken 452 times.
|
452 | if (IS_BIT_SET(cpuinfo[3], 31)) |
| 432 | ✗ | result |= CPUF_3DNOW; | |
| 433 | |||
| 434 |
1/2✗ Branch 88 → 89 not taken.
✓ Branch 88 → 90 taken 452 times.
|
452 | if (IS_BIT_SET(cpuinfo[3], 30)) |
| 435 | ✗ | result |= CPUF_3DNOW_EXT; | |
| 436 | |||
| 437 |
1/2✓ Branch 90 → 91 taken 452 times.
✗ Branch 90 → 92 not taken.
|
452 | if (IS_BIT_SET(cpuinfo[3], 22)) |
| 438 | 452 | result |= CPUF_INTEGER_SSE; | |
| 439 | |||
| 440 |
1/2✓ Branch 92 → 93 taken 452 times.
✗ Branch 92 → 95 not taken.
|
452 | if (result & CPUF_AVX) { |
| 441 |
1/2✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 452 times.
|
452 | if (IS_BIT_SET(cpuinfo[2], 16)) |
| 442 | ✗ | result |= CPUF_FMA4; | |
| 443 | } | ||
| 444 | } | ||
| 445 | #endif | ||
| 446 | |||
| 447 | 452 | return result; | |
| 448 | } | ||
| 449 | #endif // defined(X86_32) || defined(X86_64) | ||
| 450 | |||
| 451 | // ------------------------------------------------------------------ | ||
| 452 | // Master Feature Detection Function (Dispatcher) | ||
| 453 | // ------------------------------------------------------------------ | ||
| 454 | 452 | static int64_t CPUCheckForExtensions() | |
| 455 | { | ||
| 456 | 452 | int64_t result = 0; | |
| 457 | |||
| 458 | #if defined(X86_32) || defined(X86_64) | ||
| 459 | 452 | result |= X86CheckForExtensions(); | |
| 460 | #elif defined(ARM64) | ||
| 461 | result |= ARMCheckForExtensions(); | ||
| 462 | #else | ||
| 463 | // Fallback for architectures without specific detection implemented | ||
| 464 | #endif | ||
| 465 | |||
| 466 | 452 | return result; | |
| 467 | } | ||
| 468 | |||
| 469 | #if defined(ARM64) && (defined(AVS_LINUX) || defined(AVS_BSD)) | ||
| 470 | static size_t parse_sysfs_size_string(const char* size_str) { | ||
| 471 | if (size_str == nullptr) { | ||
| 472 | return 0; | ||
| 473 | } | ||
| 474 | |||
| 475 | char* endptr; | ||
| 476 | long value = strtol(size_str, &endptr, 10); | ||
| 477 | if (value <= 0) { | ||
| 478 | return 0; | ||
| 479 | } | ||
| 480 | |||
| 481 | if (*endptr == 'K' || *endptr == 'k') { | ||
| 482 | return (size_t)value * 1024; | ||
| 483 | } | ||
| 484 | else if (*endptr == 'M' || *endptr == 'm') { | ||
| 485 | return (size_t)value * 1024 * 1024; | ||
| 486 | } | ||
| 487 | else if (*endptr == 'G' || *endptr == 'g') { | ||
| 488 | return (size_t)value * 1024 * 1024 * 1024; | ||
| 489 | } | ||
| 490 | return (size_t)value; | ||
| 491 | } | ||
| 492 | #endif // defined(ARM64) && (defined(AVS_LINUX) || defined(AVS_BSD)) | ||
| 493 | |||
| 494 | // ------------------------------------------------------------------ | ||
| 495 | // Core L2 Cache Detection Function | ||
| 496 | // ------------------------------------------------------------------ | ||
| 497 | 452 | static size_t DetectL2CacheSize() | |
| 498 | { | ||
| 499 | #if defined(X86_32) || defined(X86_64) | ||
| 500 | int info[4]; | ||
| 501 | |||
| 502 | // ------------------------------------------------------- | ||
| 503 | // 1. PRIMARY METHOD: Deterministic Cache Parameters (Leaf 4) | ||
| 504 | // (Modern, cross-vendor, and supports topology) | ||
| 505 | // ------------------------------------------------------- | ||
| 506 | 452 | for (int i = 0; ; ++i) { | |
| 507 | // We use the helper that supports sub-leaves (i) | ||
| 508 | 452 | __cpuid_count_wrapper(info, 0x4, i); | |
| 509 | |||
| 510 | // EAX[4:0] = Cache Type: 1=Data, 2=Instruction, 3=Unified | ||
| 511 | 452 | int cache_type = info[0] & 0x1F; | |
| 512 | |||
| 513 | // EAX[7:5] = Cache Level: 1=L1, 2=L2, 3=L3, ... | ||
| 514 | 452 | int cache_level = (info[0] >> 5) & 0b111; | |
| 515 | |||
| 516 | // Check for end of list (type 0) | ||
| 517 |
1/2✓ Branch 4 → 5 taken 452 times.
✗ Branch 4 → 6 not taken.
|
452 | if (cache_type == 0) { |
| 518 | 452 | break; | |
| 519 | } | ||
| 520 | |||
| 521 | // We look for Cache Level 2, regardless of whether it's reported as Unified (3) or Instruction (2). | ||
| 522 | ✗ | if (cache_level == 2) { | |
| 523 | // Cache Size (Bytes) = (Ways + 1) * (Partitions + 1) * (Line Size + 1) * (Sets + 1) | ||
| 524 | |||
| 525 | ✗ | size_t line_size = (info[1] & 0xFFF) + 1; // EBX[11:0] | |
| 526 | ✗ | size_t partitions = ((info[1] >> 12) & 0x3FF) + 1; // EBX[21:12] | |
| 527 | ✗ | size_t ways = ((info[1] >> 22) & 0x3FF) + 1; // EBX[31:22] | |
| 528 | ✗ | size_t sets = (size_t)info[2] + 1; // ECX[31:0] | |
| 529 | |||
| 530 | ✗ | return ways * partitions * line_size * sets; | |
| 531 | } | ||
| 532 | ✗ | } | |
| 533 | |||
| 534 | // ------------------------------------------------------- | ||
| 535 | // 2. FALLBACK METHOD: AMD Extended Cache (Leaf 80000006) | ||
| 536 | // (Legacy method, but reliable for older AMD CPUs) | ||
| 537 | // ------------------------------------------------------- | ||
| 538 | 452 | __cpuid(info, 0x80000000); | |
| 539 | // Check if the CPU supports extended leaf 80000006 | ||
| 540 |
1/2✓ Branch 10 → 11 taken 452 times.
✗ Branch 10 → 13 not taken.
|
452 | if (info[0] >= 0x80000006) { |
| 541 | 452 | __cpuid(info, 0x80000006); | |
| 542 | // ECX[31:16] is L2 cache size in KB. Convert to bytes. | ||
| 543 | 452 | return (size_t)(info[2] >> 16) * 1024; | |
| 544 | } | ||
| 545 | |||
| 546 | // 3. If neither method worked, return 0. | ||
| 547 | ✗ | return 0; | |
| 548 | #elif defined(ARM64) | ||
| 549 | // Cache detection on ARM is highly vendor/OS-specific. | ||
| 550 | // --- Linux/BSD Implementation (e.g., Raspberry Pi 5) --- | ||
| 551 | #if defined(AVS_LINUX) || defined(AVS_BSD) | ||
| 552 | // Detection via /sys filesystem (standard on Linux/BSD for cache info). | ||
| 553 | // The path targets the L2 cache size for cpu0 (per core L2 on RPi5/Cortex-A76). | ||
| 554 | // e.g. cat /sys/devices/system/cpu/cpu0/cache/index2/size returns 512K on RPi5 | ||
| 555 | static constexpr const char* ARM_L2_CACHE_SYSFS_PATH = "/sys/devices/system/cpu/cpu0/cache/index2/size"; | ||
| 556 | FILE* file = fopen(ARM_L2_CACHE_SYSFS_PATH, "r"); | ||
| 557 | if (file) { | ||
| 558 | char size_str[32] = { 0 }; | ||
| 559 | if (fgets(size_str, sizeof(size_str) - 1, file) != NULL) { | ||
| 560 | // Remove trailing newline character | ||
| 561 | size_t str_len = strlen(size_str); | ||
| 562 | if (str_len > 0 && size_str[str_len - 1] == '\n') { | ||
| 563 | size_str[str_len - 1] = '\0'; | ||
| 564 | } | ||
| 565 | |||
| 566 | // Parse the string (e.g., "512K") and return in bytes. | ||
| 567 | size_t l2_cache_bytes = parse_sysfs_size_string(size_str); | ||
| 568 | fclose(file); | ||
| 569 | return l2_cache_bytes; | ||
| 570 | } | ||
| 571 | fclose(file); | ||
| 572 | } | ||
| 573 | #elif defined(AVS_MACOS) | ||
| 574 | // macOS/Apple Silicon detection via sysctlbyname. | ||
| 575 | // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_system_capabilities | ||
| 576 | // The generic 'hw.l2cachesize' is for the least performant core (E-cores). | ||
| 577 | // For performance optimization, we target the L2 cache of the high-performance (P) cores. | ||
| 578 | // These are typically designated as perflevel0. | ||
| 579 | // Note: sysctl returns the cache size in bytes. | ||
| 580 | |||
| 581 | int l2_cache_bytes = 0; | ||
| 582 | size_t len = sizeof(l2_cache_bytes); | ||
| 583 | |||
| 584 | // Check for the L2 cache size of the high-performance cores (P-cores), | ||
| 585 | // which is the largest and most relevant for optimization. | ||
| 586 | if (sysctlbyname("hw.perflevel0.l2cachesize", &l2_cache_bytes, &len, NULL, 0) == 0 && l2_cache_bytes > 0) { | ||
| 587 | // Return size in bytes. | ||
| 588 | return static_cast<size_t>(l2_cache_bytes); | ||
| 589 | } | ||
| 590 | |||
| 591 | // Fallback to the generic L2 key (typically the E-core cache size). | ||
| 592 | if (sysctlbyname("hw.l2cachesize", &l2_cache_bytes, &len, NULL, 0) == 0 && l2_cache_bytes > 0) { | ||
| 593 | // Return size in bytes. | ||
| 594 | return static_cast<size_t>(l2_cache_bytes); | ||
| 595 | } | ||
| 596 | #endif | ||
| 597 | |||
| 598 | |||
| 599 | // Returning 0 is a safe default for a portable cross-platform implementation. | ||
| 600 | return 0; | ||
| 601 | #else | ||
| 602 | return 0; | ||
| 603 | #endif | ||
| 604 | } | ||
| 605 | |||
| 606 | class _CPUFlags | ||
| 607 | { | ||
| 608 | private: | ||
| 609 | size_t L2CacheSize; // in bytes | ||
| 610 | int64_t lCPUExtensionsAvailable; | ||
| 611 | 452 | _CPUFlags() { | |
| 612 | 452 | lCPUExtensionsAvailable = CPUCheckForExtensions(); | |
| 613 | 452 | L2CacheSize = DetectL2CacheSize(); | |
| 614 | 452 | } | |
| 615 | |||
| 616 | public: | ||
| 617 | 906 | static _CPUFlags& getInstance() { | |
| 618 |
3/4✓ Branch 2 → 3 taken 452 times.
✓ Branch 2 → 7 taken 454 times.
✓ Branch 4 → 5 taken 452 times.
✗ Branch 4 → 7 not taken.
|
906 | static _CPUFlags theInstance; |
| 619 | 906 | return theInstance; | |
| 620 | } | ||
| 621 | |||
| 622 | ✗ | int GetCPUFlags() { | |
| 623 | ✗ | return lCPUExtensionsAvailable & 0xFFFFFFFF; | |
| 624 | } | ||
| 625 | |||
| 626 | 453 | int64_t GetCPUFlagsEx() { | |
| 627 | 453 | return lCPUExtensionsAvailable; | |
| 628 | } | ||
| 629 | |||
| 630 | void SetCPUFlags(int64_t new_flags) { | ||
| 631 | lCPUExtensionsAvailable = new_flags; | ||
| 632 | } | ||
| 633 | |||
| 634 | 453 | size_t GetL2CacheSize() { | |
| 635 | 453 | return L2CacheSize; | |
| 636 | } | ||
| 637 | }; | ||
| 638 | |||
| 639 | ✗ | int GetCPUFlags() { | |
| 640 | ✗ | return _CPUFlags::getInstance().GetCPUFlags(); | |
| 641 | } | ||
| 642 | |||
| 643 | 453 | int64_t GetCPUFlagsEx() { | |
| 644 | 453 | return _CPUFlags::getInstance().GetCPUFlagsEx(); | |
| 645 | } | ||
| 646 | |||
| 647 | 453 | size_t GetL2CacheSize() { | |
| 648 | 453 | return _CPUFlags::getInstance().GetL2CacheSize(); | |
| 649 | } | ||
| 650 |