Clang vectorization

Hello cfe-users,

I’m trying to get clang (or GCC for that matter) to vectorize a very simple loop, and I’m wondering what I’m doing wrong. I’d rather write the loop as a loop instead of using intrinsics or the clang vector extensions, because I want the code to be portable. Pragmas and magic attributes are also undesirable, but they’re better than intrinsics.

This file is representative of what I’m trying to do. I’m compiling with -O3 -std=c99 -mavx2, but the same issues should apply for other vector settings.
“””
#include <stdint.h>

typedef struct this_should_totally_be_a_vector {
    uint64_t limb[8];
} __attribute__((aligned(32))) a_vector;

void add(a_vector *a, const a_vector *b) {
    for (int i=0; i<8; i++) a->limb[i] += b->limb[i];
}

void mac(a_vector *a, const a_vector *b) {
    const a_vector c = {{0,1,2,3,4,5,6,7}};
    for (int i=0; i<8; i++) a->limb[i] += b->limb[i] + 3*c.limb[i];
}
“””

Can someone suggest flags, pragmas, attributes etc which would cause these functions to produce good code? I’m seeing lots of problems. I’m testing for now on clang-3.6 release.

For starters, the compiler is unable to determine that there is no loop dependency, and therefore unrolls the loop instead of vectorizing. When passed #pragma clang loop unroll(disable) vectorize(enable), it is still not able to determine that there is no dependency, and so branches to a scalar version if a is close to b. Furthermore, it ignores the alignment hint and uses vmovdqu for everything, though maybe that doesn’t actually cost any performance. In fact, there cannot be a loop dependency both because of the alignment and because the arrays are in structs.

Clang produces the correct code if a is declared __restrict__, but in the real code it is possible that a=b so I’d rather not say __restrict__ if I don’t have to (especially since the code may be inlined, possibly causing alias analysis to break). GCC has #pragma GCC ivdep, which causes it to vectorize properly, but does Clang have any equivalent to #pragma ivdep? Also, __restrict__ still doesn’t give me vmovdqa.

For mac, with __restrict__ (again undesirable) I get decent 2-way vectorized sse3 code, which isn’t bad I guess, but I’d rather the compiler automatically produced 4-way avx2 code. If I add #pragma clang loop unroll(disable) vectorize(enable), I get
“”"
  vmovdqa mac.c(%rip), %ymm0
  vpbroadcastq .LCPI2_0(%rip), %ymm1
  vpmuludq %ymm1, %ymm0, %ymm2
  vpxor %ymm3, %ymm3, %ymm3
  vpmuludq %ymm3, %ymm0, %ymm4
  vpsllq $32, %ymm4, %ymm4
  vpaddq %ymm4, %ymm2, %ymm2
  vpsrlq $32, %ymm0, %ymm0
  vpmuludq %ymm1, %ymm0, %ymm0
  vpsllq $32, %ymm0, %ymm0
  vpaddq %ymm0, %ymm2, %ymm0
  vpaddq (%rsi), %ymm0, %ymm0
  vpaddq (%rdi), %ymm0, %ymm0
  vmovdqu %ymm0, (%rdi)
  vmovdqa mac.c+32(%rip), %ymm0
  vpmuludq %ymm1, %ymm0, %ymm2
  vpmuludq %ymm3, %ymm0, %ymm3
  vpsllq $32, %ymm3, %ymm3
  vpaddq %ymm3, %ymm2, %ymm2
  vpsrlq $32, %ymm0, %ymm0
  vpmuludq %ymm1, %ymm0, %ymm0
  vpsllq $32, %ymm0, %ymm0
  vpaddq %ymm0, %ymm2, %ymm0
  vpaddq 32(%rsi), %ymm0, %ymm0
  vpaddq 32(%rdi), %ymm0, %ymm0
  vmovdqu %ymm0, 32(%rdi)
  vzeroupper
  retq
“”"
In other words, clang has failed to propagate constants, and is trying to do 64-bit multiplies (lowered to vpsllq and vpmuludq) at runtime.

Can anyone help me get decent, portable code out of this? GCC performs well on add with #pragma GCC ivdep, but it also does silly things with mul.

Is there a way to do this which doesn’t depend on intrinsics or extensions? If I absolutely have to write this with intrinsics or extensions, is there a nice way to do it which doesn’t change the struct definition and doesn’t break strict aliasing?

Thanks a lot,
— Mike