https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105903

            Bug ID: 105903
           Summary: Missed optimization for __synth3way
           Product: gcc
           Version: 12.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: barry.revzin at gmail dot com
  Target Milestone: ---

Consider this example:

#include <compare>
#include <vector>

inline constexpr auto synth3way = std::__detail::__synth3way;

struct Iterator {
    std::vector<int>::iterator it;

    constexpr bool operator<(Iterator const& rhs) const {
        return it < rhs.it;
    }
    constexpr bool operator>(Iterator const& rhs) const {
        return it > rhs.it;
    }    
};

bool less(Iterator const& a, Iterator const& b) {
    return a < b;
}

bool less3way(Iterator const& a, Iterator const& b) {
    return synth3way(a, b) < 0;
}

Here, synth3way(a, b) < 0 produces identical code to a < b (compiling with gcc
12.1 --std=c++20 -O3), which is great. The compiler recognizes that it doesn't
have to do the second synthesized comparison. 

However, if we instead compared (sorry):

bool greater(Iterator const& a, Iterator const& b) {
    return a > b;
}

bool greater3way(Iterator const& a, Iterator const& b) {
    return synth3way(a, b) > 0;
}

This is now much worse:

greater(Iterator const&, Iterator const&):
        mov     rax, QWORD PTR [rdi]
        cmp     QWORD PTR [rsi], rax
        setb    al
        ret
greater3way(Iterator const&, Iterator const&):
        mov     rdx, QWORD PTR [rdi]
        mov     rcx, QWORD PTR [rsi]
        xor     eax, eax
        cmp     rdx, rcx
        jb      .L3
        cmp     rcx, rdx
        setb    al
.L3:
        ret

Interestingly, if we write this out:

bool greater3way(Iterator const& a, Iterator const& b) {
    auto const cmp = [&]{
        if (a < b) return std::weak_ordering::less;
        if (b < a) return std::weak_ordering::greater;
        return std::weak_ordering::equivalent;
    }();
    return cmp > 0;
}

bool greater3way_fold(Iterator const& a, Iterator const& b) {
    return [&]{
        if (a < b) return false;
        if (b < a) return true;
        return false;
    }();
}

The greater3way_fold implementation generates the same code as >, which
definitely suggests to me that the greater3way version is simply a missed
optimization. 

On compiler explorer: https://godbolt.org/z/1xvfsMrnf

Because synth3way is only used in contexts that require a weak ordering anyway,
it would be a valid optimization to replace synth3way(a, b) > 0 with b < a,
synth3way(a, b) <= 0 with !(b < a) and synth3way(a, b) >= 0 with !(a < b).
These replacements aren't generally true (because partial orders), but the
precondition on this type is that we have a weak order, so we should be able to
do better.

Reply via email to