From 5eab1e559a4a3b47e25b4a9612e1c75f650813fb Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 9 Nov 2025 10:54:12 -0800 Subject: [PATCH 1/3] CPUID: Fixes APICID for processor count calculation. Primary fix here is returning the current CPU index in function 01h. Intel Quartus uses this alongside affinity setting to check if all cores can be used for its calculation. Since we had hardcoded apicid 0 here, it assumed to only have one core and never generated worker threads. Additional fix for apicid size. This is the size of the bitmask required for apic ids, we weren't calculating this correctly at all. This mask is a "maximum" number of APICs that the CPU reserves in power of two. Say the core supports 256 APICs, but the processor only supports 16, or any other combination. --- FEXCore/Source/Interface/Core/CPUID.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/FEXCore/Source/Interface/Core/CPUID.cpp b/FEXCore/Source/Interface/Core/CPUID.cpp index d8e28181e..f49d86373 100644 --- a/FEXCore/Source/Interface/Core/CPUID.cpp +++ b/FEXCore/Source/Interface/Core/CPUID.cpp @@ -14,6 +14,7 @@ $end_info$ #include #include #include +#include #include #include @@ -443,10 +444,10 @@ FEXCore::CPUID::FunctionResults CPUIDEmu::Function_01h(uint32_t Leaf) const { Res.eax = FAMILY_IDENTIFIER; - Res.ebx = 0 | // Brand index - (8 << 8) | // Cache line size in bytes - (Cores << 16) | // Number of addressable IDs for the logical cores in the physical CPU - (0 << 24); // Local APIC ID + Res.ebx = 0 | // Brand index + (8 << 8) | // Cache line size in bytes + (Cores << 16) | // Number of addressable IDs for the logical cores in the physical CPU + (GetCPUID() << 24); // Local APIC ID Res.ecx = (1 << 0) | // SSE3 (CTX->HostFeatures.SupportsPMULL_128Bit << 1) | // PCLMULQDQ @@ -1096,9 +1097,9 @@ FEXCore::CPUID::FunctionResults CPUIDEmu::Function_8000_0008h(uint32_t Leaf) con (CTX->HostFeatures.SupportsCLZERO << 0); // CLZERO support uint32_t CoreCount = Cores - 1; - Res.ecx = (0 << 16) | // PerfTscSize: Performance timestamp count size - ((uint32_t)std::log2(CoreCount + 1) << 12) | // ApicIdSize: Number of bits in ApicID - (CoreCount << 0); // Count count subtract one + Res.ecx = (0 << 16) | // PerfTscSize: Performance timestamp count size + (std::bit_ceil(Cores) << 12) | // ApicIdSize: Number of bits in ApicID + (CoreCount << 0); // Count count subtract one return Res; } From 94edbc3436bb5e8b784c1fae43c2637077b63b34 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 9 Nov 2025 11:35:17 -0800 Subject: [PATCH 2/3] CPUID: Stop accidentally exposing the HTT bit We don't support this. --- FEXCore/Source/Interface/Core/CPUID.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FEXCore/Source/Interface/Core/CPUID.cpp b/FEXCore/Source/Interface/Core/CPUID.cpp index f49d86373..661b1f86c 100644 --- a/FEXCore/Source/Interface/Core/CPUID.cpp +++ b/FEXCore/Source/Interface/Core/CPUID.cpp @@ -510,7 +510,7 @@ FEXCore::CPUID::FunctionResults CPUIDEmu::Function_01h(uint32_t Leaf) const { (1 << 25) | // SSE (1 << 26) | // SSE2 (0 << 27) | // Self Snoop - (1 << 28) | // Max APIC IDs reserved field is valid + (0 << 28) | // (HTT) Max APIC IDs reserved field is valid (1 << 29) | // Thermal monitor (0 << 30) | // Reserved (0 << 31); // Pending break enable From 94b690df435937170650f71a9d9b2aadd5c7571a Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 9 Nov 2025 10:58:31 -0800 Subject: [PATCH 3/3] unittests/FEXLinuxTests: Adds cpu core count test to cpuid Ensures cpuid core counts are reported correctly. --- .../FEXLinuxTests/tests/cpu/cpu_count.cpp | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 unittests/FEXLinuxTests/tests/cpu/cpu_count.cpp diff --git a/unittests/FEXLinuxTests/tests/cpu/cpu_count.cpp b/unittests/FEXLinuxTests/tests/cpu/cpu_count.cpp new file mode 100644 index 000000000..89f219d0f --- /dev/null +++ b/unittests/FEXLinuxTests/tests/cpu/cpu_count.cpp @@ -0,0 +1,104 @@ +#include + +#include +#include +#include +#include + +TEST_CASE("cpu count - libraries") { + const auto hw_concurrency = std::thread::hardware_concurrency(); + CHECK(hw_concurrency == sysconf(_SC_NPROCESSORS_CONF)); + CHECK(hw_concurrency == sysconf(_SC_NPROCESSORS_ONLN)); +} + +struct core_info { + uint32_t local_apicid; + uint32_t max_addressible_ids; + uint32_t cores; + uint32_t threads; + bool HTT; +}; + +struct cpuid_fn { + uint32_t eax, ebx, ecx, edx; +}; + +cpuid_fn get_cpuid(uint32_t func, uint32_t leaf = 0) { + cpuid_fn fn {}; + __cpuid_count(func, leaf, fn.eax, fn.ebx, fn.ecx, fn.edx); + return fn; +} + +std::optional cpuid_calculate_core_info() { + core_info info {}; + + // Legacy path + const auto cpuid_fn_0 = get_cpuid(0); + if (cpuid_fn_0.eax < 1) { + return std::nullopt; + } + + const auto cpuid_fn_1 = get_cpuid(1); + + info.local_apicid = cpuid_fn_1.ebx >> 24; + info.HTT = (cpuid_fn_1.edx >> 28) & 1; + + const auto cpuid_fn_8000_0000 = get_cpuid(0x8000'0000U); + + if (cpuid_fn_8000_0000.eax < 0x8000'0008) { + return std::nullopt; + } + + const auto cpuid_fn_8000_0008 = get_cpuid(0x8000'0008U); + const uint32_t apic_id_size = (cpuid_fn_8000_0008.ecx >> 12) & 0xF; + + // E.5.2: MNLP (Maximum Number of Logical Processors) + uint32_t MNLP {}; + + if (apic_id_size) { + // Extended topology. + MNLP = 1 << apic_id_size; + } else { + // Legacy path. + MNLP = (cpuid_fn_8000_0008.ecx & 0xF) + 1; + } + + info.max_addressible_ids = MNLP; + + const auto cpuid_fn_4 = get_cpuid(4); + if (cpuid_fn_4.eax & 0xF) { + // Intel exclusive cpuid function, AMD returns zero as unsupported. + // `Maximum number of addressable IDs for processor cores in the physical package` + info.cores = (cpuid_fn_4.eax >> 26) + 1; + if (info.HTT) { + // `A value of 1 for HTT indicates the value in CPUID.1.EBX[23:16] (the Maximum number of addressable IDs for logical processors in + // this package) is valid for the package. `Maximum number of addressable IDs for logical processors in this physical package` + info.threads = (cpuid_fn_1.ebx >> 16) & 0xFF; + } else { + info.threads = info.cores; + } + } else if (info.HTT) { + info.cores = (cpuid_fn_1.ebx >> 16) & 0xFF; + info.threads = info.cores * 2; + } else { + // Legacy path means cores/threads is equal to MNLP. + info.cores = info.threads = MNLP; + } + + return info; +} + +TEST_CASE("cpu count - cpuid") { + const auto hw_concurrency = std::thread::hardware_concurrency(); + const auto core_info = cpuid_calculate_core_info(); + REQUIRE(core_info.has_value()); + CHECK(core_info->local_apicid < hw_concurrency); + CHECK(core_info->local_apicid < core_info->max_addressible_ids); + CHECK(core_info->max_addressible_ids >= hw_concurrency); + if (core_info->HTT) { + // May not be entirely correct on systems that mix HTT and non-HTT cpu cores. + CHECK((core_info->cores * 2) == core_info->threads); + } else { + CHECK(core_info->cores == core_info->threads); + } +}