struct v {
int val[16];
};
struct v test(struct v a, struct v b) {
struct v res;
for (int i = 0; i < 16; i++)
res.val[i] = a.val[i] + b.val[i];
return res;
}
Compiling as C++, GCC 7.2 emits:
push r10
vmovdqu32 zmm0, ZMMWORD PTR [rsp+16]
mov rax, rdi
vpaddd zmm0, zmm0, ZMMWORD PTR [rsp+80]
lea r10, [rsp+16]
vmovdqu32 ZMMWORD PTR [rdi], zmm0
pop r10
Compiling as C:
lea r10, [rsp+8]
and rsp, -64
mov rax, rdi
push QWORD PTR [r10-8]
push rbp
mov rbp, rsp
push r10
vmovdqu32 zmm0, ZMMWORD PTR [r10]
vpaddd zmm0, zmm0, ZMMWORD PTR [r10+64]
vmovdqa64 ZMMWORD PTR [rbp-112], zmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-112]
vmovups XMMWORD PTR [rdi], xmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-96]
vmovups XMMWORD PTR [rdi+16], xmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-80]
vmovups XMMWORD PTR [rdi+32], xmm0
vmovdqa64 xmm0, XMMWORD PTR [rbp-64]
vmovups XMMWORD PTR [rdi+48], xmm0
pop r10
pop rbp
lea rsp, [r10-8]
Compiled as C vs. C++ on the Godbolt compiler explorer:
clang x86 asm: https://godbolt.org/g/FfrKTf
gcc x86 asm: https://godbolt.org/g/SZQqqt
The same code snippet produces significantly different code for C and C++ in both gcc and clang:
The loop is auto-vectorized differently: gcc using unaligned loads / stores but g++ using scalar until an alignment boundary, with -march=sandybridge
or anything narrower than AVX2.
This isn't x86-specific: the same thing happens with auto-vectorization for AArch64 SIMD.
The C version actually stores results to a local res
on the stack and then uses copies SIMD copies from there to the return-value pointer (sometimes with different vector width for the copy than for the loop that just stored the data).
With auto-vectorization disabled (-fno-tree-vectorize
), gcc still uses SIMD load/store to copy the results.
Does anyone know why that is so?
Is there some alignment guarantee that is in the C++ standard but is not in the C standard? Is this an ABI issue? Or is that some weird compilation option on compiler explorer?
According to compiler dumps C Gimple IR works intermediate object on stack (which is then copied to return value)
$ gcc ../tmp.c -fdump-tree-gimple -O2 -S -std=c99
$ cat tmp.c.004t.gimple
...
<D.1621>:
D.1626 = a.val[i];
D.1627 = b.val[i];
D.1628 = D.1626 + D.1627;
res.val[i] = D.1628;
i = i + 1;
<D.1622>:
if (i <= 15) goto <D.1621>; else goto <D.1623>;
<D.1623>:
}
<retval> = res;
return <retval>;
whereas C++ one operates directly on retval temp:
$ g++ ../tmp.c -fdump-tree-gimple -O2 -S
$ cat tmp.c.004t.gimple
<D.2250>:
D.2254 = a.val[i];
D.2255 = b.val[i];
D.2256 = D.2254 + D.2255;
<retval>.val[i] = D.2256;
i = i + 1;
<D.2251>:
if (i <= 15) goto <D.2250>; else goto <D.2248>;
<D.2248>:
}
return <retval>;
AFAIR C and C++ Gimple generators do not share code so anomalies like this are to be expected. I encourage you to report this in GCC Bugzilla as it's probly a common performance issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With