The simple and clean Option
The gcc target attribute can be used out of hand like so
[[gnu::target("avx")]]
void foo(){}
[[gnu::target("default")]]
void foo(){}
[[gnu::target("arch=sandybridge")]]
void foo(){}
the call then becomes
foo();
This option does away with the need to name a function differently.
If you check out godbolt for example you will see that it creates @gnu_indirect_function for you. set it first to a .resolver function. Which reads the __cpu_model to find out what can be used and set the indirect function to that pointer so any subsequent calls will be a simple function indirect.
simple aint it. But you might need to remain closer to you original code base therefore there are other ways
function switching
If you do need function switching like in your original example. the following can be used. Which uses nicely worded buildtins so its clear that you are switching on architecture
[[gnu::target("avx")]]
int foo_avx(){ return 1;}
[[gnu::target("default")]]
int foo(){return 0;}
[[gnu::target("arch=sandybridge")]]
int foo_sandy(){return 2;}
int main ()
{
if (__builtin_cpu_is("sandybridge"))
return foo_sandy();
else if (__builtin_cpu_supports("avx"))
return foo_avx();
else
return foo();
}
Define your own indirect function
Because of reasons to be more verbose to others or platforms concerns were indirect functions might not be a supported use case. Below is a way that does the same as the first option but all in c++ code. using a static local function pointer. This means you could order the priority for targets to your own liking or on cases were the build in isn't supported. You can supply your own.
auto foo()
{
using T = decltype(foo_default);
static T* pointer = nullptr;
//static int (*pointer)() = nullptr;
if (pointer == nullptr)
{
if (__builtin_cpu_is("sandybridge"))
pointer = &foo_sandy;
else if (__builtin_cpu_supports("avx"))
pointer = &foo_avx;
else
pointer = &foo_default;
}
return pointer();
};
As a bonus note
the following templated example on godbolt uses template<class ... Ts>
to deal with overloads of your functions
meaning if you define a family of callXXXVersion(int)
then foo(int) will happily call the overloaded version for you. as long as you defined the entire family.
void callAVXVersion() attribute(target("avx,no-avx2,no-sse")) { some implementation } void callAVX2Version() { attribute(target("avx2,no-sse")) { some other implementation }
. Never tried it tho, but I was always curios how it works. You can also#pragma gcc target avx2
or#pragma gcc target avx
. – ExcrementWhere exactly does the attribute go?
- near function definition or declaration.__attribute__((I put them here)) int __attribute__((you can put it here))__ func(int a, int b) __atttribute__((or here, does not matter)) {}
. The description is clear -use target attribute to specify that a function is to be compiled with different target options than specified on the command line
. – Excrement