zintrin.h : inclusion intelligente des fonctions intrinsèques pour VC, GCC et plateformes Windows, Linux, Mac OS X

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_f32 pour 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)

Étiquettes: zintrin.h fonctions intrinsèques SIMD SSE AVX

Publié le 20 juillet à 10h00