How to verify which technologies the CPU supports at runtime?

Asked

Viewed 1,655 times

6

I am writing a program for PC that has optimized function calls for SSE2, SSE3 and maybe SSE4. However, I cannot guarantee that the PC running the program supports these technologies and would like to check the support so that I can choose the optimized version or even prevent the execution of the program.

The functions for verification would look like this:

namespace CPU
{
    /**
     * @return Retorna true se a CPU suporta SSE4.1.
     **/
    bool IsSSE41();

    bool IsSSE3();
    // ...
}

I saw that for Windows there is a function that can be used to check up the SSE3 at that link, besides the cpuid. However, I am looking for a more portable solution. What you suggest to me?

3 answers

6

Just to add the @Guilherme Bernal reply and share the code. I made a program to test the class that William posted and it looks like working. I haven’t fully tested yet as this varies from PC to PC. (I compiled in MSVC 2013, but should work in GCC as well).

I tested in a Core i5 and the result seems correct (compared with Cpuz).

Stayed like this:

#ifndef HARDWARE_HPP_INCLUDED
#define HARDWARE_HPP_INCLUDED

#include <string>

#ifdef _WIN32
#include <limits.h>
#include <intrin.h>
typedef unsigned __int32  uint32_t;
#else
#include <stdint.h>
#endif

namespace hardware
{
    enum TechEAX
    {
        kFSGSBASE = (1 << 0),
        kBMI = (1 << 3),
        kHLE = (1 << 4),
        kAVX2 = (1 << 5),
        kBMI2 = (1 << 8),
        kRTM    = (1 << 11),
        kRDSEED = (1 << 18),
        kADX = (1 << 19),
    };

    enum TechECX
    {
        kSSE3 = (1 << 0),
        kPCLMUL = (1 << 1),
        kLZCNT = (1 << 5),
        kSSSE3 = (1 << 9),
        kFMA = (1 << 12),
        kCMPXCHG16B = (1 << 13),
        kSSE4_1 = (1 << 19),
        kSSE4_2 = (1 << 20),
        kMOVBE = (1 << 22),
        kPOPCNT = (1 << 23),
        kAES = (1 << 25),
        kXSAVE = (1 << 26),
        kOSXSAVE = (1 << 27),
        kAVX = (1 << 28),
        kF16C = (1 << 29),
        kRDRND = (1 << 30),

        kLAHF_LM = (1 << 0),
        kABM = (1 << 5),
        kSSE4a = (1 << 6),
        kPRFCHW = (1 << 8),
        kXOP = (1 << 11),
        kLWP = (1 << 15),
        kFMA4 = (1 << 16),
        kTBM = (1 << 21)
    };

    enum TechEDX
    {
        kCMPXCHG8B = (1 << 8),
        kCMOV = (1 << 15),
        kMMX = (1 << 23),
        kFXSAVE = (1 << 24),
        kSSE = (1 << 25),
        kSSE2 = (1 << 26),

        kMMXEXT = (1 << 22),
        kLM = (1 << 29),
        k3DNOWP = (1 << 30),
        k3DNOW = (1 << 31)
    };

    class CPU
    {
        public:
            CPU();

            std::string getVendor() const;

            bool checkTechnology(TechEAX tech) const;
            bool checkTechnology(TechECX tech) const;
            bool checkTechnology(TechEDX tech) const;

        private:
            void load(unsigned value) const;

            const uint32_t& EAX() const
            {
                return regs[0];
            }

            const uint32_t& EBX() const
            {
                return regs[1];
            }

            const uint32_t& ECX() const
            {
                return regs[2];
            }

            const uint32_t& EDX() const
            {
                return regs[3];
            }

        private:
            uint32_t regs[4];
    };

}


#endif

#include "hardware.hpp"

namespace hardware
{
    CPU::CPU()
    {
        this->load(0);
    }

    bool CPU::checkTechnology(TechEAX tech) const
    {
        this->load(7);
        return (EAX() & tech) != 0;
    }

    bool CPU::checkTechnology(TechECX tech) const
    {
        this->load(1);
        return (ECX() & tech) != 0;
    }

    bool CPU::checkTechnology(TechEDX tech) const
    {
        this->load(1);
        return (EDX() & tech) != 0;
    }

    std::string CPU::getVendor() const
    {
        std::string vendor;
        vendor += std::string((const char *)&EBX(), 4);
        vendor += std::string((const char *)&EDX(), 4);
        vendor += std::string((const char *)&ECX(), 4);

        if (vendor == "AMDisbetter!" || vendor == "AuthenticAMD") return "AMD";
        if (vendor == "GenuineIntel") return "Intel";
        if (vendor == "VIA VIA VIA ") return "VIA";
        if (vendor == "CentaurHauls") return "Centaur";
        if (vendor == "CyrixInstead") return "Cyrix";
        if (vendor == "TransmetaCPU" || vendor == "GenuineTMx86") return "Transmeta";
        if (vendor == "Geode by NSC") return "National Semiconductor";
        if (vendor == "NexGenDriven") return "NexGen";
        if (vendor == "RiseRiseRise") return "Rise";
        if (vendor == "SiS SiS SiS ") return "SiS";
        if (vendor == "UMC UMC UMC ") return "UMC";
        if (vendor == "Vortex86 SoC") return "Vortex";
        if (vendor == "KVMKVMKVMKVM") return "KVM";
        if (vendor == "Microsoft Hv") return "Microsoft Hyper-V";
        if (vendor == "VMwareVMware") return "VMware";
        if (vendor == "XenVMMXenVMM") return "Xen HVM";

        return vendor;
    }

    void CPU::load(unsigned value) const
    {
#ifdef _WIN32
        __cpuid((int *) regs, (int)value);

#else
        asm volatile
            ("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
            : "a" (i), "c" (0));
        // ECX is set to zero for CPUID function 4
#endif
    }
}

#include <iostream>
#include "hardware.hpp"

int main(int argc, char** argv)
{
    using namespace hardware;

    CPU cpu;

    std::cout << "Fabricante: " << cpu.getVendor() << std::endl;
    std::cout << "       SSE: " << cpu.checkTechnology(kSSE) << std::endl;
    std::cout << "      SSE2: " << cpu.checkTechnology(kSSE2) << std::endl;
    std::cout << "      SSE3: " << cpu.checkTechnology(kSSE3) << std::endl;
    std::cout << "     SSSE3: " << cpu.checkTechnology(kSSSE3) << std::endl;
    std::cout << "    SSE4.1: " << cpu.checkTechnology(kSSE4_1) << std::endl;
    std::cout << "    SSE4.2: " << cpu.checkTechnology(kSSE4_2) << std::endl;
    std::cout << "     SSE4A: " << cpu.checkTechnology(kSSE4a) << std::endl;
    std::cout << "       AES: " << cpu.checkTechnology(kAES) << std::endl;
    std::cout << "       AVX: "  << cpu.checkTechnology(kAVX) << std::endl;
    std::cout << "      AVX2: " << cpu.checkTechnology(kAVX2) << std::endl;



    std::cin.get();

    return 0;
}

Output stayed (for the Intel Core i5 Sandy Bridge):

Fabricante: Intel
       SSE: 1
      SSE2: 1
      SSE3: 1
     SSSE3: 1
    SSE4.1: 1
    SSE4.2: 1
     SSE4A: 1
       AES: 1
       AVX: 1
      AVX2: 0
  • 1

    My recommendation (if there is no longer one in this sense), create a FOSS project on github for this purpose, make its original implementation and leave it open to contributions. I used to work in a company that did detection of machines for inventory and the code of the CPUID instructions was something that always had to be maintained with dedication, affection and revised for new architectures. There was a lot of problem, CPUZ staff lib was acquired specifically for memory detection that is more complicated, when this happened, it was considered to abandon CPUID for something that CPUZ lib already offered

  • @Okay. I’ll see if I can do it. I posted here so that the result was close to the question to help in the future, in case someone has the same doubt.

3

The library Yeppp! seems to have what you seek [1], [2], seems to be quite portable, and has a permissive license. Normally it will be used with the intention of enjoying high-level SIMD operations, but it came to mind that it offered CPU identification as well. I can’t say for sure but Boost.SIMD (not yet a Boost library) might support this as well.

The ideal is to support existing FOSS projects instead of hacking into Assembly and having to add support for future Features by yourself reissuing the wheel in your cave. Unless the intention is to create a FOSS, there is none that is to your liking, or, for a very unfortunate reason, it is not allowed to use a library with such a permissive license.

3


The ideal is to use CPUID as you suggested. It is an instruction of the x86 architecture that will return everything that the current CPU supports. To use this you need to use _cpuid() on Windows or directly Assembly on other systems. A portable class I found here (for jcoffland):

#pragma once

#ifdef _WIN32
#include <limits.h>
typedef unsigned __int32  uint32_t;
#else
#include <stdint.h>
#endif

class CPUID {
  uint32_t regs[4];

public:
  void CPUID(unsigned i) {
#ifdef _WIN32
    __cpuid((int *)regs, (int)i);
#else
    asm volatile
      ("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
       : "a" (i), "c" (0));
#endif
  }

  const uint32_t& EAX() const {return regs[0];}
  const uint32_t& EBX() const {return regs[1];}
  const uint32_t& ECX() const {return regs[2];}
  const uint32_t& EDX() const {return regs[3];}
};

Now just use according to the Wikipedia. For example:

bool hasSSE41() {
    CPUID cpuid(1);
    return (cpuid.ECX() >> 19) & 1;
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.