Vector overload of a function (provide a manually vectorized version of a function for auto-vectorization to use)

Viewed 41

I am using C, and I want to have two versions of the same function, a scalar version and a vector version. The two functions the same signature, and the compiler should pick the correct version depending on the context - if the context is autovectorized loop, it should pick an vectorized function, otherwise it should pick the scalar version.

How to achieve this, so it works in both GCC and CLANG?

One of the suggestions is to use pragma omp declare simd ..., but this doesn't work for me because I need different implementation for the two version (vectorized version is implemented using vector intrinsics).

1 Answers

Let' say we have a function int square(int num) { return num*num; } and we want to have an explicit manually vectorized version.

We compile with -fopenmp-simd, -mavx2 and -O3 compilation flags. This enables SIMD extensions and enabled vectorization.

In the first compilation unit we have something like this:

#pragma omp declare simd notinbranch
int square(int num);

...
#pragma omp simd
for (int i = 0; i < SIZE; i++) {
    res[i] = square(values[i]);
}

The compiler knows there is a vectorized version of square in another compilation unit.

In another compilation unit, we define square, both its scalar and vector counterparts. The scalar counterpart has a simple name square, whereas the vector counterparts use name mangling as described here.

In the other compilation unit we define:

#include <stdio.h>

int square(int num) {
    printf("1");
    return num * num;
}

#include <immintrin.h>

__m256i  _ZGVdN8v_square(__m256i num) {
    printf("2");
    return num;
}

__m128i _ZGVcN4v_square(__m128i num) {
    printf("3");
    return num;
}

The first function square is the scalar version, the second _ZGVdN8v_square is a version that processes 8 integers in one call, and the third version _ZGVcN4v_square processes 4 integers in one call.

For this example, name mangling goes like this:

  • _ZGV is the vector prefix
  • d is AVX2 isa, c is AVX isa
  • N is the unmasked version (corresponds to notinbranch in square declaration)
  • 4 and 8 are vector lengths *v stands for vector parameter

I tested it with GCC and it works.

Related