Bug 80517 - [missed optimization] constant propagation through Intel intrinsics
Summary: [missed optimization] constant propagation through Intel intrinsics
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: target (show other bugs)
Version: 9.0
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: missed-optimization
Depends on:
Blocks: 88918
  Show dependency treegraph
 
Reported: 2017-04-25 13:58 UTC by Matthias Kretz (Vir)
Modified: 2025-03-11 21:40 UTC (History)
2 users (show)

See Also:
Host:
Target: x86_64-*-*, i?86-*-*
Build:
Known to work:
Known to fail:
Last reconfirmed: 2025-03-11 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Matthias Kretz (Vir) 2017-04-25 13:58:53 UTC
Related: #55894

Testcase:
#include <x86intrin.h>

int f() {
    __m128i x{};
    x = _mm_cmpeq_epi16(x, x);
    return _pext_u32(_mm_movemask_epi8(x), 0xaaaa);
}

(compile with `-mbmi2 -O3 -std=c++14`)

See also https://godbolt.org/g/n92wEc

This should compile to
f():
  movl $0xff, %eax
  ret

Clang already implements constant propagation for the testcase except for `pext` (see godbolt link).

This is just a precursor to the following testcase:
#include <x86intrin.h>

auto g(__m128i x, __m128i y) {
    __m128i mask0 = _mm_cmpeq_epi16(x, y);
    auto bits = _pext_u32(_mm_movemask_epi8(mask0), 0xaaaa);
    __m128i mask = _mm_set1_epi16(bits);
    mask = _mm_and_si128(mask, _mm_setr_epi16(1, 2, 4, 8, 16, 32, 64, 128));
    mask = _mm_cmpeq_epi16(mask, _mm_setzero_si128());
    mask = _mm_xor_si128(mask, _mm_cmpeq_epi16(mask, mask));
    return mask;
}

This should compile to
f():
  vpcmpeqw %xmm0, %xmm1, %xmm0
  ret

I.e. The xmm mask `mask0` is converted to a bitmask and back to an xmm mask. Similar patterns exist for all arithmetic types for SSE and AVX. If you like, I can produce a list of testcases for all vector element types for SSE and AVX.

Motivation: An ABI-stable mask type for x86 requires a storage format that is independent of the ISA extensions available on the specific x86 CPU. In light of AVX512, the most sensible choice for such a mask storage is std::bitset<N>. This is a natural fit for AVX512 masks but requires frequent conversion to/from xmm and ymm mask when AVX/SSE registers are modified. If the above is optimized, it would go a long way to reducing the cost of using the ABI-stable types.
Reference: https://wg21.link/p0214
Comment 1 Marc Glisse 2017-04-25 15:44:45 UTC
Several of those intrinsics are implemented using vector extensions and constant propagation works fine on those. What seems to be missing here is constant folding of the very specific __builtin_ia32_pmovmskb128 and __builtin_ia32_pext_si in ix86_fold_builtin. It should not be very hard, mostly needs someone motivated ;-)
Comment 2 Richard Biener 2017-04-26 08:36:23 UTC
Confirmed.
Comment 3 Matthias Kretz (Vir) 2019-01-11 16:07:40 UTC
GCC 9 almost resolves this. However, for some reason this extended test case is not fully optimized: https://gcc.godbolt.org/z/jRrHth
i.e. the call to dont_call_me() should be eliminated as dead code

#include <x86intrin.h>

inline __m128i cmp(__m128i x, __m128i y) {
    return _mm_cmpeq_epi16(x, y);
}
inline unsigned to_bits(__m128i mask0) {
    return _pext_u32(_mm_movemask_epi8(mask0), 0xaaaa);
}

inline __m128i to_vmask(unsigned bits) {
    __m128i mask = _mm_set1_epi16(bits);
    mask = _mm_and_si128(mask, _mm_setr_epi16(1, 2, 4, 8, 16, 32, 64, 128));
    mask = _mm_cmpeq_epi16(mask, _mm_setzero_si128());
    mask = _mm_xor_si128(mask, _mm_cmpeq_epi16(mask, mask));
    return mask;
}

inline bool is_eq(unsigned bits, __m128i vmask) {
    return to_bits(vmask) == bits;
}

extern const auto a = __m128i{0x0001'0002'0004'0003, 0x0009'0008'0007'0006};
extern const auto b = __m128i{0x0001'0002'0005'0003, 0x0000'0008'0007'0006};
extern const auto c = cmp(a, b);
extern const auto d = to_bits(c);

void call_me();
void dont_call_me();
void f() {
    if (is_eq(d, cmp(b, a))) {
        call_me();
    } else {
        dont_call_me();
    }
}
Comment 4 Matthias Kretz (Vir) 2019-01-11 16:18:05 UTC
A similar test case showing that something is still missing (https://gcc.godbolt.org/z/t1DT7E):

#include <x86intrin.h>

inline __m128i cmp(__m128i x, __m128i y) {
    return _mm_cmpeq_epi16(x, y);
}
inline unsigned to_bits(__m128i mask0) {
    return _pext_u32(_mm_movemask_epi8(mask0), 0xaaaa);
}

inline __m128i to_vmask(unsigned bits) {
    __m128i mask = _mm_set1_epi16(bits);
    mask = _mm_and_si128(mask, _mm_setr_epi16(1, 2, 4, 8, 16, 32, 64, 128));
    mask = _mm_cmpeq_epi16(mask, _mm_setzero_si128());
    mask = _mm_xor_si128(mask, _mm_cmpeq_epi16(mask, mask));
    return mask;
}

auto f(__m128i x, __m128i y) {
    // should be:
    // vpcmpeqw %xmm1, %xmm0, %xmm0
    // ret
    return to_vmask(to_bits(cmp(x, y)));
}

auto f(unsigned bits) {
    // should be equivalent to `return 0xff & bits;`
    return to_bits(to_vmask(bits));
}
Comment 5 Marc Glisse 2019-01-11 16:27:19 UTC
(In reply to Matthias Kretz from comment #3)
> GCC 9 almost resolves this. However, for some reason this extended test case
> is not fully optimized: https://gcc.godbolt.org/z/jRrHth
> i.e. the call to dont_call_me() should be eliminated as dead code

We are left with:

_GLOBAL__sub_I__Z1fv ()
{
  <bb 2> [local count: 1073741824]:
  d = 125;
  return;

}

f ()
{
  unsigned int d.1_1;

  <bb 2> [local count: 1073741824]:
  d.1_1 = d;
  if (d.1_1 == 125)
[...]

This is a classic, if the initialization of global variables is only noticed to be constant after optimizations (as opposed to in the front-end), gcc doesn't manage to turn the dynamic initialization into a static one. Making the intrinsics constexpr may help, but really this is something that would be nice to fix eventually, there are several PRs blocked by this.
Comment 6 Marc Glisse 2019-01-11 16:45:33 UTC
(In reply to Matthias Kretz from comment #4)
> A similar test case showing that something is still missing

You don't seem to be passing constants here, so this is unrelated to this PR. If you file a new one, please annotate your example explaining where you expect what to simplify to what and why.

> (https://gcc.godbolt.org/z/t1DT7E):

Adding -fdump-tree-optimized=- -g0 and showing the compiler output makes this more understandable for me...
Comment 7 Uroš Bizjak 2019-01-21 12:59:52 UTC
See also PR55894.
Comment 8 Drea Pinski 2023-08-22 04:53:58 UTC
status of this bug:
comment #0 first testcase: Fixed since GCC 9
comment #0 second testcase: still needs improvement

comment #3 is now basically PR 24928