作者:zyl910。
现在很多编译器支持intrinsic函数,这给编写SSE等SIMD代码带来了方便。但是各个编译器略有差异,于是我编写了zintrin.h,智能引入intrinsic函数。
一、各种编译器的区别
1.1 Visual C++(Windows)
最早支持intrinsic函数的VC编译器是VC 6.0。它在装上Visual Studio 6.0 Service Pack 5、Visual C++ 6.0 Processor Pack这两个补丁后,便提供了mmintrin.h、mm3dnow.h、xmmintrin.h、emmintrin.h,用于支持MMX、3DNow!、SSE、SSE2的intrinsic函数。
从VC开始,提供了intrin.h,用于引入所有的intrinsic函数。
详见——
/zyl910/archive//02/28/vs_intrin_table.html
Intrinsics头文件与SIMD指令集、Visual Studio版本对应表
如果希望得知当前编译环境是否支持某种intrinsic函数,只能利用_MSC_VER判断VC的版本 来间接确认。
而且实际过程中会发现一些兼容性小问题,例如——VC不支持x64下的MMX指令、VC之前没有_mm_cvtss_f32 等。
1.2 GCC(Linux下的GCC、Windows下的MinGW)
gcc也支持intrinsic函数。例如在Fedora 17中,“/usr/lib/gcc/i686-redhat-linux/4.7.0/include/”目录下有Intrinsics头文件。而对于Windows中的MinGW,Intrinsics头文件是在MinGW的“\lib\gcc\mingw32\4.6.2\include”子目录中。
详见——
/zyl910/archive//08/27/intrin_table_gcc.html
GCC中的Intrinsics头文件与SIMD指令集、宏、参数的对应表
gcc允许通过命令行参数来控制是否打开某种指令集的支持,例如“-mmmx”用于打开MMX支持。可在终端中执行“gcc --target-help”,得到详细的参数列表。
当通过命令行参数打开指令集支持后,gcc会自动定义对应的预处理宏。例如用“-mmmx”打开MMX支持后,gcc会自动定义“__MMX__”这个预定义宏。这一类的预定义宏有——
__MMX__
__3dNOW__
__SSE__
__SSE2__
__SSE3__
__SSSE3__
__SSE4_1__
__SSE4_2__
__SSE4A__
__AES__
__PCLMUL__
__AVX__
__AVX2__
__F16C__
__FMA__
__FMA4__
__XOP__
__LWP__
__RDRND__
__FSGSBASE__
__LZCNT__
__POPCNT__
__BMI__
__BMI2__
__TBM__
gcc用于引入所有x86平台intrinsic函数的头文件是“x86intrin.h”,它会根据那些指令集预定义宏来引入相关的intrinsic函数。例如有“__MMX__”宏时,x86intrin.h会引入MMX的intrinsic函数。
1.3 Mac OS X 中的 llvm-gcc
我在Mac OS X系统中找了很久,貌似它不支持intrinsic函数。详细版本是——
操作系统:Mac OS X Lion 10.7.4(11E53)
编程工具:Xcode 4.4.1(1448),并装好了它的“Command Line Tools”。
__llvm__这个预定义宏可用来判断是不是llvm-gcc。
二、设计
2.1 思路
不同的编译器引入intrinsic函数的办法——
对于VS之前的版本,只能手动逐个逐个的包含emmintrin.h、mm3dnow.h;
对于VS之后的版本,可以利用intrin.h引入所有intrinsic函数;
对于GCC,首先应该判断__llvm__宏来排除llvm-gcc,然后利用x86intrin.h引入所有intrinsic函数。
这样做太麻烦了,我想编写一个头文件智能引入intrinsic函数。这就是zintrin.h。
其次——
VC中,没有直接判断是否支持某种intrinsic函数的办法,只能利用_MSC_VER判断VC的版本 来间接确认。而且还有x64环境下不支持MMX等特殊情况。
对于GCC,虽然可以利用__MMX__等宏判断是否打开了指令集支持,但这并不代表支持intrinsic函数。例如Mac OS X 中的 llvm-gcc,默认打开了__MMX__、__SSE__、__SSE2__,但它不支持intrinsic函数。
于是我想,如果有一种统一的方式判断当前编译环境是否支持某种intrinsic函数的办法就好了。
2.2 功能说明
功能——
1. 引入了编译器支持的所有intrinsic函数.
2. 提供了 INTRIN_MMX 等一系列宏用于判断当前编译环境是否支持该intrin函数.
3. 兼容性补丁. 例如 _mm_cvtss_f32 等.
用于判断当前编译环境是否支持该intrin函数的宏:
INTRIN_MMX
INTRIN_3dNOW
INTRIN_SSE
INTRIN_SSE2
INTRIN_SSE3
INTRIN_SSSE3
INTRIN_SSE4_1
INTRIN_SSE4_2
INTRIN_SSE4A
INTRIN_AES
INTRIN_PCLMUL
INTRIN_AVX
INTRIN_AVX2
INTRIN_F16C
INTRIN_FMA
INTRIN_FMA4
INTRIN_XOP
INTRIN_LWP
INTRIN_RDRND
INTRIN_FSGSBASE
INTRIN_LZCNT
INTRIN_POPCNT
INTRIN_BMI
INTRIN_BMI2
INTRIN_TBM
三、源码
3.1 zintrin.h
全部代码——
#ifndef __ZINTRIN_H_INCLUDED#define __ZINTRIN_H_INCLUDED// 根据不同的编译器做不同的处理.#if defined(__GNUC__)// GCC#if (defined(__i386__) || defined(__x86_64__) ) && !defined(__llvm__)// Mac OS X 的 llvm 不支持 intrin 函数.// header files#include <x86intrin.h>#include <cpuid.h>// macros#ifdef __MMX__#define INTRIN_MMX1#endif#ifdef __3dNOW__#define INTRIN_3dNOW1#endif#ifdef __SSE__#define INTRIN_SSE1#endif#ifdef __SSE2__#define INTRIN_SSE21#endif#ifdef __SSE3__#define INTRIN_SSE31#endif#ifdef __SSSE3__#define INTRIN_SSSE31#endif#ifdef __SSE4_1__#define INTRIN_SSE4_11#endif#ifdef __SSE4_2__#define INTRIN_SSE4_21#endif#ifdef __SSE4A__#define INTRIN_SSE4A1#endif#ifdef __AES__#define INTRIN_AES1#endif#ifdef __PCLMUL__#define INTRIN_PCLMUL1#endif#ifdef __AVX__#define INTRIN_AVX1#endif#ifdef __AVX2__#define INTRIN_AVX21#endif#ifdef __F16C__#define INTRIN_F16C1#endif#ifdef __FMA__#define INTRIN_FMA1#endif#ifdef __FMA4__#define INTRIN_FMA41#endif#ifdef __XOP__#define INTRIN_XOP1#endif#ifdef __LWP__#define INTRIN_LWP1#endif#ifdef __RDRND__#define INTRIN_RDRND1#endif#ifdef __FSGSBASE__#define INTRIN_FSGSBASE1#endif#ifdef __LZCNT__#define INTRIN_LZCNT1#endif#ifdef __POPCNT__#define INTRIN_POPCNT1#endif#ifdef __BMI__#define INTRIN_BMI1#endif#ifdef __BMI2__#define INTRIN_BMI21#endif#ifdef __TBM__#define INTRIN_TBM1#endif#endif// #if !defined(__llvm__)#elif defined(_MSC_VER)// MSVC// header files#if _MSC_VER >=1400// VC#include <intrin.h>#elif _MSC_VER >=1200// VC6#if (defined(_M_IX86) || defined(_M_X64))#include <emmintrin.h>// MMX, SSE, SSE2#include <mm3dnow.h>// 3DNow!#endif#endif// #if _MSC_VER >=1400#include <malloc.h>// _mm_malloc, _mm_free.// macros#if (defined(_M_IX86) || defined(_M_X64))#if _MSC_VER >=1200// VC6#if defined(_M_X64) && !defined(__INTEL_COMPILER)// VC编译器不支持64位下的MMX.#else#define INTRIN_MMX1// mmintrin.h#define INTRIN_3dNOW1// mm3dnow.h#endif#define INTRIN_SSE1// xmmintrin.h#define INTRIN_SSE21// emmintrin.h#endif#if _MSC_VER >=1300// VC#endif#if _MSC_VER >=1400// VC#endif#if _MSC_VER >=1500// VC#define INTRIN_SSE31// pmmintrin.h#define INTRIN_SSSE31// tmmintrin.h#define INTRIN_SSE4_11// smmintrin.h#define INTRIN_SSE4_21// nmmintrin.h#define INTRIN_SSE4A1// intrin.h#define INTRIN_LZCNT1// intrin.h#define INTRIN_POPCNT1// nmmintrin.h#endif#if _MSC_VER >=1600// VC#define INTRIN_AES1// wmmintrin.h#define INTRIN_PCLMUL1// wmmintrin.h#define INTRIN_AVX1// immintrin.h#define INTRIN_FMA41// ammintrin.h#define INTRIN_XOP1// ammintrin.h#define INTRIN_LWP1// ammintrin.h#endif#if _MSC_VER >=1700// VC#define INTRIN_AVX20//TODO:待查证. 先设为0.#define INTRIN_FMA0#define INTRIN_F16C0#define INTRIN_RDRND0#define INTRIN_FSGSBASE0#define INTRIN_BMI0#define INTRIN_BMI20#define INTRIN_TBM0#endif#endif//TODO:待查证 VS配合intel C编译器时intrin函数的支持性.// VC之前没有_mm_cvtss_f32#if _MSC_VER <1500// VC// float _mm_cvtss_f32(__m128 _A);#ifndef _mm_cvtss_f32#define _mm_cvtss_f32(__m128_A) ( *(float*)(void*)&(__m128_A) )#endif#endif#else//#error Only supports GCC or MSVC.#endif// #if defined(__GNUC__)#endif// #ifndef __ZINTRIN_H_INCLUDED
3.2 testzintrin.c
全部代码——
#include <stdio.h>#include "zintrin.h"#define PT_MAKE_STR(x){ #x, PT_MAKE_STR_ESC(x) }#define PT_MAKE_STR_ESC(x)#xtypedef struct tagMACRO_T{const char *name;const char *value;} MACRO_T;/* Intrinsics */const MACRO_T g_intrins[] ={{"[Intrinsics]", ""},#ifdef INTRIN_MMXPT_MAKE_STR(INTRIN_MMX),#endif#ifdef INTRIN_3dNOWPT_MAKE_STR(INTRIN_3dNOW),#endif#ifdef INTRIN_SSEPT_MAKE_STR(INTRIN_SSE),#endif#ifdef INTRIN_SSE2PT_MAKE_STR(INTRIN_SSE2),#endif#ifdef INTRIN_SSE3PT_MAKE_STR(INTRIN_SSE3),#endif#ifdef INTRIN_SSSE3PT_MAKE_STR(INTRIN_SSSE3),#endif#ifdef INTRIN_SSE4_1PT_MAKE_STR(INTRIN_SSE4_1),#endif#ifdef INTRIN_SSE4_2PT_MAKE_STR(INTRIN_SSE4_2),#endif#ifdef INTRIN_SSE4APT_MAKE_STR(INTRIN_SSE4A),#endif#ifdef INTRIN_AESPT_MAKE_STR(INTRIN_AES),#endif#ifdef INTRIN_PCLMULPT_MAKE_STR(INTRIN_PCLMUL),#endif#ifdef INTRIN_AVXPT_MAKE_STR(INTRIN_AVX),#endif#ifdef INTRIN_AVX2PT_MAKE_STR(INTRIN_AVX2),#endif#ifdef INTRIN_F16CPT_MAKE_STR(INTRIN_F16C),#endif#ifdef INTRIN_FMAPT_MAKE_STR(INTRIN_FMA),#endif#ifdef INTRIN_FMA4PT_MAKE_STR(INTRIN_FMA4),#endif#ifdef INTRIN_XOPPT_MAKE_STR(INTRIN_XOP),#endif#ifdef INTRIN_LWPPT_MAKE_STR(INTRIN_LWP),#endif#ifdef INTRIN_RDRNDPT_MAKE_STR(INTRIN_RDRND),#endif#ifdef INTRIN_FSGSBASEPT_MAKE_STR(INTRIN_FSGSBASE),#endif#ifdef INTRIN_LZCNTPT_MAKE_STR(INTRIN_LZCNT),#endif#ifdef INTRIN_POPCNTPT_MAKE_STR(INTRIN_POPCNT),#endif#ifdef INTRIN_BMIPT_MAKE_STR(INTRIN_BMI),#endif#ifdef INTRIN_BMI2PT_MAKE_STR(INTRIN_BMI2),#endif#ifdef INTRIN_TBMPT_MAKE_STR(INTRIN_TBM),#endif};// 获取程序位数(被编译为多少位的代码)int GetProgramBits(void){return sizeof(int*) * 8;}void print_MACRO_T(const MACRO_T* pArray, int cnt){int i;for( i = 0; i < cnt; ++i ){printf( "%s\t%s\n", pArray[i].name, pArray[i].value );}printf( "\n" );}int main(int argc, char* argv[]){printf("testzintrin v1.00 (%dbit)\n\n", GetProgramBits());print_MACRO_T(g_intrins, sizeof(g_intrins)/sizeof(g_intrins[0]));// _mm_malloc#ifdef INTRIN_SSEif(1){void* p;p = _mm_malloc(0x10, 0x10);printf("_mm_malloc:\t%ph\n", p);_mm_free(p);}#endif// mmx#ifdef INTRIN_MMX_mm_empty();#endif// 3DNow!#ifdef INTRIN_3dNOW//_m_femms();// AMD cpu only.#endif// sse#ifdef INTRIN_SSEif(1){__m128 xmm1;float f;printf("&xmm1:\t%ph\n", &xmm1);xmm1 = _mm_setzero_ps();// SSE instruction: xorpsf = _mm_cvtss_f32(xmm1);printf("_mm_cvtss_f32:\t%f\n", f);}#endif// popcnt#ifdef INTRIN_POPCNTprintf("popcnt(0xffffffffu):\t%u\n", _mm_popcnt_u32(0xffffffffu));#endifreturn 0;}
3.3 makefile
全部代码——
# flagsCC = gccCFS = -WallLFS = # argsRELEASE =0BITS =CFLAGS = -msse# [args] 生成模式. 0代表debug模式, 1代表release模式. make RELEASE=1.ifeq ($(RELEASE),0)# debugCFS += -gelse# releaseCFS += -static -O3 -DNDEBUGLFS += -staticendif# [args] 程序位数. 32代表32位程序, 64代表64位程序, 其他默认. make BITS=32.ifeq ($(BITS),32)CFS += -m32LFS += -m32elseifeq ($(BITS),64)CFS += -m64LFS += -m64elseendifendif# [args] 使用 CFLAGS 添加新的参数. make CFLAGS="-mpopcnt -msse4a".CFS += $(CFLAGS).PHONY : all clean# filesTARGETS = testzintrinOBJS = testzintrin.oall : $(TARGETS)testzintrin : $(OBJS)$(CC) $(LFS) -o $@ $^testzintrin.o : testzintrin.c zintrin.h$(CC) $(CFS) -c $<clean :rm -f $(OBJS) $(TARGETS) $(addsuffix .exe,$(TARGETS))
四、测试
在以下编译器中成功编译——
VC6:x86版。
VC:x86版。
VC:x86版、x64版。
VC:x86版、x64版。
GCC 4.7.0(Fedora 17 x64):x86版、x64版。
GCC 4.6.2(MinGW(0426)):x86版。
GCC 4.6.1(TDM-GCC(MinGW-w64)):x86版、x64版。
llvm-gcc-4.2(Mac OS X Lion 10.7.4, Xcode 4.4.1):x86版、x64版。
参考文献——
《Predefined Macros》. /en-us/library/b0084kay(v=vs.110).aspx
《Intrinsics头文件与SIMD指令集、Visual Studio版本对应表》. /zyl910/archive//02/28/vs_intrin_table.html
《GCC中的Intrinsics头文件与SIMD指令集、宏、参数的对应表》. /zyl910/archive//08/27/intrin_table_gcc.html
源码下载——
/zyl910/zintrin.rar