• [C/C++] ccpuid:CPUID信息模块 V1.03版,改进mmx/sse指令可用性检查(使用signal、setjmp,支持纯C)、修正AVX检查Bug


    作者:zyl910

      之前的ccpuid V1.02的mmx/sse指令可用性检查存在缺陷。现在的V1.03版改进了mmx/sse指令可用性检查,使用signal、setjmp,能够支持纯C程序。修正了AVX检查Bug。增加多文件链接ccpuid的测试例程。


    一、更新说明

    1.1 改进mmx/sse指令可用性检查

    1.1.1 问题背景

      以前是使用结构化异常处理来确认当前环境是否能运行mmx/sse指令的。该方法存在两个问题。
      首先,仅有C++支持结构化异常处理,而纯C是不支持的。所以在V1.02版中,是根据__cplusplus宏来做条件编译的。造成仅有C++版支持指令可用性检查,而纯C版没有该检查能力。
      更重大的问题是——结构化异常处理并不能捕获mmx/sse指令错误,程序会崩溃。
      这是因为——当运行了当前环境不支持指令时,不会触发结构化异常处理,而是触发SIG_ILL(非法指令)信号,若没有该信号的处理函数,程序就崩溃了。

      本来我以为“硬件支持MMX/SSE,但当前运行环境不支持”这种情况是很罕见的,因为现在的主流操作系统(Windows、Linux、Mac OS X等)都是支持mmx和sse。
      但后来发现,这种情况是很容易出现的。这是因为AVX指令集的影响。
      当gcc用上“-mavx”编译参数时,gcc会将所有的SSE代码编译为“VEX前缀编码的SSE指令”,而不是“传统SSE编码的指令”。“VEX前缀编码的SSE指令”实际上是128位的AVX指令,只有CPU硬件支持AVX,并且操作系统支持AVX时,才能运行“VEX前缀编码的SSE指令”。
      就算你的代码中没有使用SSE,但是由于编译器的自动矢量化编译优化,也可能会自动生成VEX SSE指令,导致程序崩溃。

      而VC在这方面好一点,依然是生成传统编码的SSE指令,程序能够在非AVX环境下运行。


    1.1.2 解决办法

      怎么捕获SIG_ILL(非法指令)信号呢?C标准库提供了<signal.h>,用于处理信号。
      从SIG_ILL信号中怎么恢复呢?C标准库提供了<setjmp.h>,可以实现非局部转移。

      但是还存在一个问题——在信号处理函数中,怎么知道应该回到哪一个位置呢?这时只有靠一个全局变量来存放返回位置。
      而使用全局变量,可能会引起线程安全问题。暂时不管了,目前的跨平台多线程处理很麻烦。还是等C11普及后再说吧。


    1.1.3 具体实现1(修改ccpuid.h)

      在具体实现时,会遇到这个问题——纯C版的ccpuid只是一个头文件,按照惯例只能含有声明性内容,不能含有定义性内容。而信号处理函数必须是一个实际定义的函数,这样才能获取它的函数指针进行注册。
      如果再添加一个ccpuid.c源文件,在它里面编写信号处理函数。虽然这样更符合C语言惯例,但这样用起来很麻烦,而且可能还需要修改代码才能与新版的ccpuid兼容。
      所以我决定还是将信号处理函数及相关的全局变量放在头文件中。为了避免外部暴露,可以加上static关键字,使它们仅在文件内可见、不参与链接。

      由于现在需要检查MMX与SSE这两类指令,所以可以考虑将这种检查封装成为一个函数——

    // 收到SIGILL信号时的跳回地址.
    static jmp_buf *volatile simd_pjmpback_sigill = NULL;
    
    // 处理SIGILL信号.
    static void simd_catch_sigill(int sig)
    {
        jmp_buf * pjmp = simd_pjmpback_sigill;
        // 能够跳回.
        if (NULL!=pjmp)
        {
            longjmp(*pjmp, 1);    // 跳回.
        }
        // 不能跳回.
        //fprintf(stderr, "!SIGILL!");
        abort();
    }
    
    // 尝试调用一个可能会发生SIGILL信号的函数.
    //
    // result: 若正常运行,就返回pfunc的返回值. 否则返回0.
    // pfunc: 欲测试的函数.
    // puserdata: 用户自定参数, 在调用pfunc时会传递该参数.
    static int simd_try_sigill(int (*pfunc)(void*), void* puserdata)
    {
        int rt = 0;
        jmp_buf myjmp;
        jmp_buf* poldjmp=NULL;    // 以前的跳回地址.
        void (*old_signal)(int) = SIG_DFL;    // 以前的信号处理函数.
    
        // 注册跳转.
        if (0==setjmp(myjmp))
        {
            // 登记跳回.
            poldjmp = simd_pjmpback_sigill;
            simd_pjmpback_sigill = &myjmp;
    
            // 注册信号处理函数.
            old_signal = signal(SIGILL, simd_catch_sigill);
    
            // [try]
            rt = pfunc(puserdata);
        }
        else
        {
            // [catch]
        }
    
        // 恢复信号处理函数.
        simd_pjmpback_sigill = poldjmp;
        signal(SIGILL, old_signal);
    
        return rt;
    }

      验证MMX指令是否能运行——

    // 验证mmx指令是否能运行_实际指令测试.
    static int    simd_try_mmx_pfunc(void* puserdata)
    {
        #if defined(_M_X64) && defined(_MSC_VER) && !defined(__INTEL_COMPILER)
            // VC编译器不支持64位下的MMX.
            return 0;
        #else
            _mm_empty();    // MMX instruction: emms
            return 1;
        #endif    // #if defined(_M_X64) && defined(_MSC_VER)
    }
    
    // 验证mmx指令是否能运行.
    static int    simd_try_mmx()
    {
        int rt = 0;
        #if defined(_M_X64) && defined(_MSC_VER) && !defined(__INTEL_COMPILER)
            // VC编译器不支持64位下的MMX.
        #else
            rt = simd_try_sigill(simd_try_mmx_pfunc, 0);
        #endif    // #if defined(_M_X64) && defined(_MSC_VER)
        return rt;
    }

      验证SSE指令是否能运行——

    // 验证sse指令是否能运行_实际指令测试.
    static int    simd_try_sse_pfunc(void* puserdata)
    {
        int rt = 0;
        volatile __m128 xmm1 = _mm_setzero_ps();    // SSE instruction: xorps
        int* pxmm1 = (int*)&xmm1;    // 避免GCC的 -Wstrict-aliasing 警告.
        if (0==*pxmm1)    rt = 1;    // 避免Release模式编译优化时剔除_mm_setzero_ps.
        return rt;
    }
    
    // 验证sse指令是否能运行.
    static int    simd_try_sse()
    {
        int rt = simd_try_sigill(simd_try_sse_pfunc, 0);
        return rt;
    }

      然后再修改原来的simd_mmx、simd_sse_level函数,在最后调用simd_try_mmx、simd_try_sse验证当前环境——

    // 是否支持MMX指令集.
    //
    // result: 返回当前运行环境是否支持MMX指令集. 非0表示支持, 0表示不支持.
    // phwmmx: 返回硬件是否支持MMX指令集. 非0表示支持, 0表示不支持.
    INLINE int    simd_mmx(int* phwmmx)
    {
        int    rt = 0;    // result
        #ifdef CCPUID_X86
            const uint32_t    BIT_D_MMX = 0x00800000;    // bit 23
            uint32_t dwBuf[4];
    
            // check processor support
            getcpuid(dwBuf, 1);    // Function 1: Feature Information
            if ( dwBuf[3] & BIT_D_MMX )    rt=1;
            if (NULL!=phwmmx)    *phwmmx=rt;
    
            // check OS support
            if ( rt>0 )
            {
                if (!simd_try_mmx()) rt=0;
            }
        #else    // #ifdef CCPUID_X86
            if (NULL!=phwmmx)    *phwmmx=rt;
        #endif    // #ifdef CCPUID_X86
        return rt;
    }
    
    // 检测SSE系列指令集的支持级别.
    //
    // result: 返回当前运行环境的SSE系列指令集支持级别. 详见SIMD_SSE_常数.
    // phwmmx: 返回硬件的SSE系列指令集支持级别. 详见SIMD_SSE_常数.
    INLINE int    simd_sse_level(int* phwsse)
    {
        int    rt = SIMD_SSE_NONE;    // result
        #ifdef CCPUID_X86
            const uint32_t    BIT_D_SSE = 0x02000000;    // bit 25
            const uint32_t    BIT_D_SSE2 = 0x04000000;    // bit 26
            const uint32_t    BIT_C_SSE3 = 0x00000001;    // bit 0
            const uint32_t    BIT_C_SSSE3 = 0x00000100;    // bit 9
            const uint32_t    BIT_C_SSE41 = 0x00080000;    // bit 19
            const uint32_t    BIT_C_SSE42 = 0x00100000;    // bit 20
            uint32_t dwBuf[4];
    
            // check processor support
            getcpuid(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
            if ( rt>0 )
            {
                if (!simd_try_sse()) rt=SIMD_SSE_NONE;
            }
        #else    // #ifdef CCPUID_X86
            if (NULL!=phwmmx)    *phwmmx=rt;
        #endif    // #ifdef CCPUID_X86
        return rt;
    }


    1.1.4 具体实现2(修改ccpuid.hpp、ccpuid.cpp)

      再考虑一下如何修改CCPUID类。原先是在调用RefreshProperty函数时,就一起更新_mmx、_sse等变量。而现在simd_try_mmx、simd_try_sse等验证函数不是线程安全的,如果还是一起更新的话,恐怕会影响稳定性。
      所以设计为在需要时才验证的模式更好。具体做法是,simd_mmx、simd_sse_level返回-1,由mmx、sse负责检查。

      mmx、sse原先设计为 const成员函数,而现在需要保存验证结果而修改_mmx、_sse变量,该怎么办呢?有两种方案——
    1. 去掉该函数的const关键字,使其成为普通成员函数。
    2. 将_mmx、_sse变量加上mutable关键字,使其能在 const成员函数 中修改。

      考虑到旧代码的兼容性,我选择了方案2。

      修改ccpuid.hpp:给_mmx、_sse变量加上mutable关键字,将mmx、sse函数挪至cpp文件中——

        int mmx() const;    // 系统支持MMX.
        int sse() const;    // 系统支持SSE.
    ...
        mutable int _mmx;    // 系统支持MMX.
        mutable int _sse;    // 系统支持SSE.



      修改ccpuid.cpp:simd_mmx、simd_sse_level返回-1,由mmx、sse负责检查——

    int    CCPUID::simd_mmx(int* phwmmx) const
    {
        int    rt = 0;    // result
        #ifdef CCPUID_X86
            const uint32_t    BIT_D_MMX = 0x00800000;    // bit 23
            uint32_t dwBuf[4];
    
            // check processor support
            GetData(dwBuf, 1);    // Function 1: Feature Information
            if ( dwBuf[3] & BIT_D_MMX )    rt=1;
            if (NULL!=phwmmx)    *phwmmx=rt;
    
            // check OS support
            rt = -1;    // 需要时才检查, 见mmx().
        #else    // #ifdef CCPUID_X86
            if (NULL!=phwmmx)    *phwmmx=rt;
        #endif    // #ifdef CCPUID_X86
        return rt;
    }
    
    int    CCPUID::simd_sse_level(int* phwsse) const
    {
        int    rt = SIMD_SSE_NONE;    // result
        #ifdef CCPUID_X86
            const uint32_t    BIT_D_SSE = 0x02000000;    // bit 25
            const uint32_t    BIT_D_SSE2 = 0x04000000;    // bit 26
            const uint32_t    BIT_C_SSE3 = 0x00000001;    // bit 0
            const uint32_t    BIT_C_SSSE3 = 0x00000100;    // bit 9
            const uint32_t    BIT_C_SSE41 = 0x00080000;    // bit 19
            const uint32_t    BIT_C_SSE42 = 0x00100000;    // bit 20
            uint32_t dwBuf[4];
    
            // check processor support
            GetData(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
            rt = -1;    // 需要时才检查, 见sse().
        #else    // #ifdef CCPUID_X86
            if (NULL!=phwsse)    *phwsse=rt;
        #endif    // #ifdef CCPUID_X86
        return rt;
    }
    
    ...
    
    
    int CCPUID::mmx() const
    {
        if (_mmx<0)
        {
            // 发现未检查, 进行检查.
            _mmx = _hwmmx;
            if (_mmx>0)
            {
                if (!simd_try_mmx()) _mmx=0;
            }
        }
        return _mmx;
    }
    
    int CCPUID::sse() const
    {
        if (_sse<0)
        {
            // 发现未检查, 进行检查.
            _sse = _hwsse;
            if (_sse>0)
            {
                if (!simd_try_sse()) _sse=SIMD_SSE_NONE;
            }
        }
        return _sse;
    }

    1.2 修正AVX检查Bug

      V1.02版因X86平台判断功能改动了AVX检查代码,不小心留下了一个Bug——当操作系统不支持OSXSAVE时,simd_avx_level函数却返回一个正数,报告当前环境支持AVX。而按照规定,它应该返回0。

      经过检查,发现是条件判断覆盖问题。于是修改了 simd_avx_level、CCPUID::simd_avx_level,修正了此Bug。


    1.3 增加多文件链接ccpuid的测试例程

      因SIGILL信号处理,现在ccpuid.h这个头文件中不得不含有定义性代码。一般来说,当头文件中含有定义性代码时,有时会造成多文件链接时报错。
      虽然我们已经加上了static关键字使那部分代码变为仅文件内可见、不参与链接。但是为了验证,还是编写一个测试例程比较好。

      于是在C++测试例程项目中 增加两个文件——test2.h和test2.cpp。
      test2.h用于声明test2函数——
    void test2(void);

      在test2.cpp中也引用了ccpuid.hpp,并写了简单的测试代码——

    #include <stdio.h>
    
    #include "ccpuid.hpp"
    
    #include "test2.h"
    
    // 测试调用 const对象 的 const成员函数.
    void test2_const(const CCPUID& ccid)
    {
        printf("test2_SSE:\t%d\n",ccid.sse());
    }
    
    // 测试多文件链接使用ccpuid.
    void test2(void)
    {
        test2_const(CCPUID::cur());
    }

      最后在testccpuid.cpp中添加对test2函数的调用——

    #include "test2.h"
    ...
    test2();

    二、全部代码

    (略)

    2.1 ccpuid模块

    2.1.1 ccpuid.h: CPUID信息(纯C版)

      全部代码——


    2.1.2 ccpuid.hpp: CPUID信息(C++版头文件)

      全部代码——


    2.1.3 ccpuid.cpp: CPUID信息(C++版源文件)

      全部代码——


    2.2 纯C版测试例程

    2.2.1 testccpuidc.c : [C] 测试ccpuid.h, 显示CPUID信息

      全部代码——


    2.3 C++版测试例程

    2.3.1 testccpuid.cpp : [C++] 测试ccpuid.hpp, 显示所有的CPUID信息

      全部代码——


    2.3.2 test2.h: 多文件链接ccpuid的测试(头文件)

      全部代码——


    2.3.3 test2.cpp: 多文件链接ccpuid的测试(源文件)

      全部代码——


    2.4 makefile

      makefile——


    三、编译测试

      在以下编译器中成功编译——
    VC6:x86版。
    VC2003:x86版。
    VC2005:x86版。
    VC2010:x86版、x64版。
    GCC 4.7.0(Fedora 17 x64):x86版、x64版。
    GCC 4.6.2(MinGW (20120426)):x86版。
    GCC 4.7.1(TDM-GCC (MinGW-w64)):x86版、x64版。
    llvm-gcc-4.2(Mac OS X Lion 10.7.4, Xcode 4.4.1):x86版、x64版。

    参考文献——
    《Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B, and 3C》044US. August 2012. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
    《Intel® Architecture Instruction Set Extensions Programming Reference》014. AUGUST 2012. http://software.intel.com/en-us/avx/
    《Intel® Processor Identification and the CPUID Instruction》. May 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
    《x86 architecture CPUID》. http://www.sandpile.org/x86/cpuid.htm
    《Haswell New Instruction Descriptions Now Available! 》. Mark Buxton. http://software.intel.com/en-us/blogs/2011/06/13/haswell-new-instruction-descriptions-now-available/
    [IDF2012]ARCS002《Introduction to the upcoming Intel® Advanced Vector Extensions 2 (Intel® AVX2)》. 王有伟, Henry Ou. 2012-4.
    [IDF2012]ARCS002《即将推出的英特尔® 高级矢量扩展指令集2(英特尔® AVX2)介绍》. 王有伟, Henry Ou. 2012-4.
    《x86/x64 指令系统》. mik(邓志). http://www.mouseos.com/x64/default.html
    《[x86]SIMD指令集发展历程表(MMX、SSE、AVX等)》. http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html
    《如何在各个版本的VC及64位下使用CPUID指令》. http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html
    《[VC兼容32位和64位] 检查MMX和SSE系列指令集的支持级别》. http://www.cnblogs.com/zyl910/archive/2012/05/25/checksimd64.html
    《[VC] CPUIDFIELD:CPUID字段的统一编号、读取方案。范例:检查SSE4A、AES、PCLMULQDQ指令》. http://www.cnblogs.com/zyl910/archive/2012/06/29/getcpuidfield.html
    《[VC] 检测AVX系列指令集的支持级别(AVX、AVX2、F16C、FMA、FMA4、XOP)》. http://www.cnblogs.com/zyl910/archive/2012/07/04/checkavx.html
    《[C#] cmdarg_ui:“简单参数命令行程序”的通用图形界面》.  http://www.cnblogs.com/zyl910/archive/2012/06/19/cmdarg_ui.html
    《[C/C++] 显示各种C/C++编译器的预定义宏(C11标准、C++11标准、VC、BCB、Intel、GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/02/printmacro.html
    《[C] 让VC、BCB支持C99的整数类型(stdint.h、inttypes.h)(兼容GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/08/c99int.html
    《GCC 64位程序的makefile条件编译心得——32位版与64位版、debug版与release版(兼容MinGW、TDM-GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/14/gcc64_make.html
    《[C] 在GCC中获取CPUID信息(兼容VC)》. http://www.cnblogs.com/zyl910/archive/2012/08/06/getcpuid_gcc.html
    《ccpuid:CPUID信息模块。范例:显示所有的CPUID信息》. http://www.cnblogs.com/zyl910/archive/2012/07/11/ccpuid.html
    《ccpuid:CPUID信息模块 V1.01版,支持GCC(兼容32位或64位的Windows/Linux)》. http://www.cnblogs.com/zyl910/archive/2012/08/22/ccpuid_v101.html
    《ccpuid:CPUID信息模块 V1.02版,支持Mac OS X,支持纯C,增加CPUF常数》. http://www.cnblogs.com/zyl910/archive/2012/09/29/ccpuid_v102.html


    源码下载——
    https://files.cnblogs.com/zyl910/ccpuid_v103.rar

    作者:zyl910
    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.
  • 相关阅读:
    vue 手动挂载 $amount()
    Redis 主从配置
    DMA分区管理
    C# 构造函数里的base和this的区别
    SQL Server 数据库性能优化
    TCP和UDP的优缺点及区别
    Django框架初步应用简述
    前端vue框架应用雏形
    接口mock之moco
    python进阶(九)~~~协程、进程池、线程/进程/协程对比
  • 原文地址:https://www.cnblogs.com/zyl910/p/ccpuid_v103.html
Copyright © 2020-2023  润新知