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
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 ;-)
Confirmed.
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(); } }
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)); }
(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.
(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...
See also PR55894.
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