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.

Reply via email to