Search code examples
c++qtqmakesimdavx

Dispatching SIMD instructions + SIMDPP + qmake


I'm developing a QT widget that makes use of SIMD instruction sets. I've compiled 3 versions: SSE3, AVX, and AVX2(simdpp allows to switch between them by a single #define).

Now, what I want is for my widget to switch automatically between these implementations, according to best supported instruction set. Guide that is provided with simdpp makes use of some makefile magic:

CXXFLAGS=""

test: main.o test_sse2.o test_sse3.o test_sse4_1.o test_null.o
    g++ $^ -o test

main.o: main.cc
    g++ main.cc $(CXXFLAGS) -c -o main.o

test_null.o: test.cc
    g++ test.cc -c $(CXXFLAGS) -DSIMDPP_EMIT_DISPATCHER \
        -DSIMDPP_DISPATCH_ARCH1=SIMDPP_ARCH_X86_SSE2 \
        -DSIMDPP_DISPATCH_ARCH2=SIMDPP_ARCH_X86_SSE3 \
        -DSIMDPP_DISPATCH_ARCH3=SIMDPP_ARCH_X86_SSE4_1 -o test_null.o

test_sse2.o: test.cc
    g++ test.cc -c $(CXXFLAGS) -DSIMDPP_ARCH_X86_SSE2 -msse2 -o test_sse2.o

test_sse3.o: test.cc
    g++ test.cc -c $(CXXFLAGS) -DSIMDPP_ARCH_X86_SSE3 -msse3 -o test_sse3.o

test_sse4_1.o: test.cc
    g++ test.cc -c $(CXXFLAGS) -DSIMDPP_ARCH_X86_SSE4_1 -msse4.1 -o test_sse4_1.o

Here is a link to the guide: http://p12tic.github.io/libsimdpp/v2.0~rc2/libsimdpp/arch/dispatch.html

I have no idea how to implement such behavior with qmake. Any ideas?

First that comes to mind is to create a shared library with dispatched code, and link it to the project. Here I'm stuck again. App is cross-platform, which means it has to compile with both GCC and MSVC(vc120, to be exact), which forces using nmake in Windows, and I tried, really, but it was like the worst experience in my whole programmer life.

Thanks in advance, programmers of the world!


Solution

  • sorry if this is a bit late. Hope I can still help.

    You need to consider 2 areas: Compile time and run time.

    Compile time - need to create code to support different features. Run time - need to create code to decide which features you can run.

    What you are wanting to do is create a dispatcher...

    FuncImpl.h:

    #pragma once
    void execAvx2();
    void execAvx();
    void execSse();
    void execDefault();
    

    FuncImpl.cpp:

    // Compile this file once for each variant with different compiler settings.
    #if defined(__AVX2__)
    void execAvx2()
    {
     // AVX2 impl
    ...
    }
    
    #elif defined (__AVX__)
    
    void execAvx()
    {
    // AVX impl
    ...
    }
    
    #elif defined (__SSE4_2__)
    
    void execSse()
    {
     // Sse impl
    ...
    }
    
    #else
    
    void execDefault()
    {
     // Vanilla impl
    ...
    }
    
    #endif
    

    DispatchFunc.cpp

    #include "FuncImpl.h"
    
    // Decide at runtime which code to run
    void dispatchFunc()
    {
         if(CheckCpuAvx2Flag())
         {
             execAvx2();
         } 
         else if(CheckCpuAvxFlag())
         {
             execAvx();
         }
         else if(CheckCpuSseFlags())
         {
             execSse();
         }
         else
         {
             execDefault();
         }
    }
    

    What you can do is create a set of QMAKE_EXTRA_COMPILERS.

    SampleCompiler.pri (Do this for each variant):

    MyCompiler.name = MyCompiler         # Name
    MyCompiler.input = MY_SOURCES        # Symbol of the source list to compile
    MyCompiler.dependency_type = TYPE_C
    MyCompiler.variable_out = OBJECTS
    # EXTRA_CXXFLAGS = -mavx / -mavx2 / -msse4.2
    # _var = creates FileName_var.o => replace with own variant (_sse, etc)  
    MyCompiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}_var$${first(QMAKE_EXT_OBJ)}
    MyCompiler.commands = $${QMAKE_CXX} $(CXXFLAGS) $${EXTRA_CXXFLAGS} $(INCPATH) -c ${QMAKE_FILE_IN} -o${QMAKE_FILE_OUT}
    QMAKE_EXTRA_COMPILERS += MyCompiler   # Add my compiler
    

    MyProject.pro

    ...
    include(SseCompiler.pri)
    include(AvxCompiler.pri)
    include(Avx2Compiler.pri)
    ..
    
    # Normal sources
    # Will create FuncImpl.o and DispatchFunc.o
    SOURCES += FuncImpl.cpp \
               DispatchFunc.cpp
    
    # Give the other compilers their sources
    # Will create FuncImpl_avx2.o FuncImpl_avx.o FuncImpl_sse.o
    AVX2_SOURCES += FuncImpl.cpp
    AVX_SOURCES += FuncImpl.cpp
    SSE_SOURCES += FuncImpl.cpp
    
    # Link all objects
    ...
    

    All you need now is to call dispatchFunc()!

    Checking cpu flags is another exercise for you: cpuid