diff --git a/include/MUtils/CPUFeatures.h b/include/MUtils/CPUFeatures.h index 1be9026..d9028f0 100644 --- a/include/MUtils/CPUFeatures.h +++ b/include/MUtils/CPUFeatures.h @@ -19,6 +19,13 @@ // http://www.gnu.org/licenses/lgpl-2.1.txt ////////////////////////////////////////////////////////////////////////////////// +/** +* @file +* @brief This file contains function for detecting information about the CPU +* +* Call the MUtils::CPUFetaures::detect() to detect information about the processor, which will be returned in a `MUtils::CPUFetaures::cpu_info_t` struct. +*/ + #pragma once //MUtils @@ -29,33 +36,52 @@ namespace MUtils { + /** + * \brief This namespace contains functions and constants for detecting CPU information + * + * Call the detect() to detect information about the processor, which will be returned in a `cpu_info_t` struct. + */ namespace CPUFetaures { - //CPU flags - static const quint32 FLAG_MMX = 0x01; - static const quint32 FLAG_SSE = 0x02; - static const quint32 FLAG_SSE2 = 0x04; - static const quint32 FLAG_SSE3 = 0x08; - static const quint32 FLAG_SSSE3 = 0x10; - static const quint32 FLAG_SSE4 = 0x20; - static const quint32 FLAG_SSE42 = 0x40; - static const quint32 FLAG_AVX = 0x80; + // CPU vendor flag + static const uint8_t VENDOR_INTEL = 0x01U; ///< \brief CPU vendor flag \details Indicates that the CPU's vendor is *Intel* + static const uint8_t VENDOR_AMD = 0x02U; ///< \brief CPU vendor flag \details Indicates that the CPU's vendor is *AMD* - //CPU features - typedef struct _cpu_info_t + // CPU feature flag + static const quint32 FLAG_CMOV = 0x001U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *CMOV* instruction + static const quint32 FLAG_MMX = 0x002U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *MMX* instruction set extension + static const quint32 FLAG_SSE = 0x004U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *SSE* instruction set extension + static const quint32 FLAG_SSE2 = 0x008U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *SSE2* instruction set extension + static const quint32 FLAG_SSE3 = 0x010U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *SSE3* instruction set extension + static const quint32 FLAG_SSSE3 = 0x020U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *SSSE3* instruction set extension + static const quint32 FLAG_SSE4 = 0x030U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *SSE4.1* instruction set extension + static const quint32 FLAG_SSE42 = 0x080U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *SSE4.2* instruction set extension + static const quint32 FLAG_AVX = 0x100U; ///< \brief CPU feature flag \details Indicates that the CPU supports the *AVX* instruction set extension + + /** + * \brief Struct to hold information about the CPU + */ + typedef struct { - quint32 family; - quint32 model; - quint32 stepping; - quint32 count; - quint32 features; - bool x64; - bool intel; - char vendor[0x40]; - char brand[0x40]; + quint32 family; ///< CPU *family* indicator, which specifies the processor "generation" to which the CPU belongs + quint32 model; ///< CPU *model* indicator, which is used to distinguish processor "variants" within a generation + quint32 stepping; ///< CPU *stepping* indicator, which is used to distinguish "revisions" of a certain processor model + quint32 count; ///< The number of available (logical) processors + quint32 features; ///< CPU *feature* flags, indicating suppoprt for extended instruction sets; all flags are OR-combined + bool x64; ///< Indicates that the processor and the operating system support 64-Bit (AMD64/EM64T) + uint8_t vendor; ///< CPU *vendor* flag; might be zero, if vendor is unknown + char idstr[13]; ///< CPU *identifier* string, exactly 12 characters (e.g. "GenuineIntel" or "AuthenticAMD") + char brand[48]; ///< CPU *brand* string, up to 48 characters (e.g. "Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz") } cpu_info_t; + /** + * \brief Detect processor information + * + * Detects information about the CPU on which the application is running, including CPU vendor, identifier string, feature flags (MMX, SSE, AVX, etc) as well as the CPU core count. + * + * \return The function returns a `cpu_info_t` struct containing the detected information about the CPU. + */ MUTILS_API cpu_info_t detect(void); } } diff --git a/include/MUtils/Global.h b/include/MUtils/Global.h index 5f7741f..687db49 100644 --- a/include/MUtils/Global.h +++ b/include/MUtils/Global.h @@ -80,6 +80,9 @@ class QProcess; /////////////////////////////////////////////////////////////////////////////// +/** +* \brief Global MUtils namespace +*/ namespace MUtils { /** diff --git a/include/MUtilsInfo.dox b/include/MUtilsInfo.dox index 6dd7798..069e190 100644 --- a/include/MUtilsInfo.dox +++ b/include/MUtilsInfo.dox @@ -10,6 +10,7 @@ * * The public API of the *MUtilities* library is defined in the following header files (select file for details): * - **Global.h** – miscellaneous useful functions + * - **CPUFeatures.h** – functions for detection information about the CPU * * * # Example diff --git a/src/CPUFeatures_Win32.cpp b/src/CPUFeatures_Win32.cpp index 1220744..ce2e890 100644 --- a/src/CPUFeatures_Win32.cpp +++ b/src/CPUFeatures_Win32.cpp @@ -28,76 +28,73 @@ #include #include "Utils_Win32.h" +#define MY_CPUID(X,Y) __cpuid(((int*)(X)), ((int)(Y))) +#define CHECK_VENDOR(X,Y,Z) (_stricmp((X), (Y)) ? 0U : (Z)); +#define CHECK_FLAG(X,Y,Z) (((X) & (Y)) ? (Z) : 0U) + MUtils::CPUFetaures::cpu_info_t MUtils::CPUFetaures::detect(void) { const OS::ArgumentMap &args = OS::arguments(); typedef BOOL (WINAPI *IsWow64ProcessFun)(__in HANDLE hProcess, __out PBOOL Wow64Process); + static const quint32 FLAGS_X64 = (FLAG_MMX | FLAG_SSE | FLAG_SSE2); - cpu_info_t features; + cpu_info_t features; SYSTEM_INFO systemInfo; - int CPUInfo[4] = {-1}; - char CPUIdentificationString[0x40]; - char CPUBrandString[0x40]; + uint32_t cpuInfo[4]; - memset(&features, 0, sizeof(cpu_info_t)); + //Initialize variables to zero + memset(&features, 0, sizeof(cpu_info_t)); memset(&systemInfo, 0, sizeof(SYSTEM_INFO)); - memset(CPUIdentificationString, 0, sizeof(CPUIdentificationString)); - memset(CPUBrandString, 0, sizeof(CPUBrandString)); - - __cpuid(CPUInfo, 0); - memcpy(CPUIdentificationString, &CPUInfo[1], sizeof(int)); - memcpy(CPUIdentificationString + 4, &CPUInfo[3], sizeof(int)); - memcpy(CPUIdentificationString + 8, &CPUInfo[2], sizeof(int)); - features.intel = (_stricmp(CPUIdentificationString, "GenuineIntel") == 0); - strncpy_s(features.vendor, 0x40, CPUIdentificationString, _TRUNCATE); + memset(cpuInfo, 0, sizeof(cpuInfo)); - if(CPUInfo[0] >= 1) + //Detect the CPU identifier string + MY_CPUID(&cpuInfo[0], 0); + memcpy(&features.idstr[0U * sizeof(uint32_t)], &cpuInfo[1], sizeof(uint32_t)); + memcpy(&features.idstr[1U * sizeof(uint32_t)], &cpuInfo[3], sizeof(uint32_t)); + memcpy(&features.idstr[2U * sizeof(uint32_t)], &cpuInfo[2], sizeof(uint32_t)); + features.idstr[3U * sizeof(uint32_t)] = '\0'; + features.vendor |= CHECK_VENDOR(features.idstr, "GenuineIntel", VENDOR_INTEL); + features.vendor |= CHECK_VENDOR(features.idstr, "AuthenticAMD", VENDOR_AMD); + + //Detect the CPU model and feature flags + if(cpuInfo[0] >= 1) { - __cpuid(CPUInfo, 1); - if(CPUInfo[3] & 0x00800000) features.features |= FLAG_MMX; - if(CPUInfo[3] & 0x02000000) features.features |= FLAG_SSE; - if(CPUInfo[3] & 0x04000000) features.features |= FLAG_SSE2; - if(CPUInfo[2] & 0x00000001) features.features |= FLAG_SSE3; - if(CPUInfo[2] & 0x00000200) features.features |= FLAG_SSSE3; - if(CPUInfo[2] & 0x00080000) features.features |= FLAG_SSE4; - if(CPUInfo[2] & 0x00100000) features.features |= FLAG_SSE42; - if ((CPUInfo[2] & 0x18000000) == 0x18000000) + MY_CPUID(&cpuInfo[0], 1); + features.features |= CHECK_FLAG(cpuInfo[3], 0x00008000, FLAG_CMOV); + features.features |= CHECK_FLAG(cpuInfo[3], 0x00800000, FLAG_MMX); + features.features |= CHECK_FLAG(cpuInfo[3], 0x02000000, FLAG_SSE); + features.features |= CHECK_FLAG(cpuInfo[3], 0x04000000, FLAG_SSE2); + features.features |= CHECK_FLAG(cpuInfo[2], 0x00000001, FLAG_SSE3); + features.features |= CHECK_FLAG(cpuInfo[2], 0x00000200, FLAG_SSSE3); + features.features |= CHECK_FLAG(cpuInfo[2], 0x00080000, FLAG_SSE4); + features.features |= CHECK_FLAG(cpuInfo[2], 0x00100000, FLAG_SSE42); + + //Check for AVX + if ((cpuInfo[2] & 0x18000000) == 0x18000000) { if((_xgetbv(0) & 0x6ui64) == 0x6ui64) /*AVX requires OS support!*/ { features.features |= FLAG_AVX; } } - features.stepping = CPUInfo[0] & 0xf; - features.model = ((CPUInfo[0] >> 4) & 0xf) + (((CPUInfo[0] >> 16) & 0xf) << 4); - features.family = ((CPUInfo[0] >> 8) & 0xf) + ((CPUInfo[0] >> 20) & 0xff); + + //Compute the CPU stepping, model and family + features.stepping = cpuInfo[0] & 0xf; + features.model = ((cpuInfo[0] >> 4) & 0xf) + (((cpuInfo[0] >> 16) & 0xf) << 4); + features.family = ((cpuInfo[0] >> 8) & 0xf) + ((cpuInfo[0] >> 20) & 0xff); } - __cpuid(CPUInfo, 0x80000000); - int nExIds = qMax(qMin(CPUInfo[0], 0x80000004), 0x80000000); - - for(int i = 0x80000002; i <= nExIds; ++i) + //Read the CPU "brand" string + MY_CPUID(&cpuInfo[0], 0x80000000); + const uint32_t nExIds = qBound(0x80000000, cpuInfo[0], 0x80000004); + for(uint32_t i = 0x80000002; i <= nExIds; ++i) { - __cpuid(CPUInfo, i); - switch(i) - { - case 0x80000002: - memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); - break; - case 0x80000003: - memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); - break; - case 0x80000004: - memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); - break; - } + MY_CPUID(&cpuInfo[0], i); + memcpy(&features.brand[(i - 0x80000002) * sizeof(cpuInfo)], &cpuInfo[0], sizeof(cpuInfo)); } + features.brand[sizeof(features.brand) - 1] = '\0'; - strncpy_s(features.brand, 0x40, CPUBrandString, _TRUNCATE); - - if(strlen(features.brand) < 1) strncpy_s(features.brand, 0x40, "Unknown", _TRUNCATE); - if(strlen(features.vendor) < 1) strncpy_s(features.vendor, 0x40, "Unknown", _TRUNCATE); - + //Detect 64-Bit processors #if (!(defined(_M_X64) || defined(_M_IA64))) const IsWow64ProcessFun isWow64ProcessPtr = MUtils::Win32Utils::resolve(QLatin1String("kernel32"), QLatin1String("IsWow64Process")); if(isWow64ProcessPtr) @@ -105,24 +102,26 @@ MUtils::CPUFetaures::cpu_info_t MUtils::CPUFetaures::detect(void) BOOL x64flag = FALSE; if(isWow64ProcessPtr(GetCurrentProcess(), &x64flag)) { - if(x64flag) features.x64 = true; + if (x64flag) + { + features.x64 = true; + features.features |= FLAGS_X64; /*x86_64 implies SSE2*/ + } } } #else features.x64 = true; + features.features |= FLAGS_X64; #endif - if (features.x64) - { - features.features |= (FLAG_MMX | FLAG_SSE | FLAG_SSE2); /*x86_64 implies SSE2*/ - } - + //Make sure that (at least) the MMX flag has been set! if (!(features.features & FLAG_MMX)) { qWarning("Warning: CPU does not seem to support MMX. Take care!\n"); features.features = 0; } + //Count the number of available(!) CPU cores DWORD_PTR procAffinity, sysAffinity; if(GetProcessAffinityMask(GetCurrentProcess(), &procAffinity, &sysAffinity)) { @@ -137,11 +136,11 @@ MUtils::CPUFetaures::cpu_info_t MUtils::CPUFetaures::detect(void) features.count = qBound(1UL, systemInfo.dwNumberOfProcessors, 64UL); } + //Apply manual CPU overwrites bool userFlag = false; - if(args.contains("force-cpu-no-64bit")) { userFlag = true; features.x64 = false; } - if(args.contains("force-cpu-no-sse" )) { userFlag = true; features.features &= (~(FLAG_SSE | FLAG_SSE2 | FLAG_SSE3 | FLAG_SSSE3 | FLAG_SSE4 | FLAG_SSE42)); } - if(args.contains("force-cpu-no-intel")) { userFlag = true; features.intel = false; } - + if (args.contains(QLatin1String("cpu-no-simd"))) { userFlag = true; features.features = 0U; } + if (args.contains(QLatin1String("cpu-no-vendor"))) { userFlag = true; features.vendor = 0U; } + if (args.contains(QLatin1String("cpu-no-x64"))) { userFlag = true; features.x64 = 0U; } if(userFlag) { qWarning("CPU flags overwritten by user-defined parameters. Take care!\n");