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!
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