[VC兼容32位和64位] 檢查MMX和SSE系列指令集的支持級別


  以前我寫了一篇《[VC6] 檢查MMX和SSE系列指令集的支持級別(最高SSE4.2)》(http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html)。現在發現該方法存在兩點缺陷——
1.不支持64位,因為VC的64位程序不支持內嵌匯編;
2.沒有區分硬件支持與操作系統支持。

  怎么解決這兩點缺陷呢?
  對於第1點,可以利用Intrinsics函數來兼容32位和64位。為了更方便的使用CPUID指令,可以利用《如何在各個版本的VC及64位下使用CPUID指令》(http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html)的成果。
  對於第2點,考慮到再增加一組函數會使函數過多,於是決定采用增加一個指針參數的方式——函數的返回值用於返回操作系統支持性,指針參數用於返回硬件支持性。因為操作系統支持性,對開發SIMD程序來說更加重要。

一、檢查MMX

  檢查以下標志位可判斷硬件是否支持MMX——
CPUID(1).EDX.MMX[bit 23]=1 // 硬件支持MMX

  檢查完硬件支持性,還需檢查操作系統的支持性。最簡單的辦法就是使用結構化異常處理來執行一條MMX指令。代碼如下——

BOOL    simd_mmx(BOOL* phwmmx)
{
    const INT32    BIT_D_MMX = 0x00800000;    // bit 23
    BOOL    rt = FALSE;    // result
    INT32 dwBuf[4];

    // check processor support
    __cpuid(dwBuf, 1);    // Function 1: Feature Information
    if ( dwBuf[3] & BIT_D_MMX )    rt=TRUE;
    if (NULL!=phwmmx)    *phwmmx=rt;

    // check OS support
    if ( rt )
    {
#if defined(_WIN64)
        // VC編譯器不支持64位下的MMX。
        rt=FALSE;
#else
        __try 
        {
            _mm_empty();    // MMX instruction: emms
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            rt=FALSE;
        }
#endif    // #if defined(_WIN64)
    }

    return rt;
}

 

  根據Intel文檔,似乎64位下也支持MMX指令。但是VC編譯器似乎不允許在64位下使用MMX指令(例如對於_mm_empty,會報告找不到符號鏈接)。具體原因尚不清楚。


二、檢查SSE

  檢查以下標志位可判斷硬件是否支持SSE——
CPUID(1).EDX.SSE[bit 25]=1 // 硬件支持SSE1
CPUID(1).EDX.SSE2[bit 26]=1 // 硬件支持SSE2
CPUID(1).ECX.SSE3[bit 0]=1 // 硬件支持SSE3
CPUID(1).ECX.SSSE3[bit 9]=1 // 硬件支持SSE3
CPUID(1).ECX.SSE41[bit 19]=1 // 硬件支持SSE4.1
CPUID(1).ECX.SSE42[bit 20]=1 // 硬件支持SSE4.2

  檢查完硬件支持性,還需檢查操作系統的支持性。很多資料說需要檢查以下標志位——
CR0.EM[bit 2]=0 // 浮點模擬被禁止
CR4.OSFXSR[bit 9]=1 // 在進程切換時,操作系統支持保護SIMD浮點狀態

  可是CR0、CR4這些控制寄存器只能在ring0中訪問,而一般應用程序是ring3的,無法獲得上述標志位信息。所以建議還是使用結構化異常處理來執行一條SSE指令。代碼如下——

int    simd_sse_level(int* phwsse)
{
    const INT32    BIT_D_SSE = 0x02000000;    // bit 25
    const INT32    BIT_D_SSE2 = 0x04000000;    // bit 26
    const INT32    BIT_C_SSE3 = 0x00000001;    // bit 0
    const INT32    BIT_C_SSSE3 = 0x00000100;    // bit 9
    const INT32    BIT_C_SSE41 = 0x00080000;    // bit 19
    const INT32    BIT_C_SSE42 = 0x00100000;    // bit 20
    int    rt = SIMD_SSE_NONE;    // result
    INT32 dwBuf[4];

    // check processor support
    __cpuid(dwBuf, 1);    // Function 1: Feature Information
    if ( dwBuf[3] & BIT_D_SSE )
    {
        rt = SIMD_SSE_1;
        if ( dwBuf[3] & BIT_D_SSE2 )
        {
            rt = SIMD_SSE_2;
            if ( dwBuf[2] & BIT_C_SSE3 )
            {
                rt = SIMD_SSE_3;
                if ( dwBuf[2] & BIT_C_SSSE3 )
                {
                    rt = SIMD_SSE_3S;
                    if ( dwBuf[2] & BIT_C_SSE41 )
                    {
                        rt = SIMD_SSE_41;
                        if ( dwBuf[2] & BIT_C_SSE42 )
                        {
                            rt = SIMD_SSE_42;
                        }
                    }
                }
            }
        }
    }
    if (NULL!=phwsse)    *phwsse=rt;

    // check OS support
    __try 
    {
        __m128 xmm1 = _mm_setzero_ps();    // SSE instruction: xorps
        if (0!=*(int*)&xmm1)    rt = SIMD_SSE_NONE;    // 避免Release模式編譯優化時剔除上一條語句
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        rt = SIMD_SSE_NONE;
    }

    return rt;
}

  “if (0!=*(int*)&xmm1) rt = SIMD_SSE_NONE;”是為了避免編譯優化剔除無意義語句。“*(int*)&xmm1”返回xmm1中的首個int,如果上面的“_mm_setzero_ps()”執行成功,那么它應該是0。

 

三、全部代碼

  全部代碼——

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#if _MSC_VER >=1400    // VC2005才支持intrin.h
#include <intrin.h>    // 所有Intrinsics函數
#else
#include <emmintrin.h>    // MMX, SSE, SSE2
#endif


// SSE系列指令集的支持級別. simd_sse_level 函數的返回值。
#define SIMD_SSE_NONE    0    // 不支持
#define SIMD_SSE_1    1    // SSE
#define SIMD_SSE_2    2    // SSE2
#define SIMD_SSE_3    3    // SSE3
#define SIMD_SSE_3S    4    // SSSE3
#define SIMD_SSE_41    5    // SSE4.1
#define SIMD_SSE_42    6    // SSE4.2

const char*    simd_sse_names[] = {
    "None",
    "SSE",
    "SSE2",
    "SSE3",
    "SSSE3",
    "SSE4.1",
    "SSE4.2",
};

char szBuf[64];
INT32 dwBuf[4];

#if defined(_WIN64)
// 64位下不支持內聯匯編. 應使用__cpuid、__cpuidex等Intrinsics函數。
#else
#if _MSC_VER < 1600    // VS2010. 據說VC2008 SP1之后才支持__cpuidex
void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue)
{
    if (NULL==CPUInfo)    return;
    _asm{
        // load. 讀取參數到寄存器
        mov edi, CPUInfo;    // 准備用edi尋址CPUInfo
        mov eax, InfoType;
        mov ecx, ECXValue;
        // CPUID
        cpuid;
        // save. 將寄存器保存到CPUInfo
        mov    [edi], eax;
        mov    [edi+4], ebx;
        mov    [edi+8], ecx;
        mov    [edi+12], edx;
    }
}
#endif    // #if _MSC_VER < 1600    // VS2010. 據說VC2008 SP1之后才支持__cpuidex

#if _MSC_VER < 1400    // VC2005才支持__cpuid
void __cpuid(INT32 CPUInfo[4], INT32 InfoType)
{
    __cpuidex(CPUInfo, InfoType, 0);
}
#endif    // #if _MSC_VER < 1400    // VC2005才支持__cpuid

#endif    // #if defined(_WIN64)

// 取得CPU廠商(Vendor)
//
// result: 成功時返回字符串的長度(一般為12)。失敗時返回0。
// pvendor: 接收廠商信息的字符串緩沖區。至少為13字節。
int cpu_getvendor(char* pvendor)
{
    INT32 dwBuf[4];
    if (NULL==pvendor)    return 0;
    // Function 0: Vendor-ID and Largest Standard Function
    __cpuid(dwBuf, 0);
    // save. 保存到pvendor
    *(INT32*)&pvendor[0] = dwBuf[1];    // ebx: 前四個字符
    *(INT32*)&pvendor[4] = dwBuf[3];    // edx: 中間四個字符
    *(INT32*)&pvendor[8] = dwBuf[2];    // ecx: 最后四個字符
    pvendor[12] = '\0';
    return 12;
}

// 取得CPU商標(Brand)
//
// result: 成功時返回字符串的長度(一般為48)。失敗時返回0。
// pbrand: 接收商標信息的字符串緩沖區。至少為49字節。
int cpu_getbrand(char* pbrand)
{
    INT32 dwBuf[4];
    if (NULL==pbrand)    return 0;
    // Function 0x80000000: Largest Extended Function Number
    __cpuid(dwBuf, 0x80000000);
    if (dwBuf[0] < 0x80000004)    return 0;
    // Function 80000002h,80000003h,80000004h: Processor Brand String
    __cpuid((INT32*)&pbrand[0], 0x80000002);    // 前16個字符
    __cpuid((INT32*)&pbrand[16], 0x80000003);    // 中間16個字符
    __cpuid((INT32*)&pbrand[32], 0x80000004);    // 最后16個字符
    pbrand[48] = '\0';
    return 48;
}


// 是否支持MMX指令集
BOOL    simd_mmx(BOOL* phwmmx)
{
    const INT32    BIT_D_MMX = 0x00800000;    // bit 23
    BOOL    rt = FALSE;    // result
    INT32 dwBuf[4];

    // check processor support
    __cpuid(dwBuf, 1);    // Function 1: Feature Information
    if ( dwBuf[3] & BIT_D_MMX )    rt=TRUE;
    if (NULL!=phwmmx)    *phwmmx=rt;

    // check OS support
    if ( rt )
    {
#if defined(_WIN64)
        // VC編譯器不支持64位下的MMX。
        rt=FALSE;
#else
        __try 
        {
            _mm_empty();    // MMX instruction: emms
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            rt=FALSE;
        }
#endif    // #if defined(_WIN64)
    }

    return rt;
}

// 檢測SSE系列指令集的支持級別
int    simd_sse_level(int* phwsse)
{
    const INT32    BIT_D_SSE = 0x02000000;    // bit 25
    const INT32    BIT_D_SSE2 = 0x04000000;    // bit 26
    const INT32    BIT_C_SSE3 = 0x00000001;    // bit 0
    const INT32    BIT_C_SSSE3 = 0x00000100;    // bit 9
    const INT32    BIT_C_SSE41 = 0x00080000;    // bit 19
    const INT32    BIT_C_SSE42 = 0x00100000;    // bit 20
    int    rt = SIMD_SSE_NONE;    // result
    INT32 dwBuf[4];

    // check processor support
    __cpuid(dwBuf, 1);    // Function 1: Feature Information
    if ( dwBuf[3] & BIT_D_SSE )
    {
        rt = SIMD_SSE_1;
        if ( dwBuf[3] & BIT_D_SSE2 )
        {
            rt = SIMD_SSE_2;
            if ( dwBuf[2] & BIT_C_SSE3 )
            {
                rt = SIMD_SSE_3;
                if ( dwBuf[2] & BIT_C_SSSE3 )
                {
                    rt = SIMD_SSE_3S;
                    if ( dwBuf[2] & BIT_C_SSE41 )
                    {
                        rt = SIMD_SSE_41;
                        if ( dwBuf[2] & BIT_C_SSE42 )
                        {
                            rt = SIMD_SSE_42;
                        }
                    }
                }
            }
        }
    }
    if (NULL!=phwsse)    *phwsse=rt;

    // check OS support
    __try 
    {
        __m128 xmm1 = _mm_setzero_ps();    // SSE instruction: xorps
        if (0!=*(int*)&xmm1)    rt = SIMD_SSE_NONE;    // 避免Release模式編譯優化時剔除上一條語句
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        rt = SIMD_SSE_NONE;
    }

    return rt;
}


int _tmain(int argc, _TCHAR* argv[])
{
    //__cpuidex(dwBuf, 0,0);
    //__cpuid(dwBuf, 0);
    //printf("%.8X\t%.8X\t%.8X\t%.8X\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]);

    cpu_getvendor(szBuf);
    printf("CPU Vendor:\t%s\n", szBuf);

    cpu_getbrand(szBuf);
    printf("CPU Name:\t%s\n", szBuf);

    BOOL bhwmmx;    // 硬件支持MMX
    BOOL bmmx;    // 操作系統支持MMX
    bmmx = simd_mmx(&bhwmmx);
    printf("MMX: %d\t// hw: %d\n", bmmx, bhwmmx);

    int    nhwsse;    // 硬件支持SSE
    int    nsse;    // 操作系統支持SSE
    nsse = simd_sse_level(&nhwsse);
    printf("SSE: %d\t// hw: %d\n", nsse, nhwsse);
    for(int i=1; i<sizeof(simd_sse_names); ++i)
    {
        if (nhwsse>=i)    printf("\t%s\n", simd_sse_names[i]);
    }

    return 0;
}

 

  在以下編譯器中編譯成功——
VC6(32位)
VC2003(32位)
VC2005(32位、64位)
VC2010(32位、64位)


四、測試結果

  在64位的win7中執行“x64\Release\checksimd64_2010.exe”,運行效果——


  還可以觀察編譯器生成的匯編代碼,摘自“x64\Release\checksimd64.cod”——

; 187  :     // check OS support
; 188  :     __try 
; 189  :     {
; 190  :         __m128 xmm1 = _mm_setzero_ps();    // SSE instruction: xorps

  00077    0f 57 c0     xorps     xmm0, xmm0
  0007a    0f 29 44 24 10     movaps     XMMWORD PTR xmm1$72829[rsp], xmm0

; 191  :         if (0!=*(int*)&xmm1)    rt = SIMD_SSE_NONE;    // 避免Release模式編譯優化時剔除上一條語句

  0007f    83 7c 24 10 00     cmp     DWORD PTR xmm1$72829[rsp], 0
  00084    45 0f 45 c2     cmovne     r8d, r10d
  00088    44 89 04 24     mov     DWORD PTR rt$[rsp], r8d

; 192  :     }

  可見Release模式下也正常生成了xorps指令,並沒有被編譯優化掉。

 

參考文獻——

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. May 2012. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html

《Intel® Processor Identification and the CPUID Instruction》. April 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html

《AMD64 Architecture Programmer's Manual Volume 3: General Purpose and System Instructions》. December 2011. http://support.amd.com/us/Processor_TechDocs/24594_APM_v3.pdf

《AMD CPUID Specification》. September 2010. http://support.amd.com/us/Embedded_TechDocs/25481.pdf

《[VC6] 檢查MMX和SSE系列指令集的支持級別(最高SSE4.2)》:http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html

《如何在各個版本的VC及64位下使用CPUID指令》:http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html

 

源碼下載——

http://files.cnblogs.com/zyl910/checksimd64.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM