본문 바로가기

Developement/C/C++

MinGW-W64, CPU 명령어 빌드에 맞춰 구동 테스트 하기.


만약

 MinGW-W64 (또는 일반 MinGW) 에서 -mavx 또는 -march=corei7-avx 와 같은 빌드를 지시해서 만든 바이너리를 배포 하는데, 만약 이를 구동하는 PC 가 AVX 관련 명령어를 지원 하지 않는 환경에서 해당 바이너리를 구동하면?

이럴 경우 해당 바이너리는 그냥은 구동이 되나, AVX 관련 명령어가 실행 되는 위치에서 exception 을 뱉고, 해당 callstack 은 이전 pc 로 돌아가나, 여기서 문제가 발생 해서 함수 자체가 멍청이가 됩니다. 그래서 배포한 바이너리가 내가 빌드한 환경과 자동으로 구동 될 PC 에서의 환경과 맞는지를 간단히 테스트 할 수 있는 방법을 gcc 는 물론 MinGW 역시 지원 합니다.

 이를 확인 하기 위해서는 먼저 #include 에 <cpuid.h> 를 넣어 주고 나서 다음과 같은 함수를 main() 초기에 호출해 주면 됩니다.


#include <cpuid.h>

bool testing_cpu()
{
    unsigned int eax = 0;
    unsigned int ebx = 0;
    unsigned int ecx = 0;
    unsigned int edx = 0;
    unsigned int max_level = 0;
    unsigned int ext_level = 0;
    unsigned int vendor = 0;
    unsigned int model = 0;
    unsigned int family = 0;

    ⁄⁄ related in Core2Duo or DualCore.
    unsigned int has_sse3 = 0;
    unsigned int has_ssse3 = 0;
    unsigned int has_cmpxchg8b = 0;
    unsigned int has_cmov = 0;
    unsigned int has_mmx = 0;
    unsigned int has_sse = 0;
    unsigned int has_sse2 = 0;

    ⁄⁄ related in Core I series and AVX.
    unsigned int has_movbe = 0;
    unsigned int has_sse4_1 = 0;
    unsigned int has_sse4_2 = 0;
    unsigned int has_avx = 0;

    max_level = __get_cpuid_max (0, &vendor);
    if (max_level < 1)
        return false;

    __cpuid (1, eax, ebx, ecx, edx);

    model = (eax >> 4) & 0x0f;
    family = (eax >> 8) & 0x0f;

    if ( vendor == signature_INTEL_ebx || vendor == signature_AMD_ebx )
    {
        unsigned int extended_model, extended_family;

        extended_model = (eax >> 12) & 0xf0;
        extended_family = (eax >> 20) & 0xff;

        if (family == 0x0f)
        {
            family += extended_family;
            model  += extended_model;
        }
        else
        if (family == 0x06)
        {
            model += extended_model;
        }

        has_cmpxchg8b = edx & bit_CMPXCHG8B;
        has_cmov = edx & bit_CMOV;
        has_mmx = edx & bit_MMX;
        has_sse = edx & bit_SSE;
        has_sse2 = edx & bit_SSE2;

        has_sse3 = ecx & bit_SSE3;
        has_ssse3 = ecx & bit_SSSE3;
        has_sse4_1 = ecx & bit_SSE4_1;
        has_sse4_2 = ecx & bit_SSE4_2;
        has_avx = ecx & bit_AVX;

#ifdef __SSE3__
        if ( has_sse3 == 0 )
            return false;
#endif ⁄⁄⁄ of __SSE3__

#ifdef __SSE4_1__
        if ( has_sse4_1 == 0 )
            return false;
#endif ⁄⁄⁄ of __SSE4_1__

#ifdef __SSE4_2__
        if ( has_sse4_2 == 0 )
            return false;
#endif ⁄⁄⁄ of __SSE4_2__

#ifdef __AVX__
        if ( has_avx == 0 )
            return false;
#endif ⁄⁄⁄ of __AVX__

        return true;
    }

    return false;
}

 이는 자동으로 현재 빌드에 사용된 명렁어와 구동시 CPU 검사를 통해 맞지 않을 경우 자동으로 false 를 return 하도록 한 것인데, gcc 내에 구현된 cpuid 가 틀리지 않았다면 매우 정확히 구동 됩니다.

 예로 test.exe 를 -march=corei7-avx 로 빌드 한 것을 일반 DualCore 또는 Core2Duo Intel CPU 에서 구동을 하게 되면 이 함수에서 무조건 return false 를 받게 되므로 main() 에서 구동시 이를 확인하여 구동을 종료 시킬 수 있습니다.