1

With GCC 9.4.0, the code

static inline double k(double const *restrict a) {
    return a[-1] + a[0] + a[1];
}

static void f(double const *restrict a, double *restrict b, int n) {
    for (int i = 0; i < n; ++i) {
        b[i] = k(a);
    }
}

static inline void swap(double **a, double **b) {
    double *tmp = *a;
    *a = *b;
    *b = tmp;
}

void g(double * a, double * b, int n, int nt) {
    for (int t = 0; t < nt; ++t) {
        f(a, b, n);
        swap(&a, &b);
    }
}

results in loop versioned for vectorization because of possible aliasing for the loop in f when compiled using gcc -march=native -Ofast -fopt-info-vec, suggesting that the restrict qualifiers in f are being ignored.

If I instead manually inline k into f:

static void f(double const *restrict a, double *restrict b, int n) {
    for (int i = 0; i < n; ++i) {
        b[i] = a[-1] + a[0] + a[1];
    }
}

the loop is vectorized without this versioning.

In both cases GCC reports that all of the functions are automatically inlined into each other.

The same occurs if I instead use __restrict and compile with g++.

Why does this happen? Is there a cleanly portable (using standard language features) way of causing GCC to respect the restrict qualifiers in the first case so that I can benefit from vectorization without versioning, without separating f and g into different translation units?

user3708067
  • 543
  • 5
  • 12
  • 2
    `restrict` is not a C++ [keyword](https://en.cppreference.com/w/cpp/keyword) so either the code is `C` or a makes use of a GCC extension. If `C` please remove the `C++` tag. – Richard Critten Jun 27 '22 at 09:49
  • 3
    @RichardCritten I think OP knows this. Note the paragraph stating: *The same occurs if I instead use __restrict and compile with g++.* – Adrian Mole Jun 27 '22 at 09:50
  • While I prefer only "using standard language features", I make an exception for `__restrict` when using C++ because it is almost universally supported by compilers. – user3708067 Jun 27 '22 at 10:03
  • there's no `restrict` in `g()` so formally speaking I think `g()` can't call `f()` safely. Maybe that makes the compiler ignoring the qualifiers – Ingo Leonhardt Jun 27 '22 at 10:11
  • @IngoLeonhardt Isn't that similar to saying that `g` can't call `f` because `g` doesn't have a `const` qualifier? The code asserts that within `f` the `restrict` and `const` qualifiers apply. I think that is valid. – user3708067 Jun 27 '22 at 10:22
  • I don't think so. The signatur of `g()` doesn't forbid calling `g(a,a,...)` but then `f(a,a,...)` would be called violating `restrict`. It would be similar if `g()` had `const` qualifiers but `f()` hadn't (which would emit a warning). – Ingo Leonhardt Jun 27 '22 at 10:55
  • @IngoLeonhardt Good point, but in this case I think it might be valid for `restrict`/`__restrict` to also be applied in `g` and `swap` since the object values are not accessed in the pointer swap. When I do that, the result is the same (versioning unless I manually inline `k`). All of this also doesn't explain why there is no versioning when I manually inline `k`. I checked with the latest version of GCC on godbolt.org and the outcome is still the same. – user3708067 Jun 27 '22 at 11:36
  • In fact it doesn't, if adding `restrict` doesn't change the compiler's behaviour. I just hoped that maybe it would – Ingo Leonhardt Jun 27 '22 at 11:55
  • I suspect that the obscure pointer swap blocks inlining, because you are constantly changing the pointers and so it might not be able to inline `f` because the pointers on the caller-side don't fulfil the criteria for `restrict`. If you want performance, then consider a different algorithm. – Lundin Jun 27 '22 at 13:52

0 Answers0