GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 58.8% 80 / 0 / 136
Functions: 76.9% 10 / 0 / 13
Branches: 26.4% 29 / 0 / 110

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