https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120395
Bug ID: 120395
Summary: Calls to std::__is_constant_evaluated() hurt codegen
at -O0
Product: gcc
Version: 16.0
Status: UNCONFIRMED
Keywords: missed-optimization
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: redi at gcc dot gnu.org
Target Milestone: ---
void x(int);
[[gnu::always_inline]] inline bool
is_constant_evaluated()
{ return __builtin_is_constant_evaluated(); }
struct Iter
{
typedef int value_type;
int& operator*() const;
Iter& operator++();
bool operator!=(const Iter&) const;
};
void f(Iter first, Iter last)
{
if (__is_trivial(Iter::value_type))
if (!is_constant_evaluated())
return;
for (; first != last; ++first)
x(*first);
}
At -O1 this is fine, the code compiles away completey as expected. But at -O0
the always_inline function seems to not be inlined, or to act as though it
isn't.
Even though the function f is not constexpr, so can never be constant
evaluated, the generated code includes the loop, which should be dead code:
f(Iter, Iter):
push rbp
mov rbp, rsp
sub rsp, 16
mov eax, 0
xor eax, 1
test al, al
jne .L7
jmp .L5
.L6:
lea rax, [rbp-1]
mov rdi, rax
call Iter::operator*() const
mov eax, DWORD PTR [rax]
mov edi, eax
call x(int)
lea rax, [rbp-1]
mov rdi, rax
call Iter::operator++()
.L5:
lea rdx, [rbp-2]
lea rax, [rbp-1]
mov rsi, rdx
mov rdi, rax
call Iter::operator!=(Iter const&) const
test al, al
jne .L6
jmp .L1
.L7:
nop
.L1:
leave
ret
If we comment out the if (!is_constant_evaluated()) check then the code is
ideal:
f(Iter, Iter):
push rbp
mov rbp, rsp
nop
pop rbp
ret
(again, this is for -O0 so it's not a single instruction like at -O1 but it's
what I expect for -O0).
If we replace the always_inline function and just call
__builtin_is_constant_evaluated() directly, the code is ideal. So why does an
always_inline function that calls the builtin not produce the same code as the
builtin? Using the builtin directly isn't an option for the library code.
Is there anything we can do to remove this overhead for
std::__is_constant_evaluated() in libstdc++? Newer C++ standards require us to
put those checks in more and more places, but I didn't realise the effect on
codegen could be so significant.
I'd really prefer not to have to replace the always_inline function with a
macro that expands directly to the builtin (or to `if consteval` for C++23).
(As a separate issue, I've noticed that std::is_constant_evaluated() isn't
marked always_inline, which should be fixed so it's at least on a par with
std::__is_constant_evaluated(), even if that has bad codegen for -O0.)