Pourquoi un en-tête unifié pour les intrinsèques ?
La gestion des foncsions intrinsèques (utilisées pour les instructions SIMD comme SSE) varie selon les compilateurs. Ce document présente zintrin.h, un fichier d'en-tête conçu pour unifier cette inclusion et offrir des macros de détection.
Disparités entre compilateurs
Visual C++ (Windows)
Visual C++ 6.0 a été le premier à supporter les intrinsèques après l'installation des Service Pack 5 et Processor Pack, avec les en-têtes mmintrin.h, mm3dnow.h, xmmintrin.h et emmintrin.h pour MMX, 3DNow!, SSE et SSE2. À partir de VC2005, intrin.h permet d'inclure toutes les fonctions. La détection se fait via la macro _MSC_VER, avec des limitations : absence de MMX en x64, et absence de _mm_cvtss_f32 avant VC2008.
GCC (Linux, MinGW)
GCC utilise des drapeaux comme -mmmx ou -msse pour activer les jeux d'instructions. L'en-tête x86intrin.h inclut automatiquement les fichiers nécessaires si les macros correspondantes (ex : __MMX__) sont définies. Sur MinGW, ces en-têtes se trouvent dans le répertoire d'installation. Pour obtenir la liste complète des options, exécutez :
gcc --target-help
Exemple de sortie tronquée :
$ gcc --target-help
...
-mmmx Support MMX built-in functions
-msse Support MMX and SSE built-in functions and code generation
-msse2 Support MMX, SSE and SSE2 built-in functions
-mavx Support MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 and AVX built-in functions
...
llvm-gcc (Mac OS X)
Sous Mac OS X Lion (10.7.4) avec Xcode 4.4.1, llvm-gcc ne supporte pas les fonctions intrinsèques malgré la définition de __MMX__, __SSE__ et __SSE2__. La macro __llvm__ permet d'identifier ce compilateur.
Conception de zintrin.h
Objectifs
- Inclure toutes les fonctions intrinsèques supportées par le compilateur.
- Fournir des macros de détection (ex :
INTRIN_SSE) pour vérifier la disponibilité. - Appliquer des correctifs de compatibilité (ex :
_mm_cvtss_f32pour VC antérieur à VC2008).
Macros de détection fournies
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
Mise en œuvre
zintrin.h
#ifndef __ZINTRIN_H_INCLUDED
#define __ZINTRIN_H_INCLUDED
#if defined(__GNUC__)
#if (defined(__i386__) || defined(__x86_64__)) && !defined(__llvm__)
#include <x86intrin.h>
#include <cpuid.h>
#ifdef __MMX__
#define INTRIN_MMX 1
#endif
#ifdef __3dNOW__
#define INTRIN_3dNOW 1
#endif
#ifdef __SSE__
#define INTRIN_SSE 1
#endif
#ifdef __SSE2__
#define INTRIN_SSE2 1
#endif
#ifdef __SSE3__
#define INTRIN_SSE3 1
#endif
#ifdef __SSSE3__
#define INTRIN_SSSE3 1
#endif
#ifdef __SSE4_1__
#define INTRIN_SSE4_1 1
#endif
#ifdef __SSE4_2__
#define INTRIN_SSE4_2 1
#endif
#ifdef __SSE4A__
#define INTRIN_SSE4A 1
#endif
#ifdef __AES__
#define INTRIN_AES 1
#endif
#ifdef __PCLMUL__
#define INTRIN_PCLMUL 1
#endif
#ifdef __AVX__
#define INTRIN_AVX 1
#endif
#ifdef __AVX2__
#define INTRIN_AVX2 1
#endif
#ifdef __F16C__
#define INTRIN_F16C 1
#endif
#ifdef __FMA__
#define INTRIN_FMA 1
#endif
#ifdef __FMA4__
#define INTRIN_FMA4 1
#endif
#ifdef __XOP__
#define INTRIN_XOP 1
#endif
#ifdef __LWP__
#define INTRIN_LWP 1
#endif
#ifdef __RDRND__
#define INTRIN_RDRND 1
#endif
#ifdef __FSGSBASE__
#define INTRIN_FSGSBASE 1
#endif
#ifdef __LZCNT__
#define INTRIN_LZCNT 1
#endif
#ifdef __POPCNT__
#define INTRIN_POPCNT 1
#endif
#ifdef __BMI__
#define INTRIN_BMI 1
#endif
#ifdef __BMI2__
#define INTRIN_BMI2 1
#endif
#ifdef __TBM__
#define INTRIN_TBM 1
#endif
#endif
#elif defined(_MSC_VER)
#if _MSC_VER >= 1400
#include <intrin.h>
#elif _MSC_VER >= 1200
#if defined(_M_IX86) || defined(_M_X64)
#include <emmintrin.h>
#include <mm3dnow.h>
#endif
#endif
#include <malloc.h>
#if defined(_M_IX86) || defined(_M_X64)
#if _MSC_VER >= 1200
#if !(defined(_M_X64) && !defined(__INTEL_COMPILER))
#define INTRIN_MMX 1
#define INTRIN_3dNOW 1
#endif
#define INTRIN_SSE 1
#define INTRIN_SSE2 1
#endif
#if _MSC_VER >= 1500
#define INTRIN_SSE3 1
#define INTRIN_SSSE3 1
#define INTRIN_SSE4_1 1
#define INTRIN_SSE4_2 1
#define INTRIN_SSE4A 1
#define INTRIN_LZCNT 1
#define INTRIN_POPCNT 1
#endif
#if _MSC_VER >= 1600
#define INTRIN_AES 1
#define INTRIN_PCLMUL 1
#define INTRIN_AVX 1
#define INTRIN_FMA4 1
#define INTRIN_XOP 1
#define INTRIN_LWP 1
#endif
#if _MSC_VER >= 1700
// Valeurs à vérifier pour AVX2, FMA, F16C, RDRND, FSGSBASE, BMI, BMI2, TBM
#define INTRIN_AVX2 0
#define INTRIN_FMA 0
#define INTRIN_F16C 0
#define INTRIN_RDRND 0
#define INTRIN_FSGSBASE 0
#define INTRIN_BMI 0
#define INTRIN_BMI2 0
#define INTRIN_TBM 0
#endif
#endif
#if _MSC_VER < 1500
#ifndef _mm_cvtss_f32
#define _mm_cvtss_f32(__m128_A) ( *(float*)(void*)&(__m128_A) )
#endif
#endif
#endif
#endif
Exemple de test : 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) #x
typedef struct tagMACRO_T {
const char *name;
const char *value;
} MACRO_T;
const MACRO_T g_intrins[] = {
{"[Intrinsics]", ""},
#ifdef INTRIN_MMX PT_MAKE_STR(INTRIN_MMX), #endif
#ifdef INTRIN_3dNOW PT_MAKE_STR(INTRIN_3dNOW), #endif
#ifdef INTRIN_SSE PT_MAKE_STR(INTRIN_SSE), #endif
#ifdef INTRIN_SSE2 PT_MAKE_STR(INTRIN_SSE2), #endif
#ifdef INTRIN_SSE3 PT_MAKE_STR(INTRIN_SSE3), #endif
#ifdef INTRIN_SSSE3 PT_MAKE_STR(INTRIN_SSSE3), #endif
#ifdef INTRIN_SSE4_1 PT_MAKE_STR(INTRIN_SSE4_1), #endif
#ifdef INTRIN_SSE4_2 PT_MAKE_STR(INTRIN_SSE4_2), #endif
#ifdef INTRIN_SSE4A PT_MAKE_STR(INTRIN_SSE4A), #endif
#ifdef INTRIN_AES PT_MAKE_STR(INTRIN_AES), #endif
#ifdef INTRIN_PCLMUL PT_MAKE_STR(INTRIN_PCLMUL), #endif
#ifdef INTRIN_AVX PT_MAKE_STR(INTRIN_AVX), #endif
#ifdef INTRIN_AVX2 PT_MAKE_STR(INTRIN_AVX2), #endif
#ifdef INTRIN_F16C PT_MAKE_STR(INTRIN_F16C), #endif
#ifdef INTRIN_FMA PT_MAKE_STR(INTRIN_FMA), #endif
#ifdef INTRIN_FMA4 PT_MAKE_STR(INTRIN_FMA4), #endif
#ifdef INTRIN_XOP PT_MAKE_STR(INTRIN_XOP), #endif
#ifdef INTRIN_LWP PT_MAKE_STR(INTRIN_LWP), #endif
#ifdef INTRIN_RDRND PT_MAKE_STR(INTRIN_RDRND), #endif
#ifdef INTRIN_FSGSBASE PT_MAKE_STR(INTRIN_FSGSBASE), #endif
#ifdef INTRIN_LZCNT PT_MAKE_STR(INTRIN_LZCNT), #endif
#ifdef INTRIN_POPCNT PT_MAKE_STR(INTRIN_POPCNT), #endif
#ifdef INTRIN_BMI PT_MAKE_STR(INTRIN_BMI), #endif
#ifdef INTRIN_BMI2 PT_MAKE_STR(INTRIN_BMI2), #endif
#ifdef INTRIN_TBM PT_MAKE_STR(INTRIN_TBM), #endif
};
int main(void) {
printf("Test zintrin v1.00 (%d bits)\n", (int)(sizeof(void*) * 8));
for (size_t i = 0; i < sizeof(g_intrins)/sizeof(g_intrins[0]); i++)
printf("%s\t%s\n", g_intrins[i].name, g_intrins[i].value);
#ifdef INTRIN_SSE
__m128 v = _mm_setzero_ps();
printf("_mm_cvtss_f32: %f\n", _mm_cvtss_f32(v));
void* p = _mm_malloc(16, 16);
printf("_mm_malloc: %p\n", p);
_mm_free(p);
#endif
return 0;
}
Makefile (GCC)
CC = gcc
CFLAGS = -Wall -msse
LDFLAGS =
BITS ?=
RELEASE ?= 0
ifeq ($(RELEASE),0)
CFLAGS += -g
else
CFLAGS += -O3 -DNDEBUG
LDFLAGS += -static
endif
ifeq ($(BITS),32)
CFLAGS += -m32
LDFLAGS += -m32
else ifeq ($(BITS),64)
CFLAGS += -m64
LDFLAGS += -m64
endif
CFLAGS += $(EXTRA_CFLAGS)
all: testzintrin
testzintrin: testzintrin.o
$(CC) $(LDFLAGS) -o $@ $^
testzintrin.o: testzintrin.c zintrin.h
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o testzintrin testzintrin.exe
Tests et compatibilité
L'en-tête a été testé avec succès sur les environnements suivants :
- Visual C++ 6.0 (x86)
- Visual C++ 2003 (x86)
- Visual C++ 2005 (x86, x64)
- Visual C++ 2010 (x86, x64)
- GCC 4.7.0 (Fedora 17 x64 – x86 et x64)
- GCC 4.6.2 (MinGW 20120426 – 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)