https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128291
Bug ID: 128291
Summary: bool comparison result is not fused into sbb when an
internal call (.UADDC, .ADD_OVERFLOW) is between it
and its use
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Keywords: missed-optimization
Severity: normal
Priority: P3
Component: middle-end
Assignee: unassigned at gcc dot gnu.org
Reporter: chfast at gmail dot com
CC: chfast at gmail dot com
Target Milestone: ---
Target: x86_64-pc-linux-gnu
typedef unsigned long u64;
u64 f_u64(u64 q, u64 a, u64 b, u64 y)
{
u64 n = a < b;
u64 s = a + y;
u64 c = s < a;
return q - n + s + c;
}
u64 f_bool(u64 q, u64 a, u64 b, u64 y)
{
_Bool n = a < b;
u64 s = a + y;
u64 c = s < a;
return q - n + s + c;
}
GCC 16.2 -O2 (GCC 14, 15 and trunk are the same):
f_u64:
addq %rsi, %rcx
adcq %rdi, %rcx
cmpq %rdx, %rsi
movq %rcx, %rax
sbbq $0, %rax
ret
f_bool:
cmpq %rdx, %rsi
setb %al
addq %rcx, %rsi
adcq %rdi, %rsi
movzbl %al, %edx
movq %rsi, %rax
subq %rdx, %rax
ret
The functions differ only in the type of n. In f_bool, a < b is materialized
with setb and movzbl and subtracted at the end, instead of being fused into the
subtraction (cmp, sbb $0) as in f_u64. clang emits the same code for both.
In the optimized dump of f_bool, n_7 = a_5(D) < b_6(D) comes first and its
conversion _2 = (long unsigned int) n_7 comes after _14 = .UADDC (a_5(D),
y_8(D), 0). TER does not replace n_7: tree-ssa-ter.cc counts every call without
a built-in fndecl as a call, so .UADDC is one, and an expression with two SSA
operands is not moved across a call. In f_u64 the conversion n_7 = (u64) _1
directly follows the comparison and has a single operand, so it is replaced
across the call together with the comparison. The same happens with
__builtin_add_overflow (.ADD_OVERFLOW) in place of the carry, but not with
__builtin_popcountl.
We found this in intx (a multi-precision integer library), which keeps such
flags uint64_t instead of bool because of it.