-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Description
The current implementation of the safe comparison functions is less not optimal:
llvm-project/libcxx/include/__utility/cmp.h
Lines 65 to 75 in 22044f0
| template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up> | |
| _LIBCPP_INLINE_VISIBILITY constexpr | |
| bool cmp_less(_Tp __t, _Up __u) noexcept | |
| { | |
| if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) | |
| return __t < __u; | |
| else if constexpr (is_signed_v<_Tp>) | |
| return __t < 0 ? true : make_unsigned_t<_Tp>(__t) < __u; | |
| else | |
| return __u < 0 ? false : __t < make_unsigned_t<_Up>(__u); | |
| } |
For two maximally wide integers (i.e. unsigned long), this is the best you can do. However, if a wider integer type is available, then all comparisons can be made safe by converting to the wider integer.
For example, a comparison unsigned int < signed int could be implemented through long long < long long, which is either zero-cost, or involves a sign extension for one of the operands. Indeed, if we compare std::cmp_less to such an extending implementation: (https://godbolt.org/z/M1s5dxh11)
bool std_less(unsigned int a, signed int b) { return std::cmp_less(a, b); }
bool ext_less(unsigned int a, signed int b) { return static_cast<long long>(a) < static_cast<long long>(b); }The output for ext_less is slightly better, although it's not strikingly obvious. The additional mov and setl in ext_less are artifacts of the ABI, and movsxd could also disappear through inlining.
std_less(unsigned int, int):
test esi, esi
setns cl
cmp edi, esi
setb al
and al, cl
ret
ext_less(unsigned int, int):
mov eax, edi
movsxd rcx, esi
cmp rax, rcx
setl al
retWhen we add __builtin_assume(a <= INT_MAX); to both functions, the difference becomes more striking. std_less is completely unchanged, but ext_less becomes:
ext_less(unsigned int, int):
cmp edi, esi
setl al
retThis would turn std::cmp_less into a true zero-overhead utility, since LLVM could now always fall back onto a simple cmp instruction if it knows that the unsigned operand is restricted in its value range (which is quite plausible).
Proposed Change
template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
_LIBCPP_INLINE_VISIBILITY constexpr
bool cmp_less(_Tp __t, _Up __u) noexcept
{
+ if constexpr (sizeof(_Tp) <= sizeof(long long) && sizeof(_Up) <= sizeof(long long))
+ return static_cast<long long>(__t) < static_cast<long long>(__u);
- if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>)
+ else if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>)
return __t < __u;
else if constexpr (is_signed_v<_Tp>)
return __t < 0 ? true : make_unsigned_t<_Tp>(__t) < __u;
else
return __u < 0 ? false : __t < make_unsigned_t<_Up>(__u);
}The same style of change could be applied to the std::cmp_equal.