[libc++] std::abs support for _BitInt(N) and __int128 (#196532)

`std::abs` does not accept `__int128` or signed `_BitInt(N)`: the call
is ambiguous and fails to compile (#204212).

This adds an explicit `abs(__int128_t)` overload and an
`abs(_BitInt(N))` overload that deduces the width, so every signed
`_BitInt` gets a same-type result. `_BitInt` does not integer-promote,
so without this overload a narrow signed `_BitInt` would be an ambiguous
call against `abs(int/long/long long)` instead of promoting the way
`signed char` and `short` do. Standard narrow types are unchanged: they
still go through `abs(int)`.

Part of the [_BitInt(N) libc++
effort](https://discourse.llvm.org/t/bitint-n-support-in-libc-investigations-possible-improvements-looking-for-guidance/90063).

Fixes #204212

Assisted-by: Claude (Anthropic)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Xavier Roche 2026-06-22 19:19:19 +02:00 committed by GitHub
parent a88b88bdd3
commit 20cf19f0c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 112 additions and 0 deletions

View file

@ -63,6 +63,19 @@ template <class = int>
return __builtin_llabs(__x);
}
#if _LIBCPP_HAS_INT128
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI inline __int128_t abs(__int128_t __x) _NOEXCEPT { return __x < 0 ? -__x : __x; }
#endif
#if defined(__BITINT_MAXWIDTH__)
// _BitInt does not integer-promote, so without a same-type overload a narrow
// signed _BitInt would be an ambiguous call against abs(int/long/long long).
template <int _Np>
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _BitInt(_Np) abs(_BitInt(_Np) __x) _NOEXCEPT {
return __x < 0 ? -__x : __x;
}
#endif
} // namespace __math
_LIBCPP_END_NAMESPACE_STD

View file

@ -6,6 +6,8 @@
//
//===----------------------------------------------------------------------===//
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <assert.h>
#include <cmath>
#include <cstdint>
@ -37,6 +39,65 @@ void test_big() {
assert(std::abs(negative_big_value) == big_value); // make sure it doesn't get casted to a smaller type
}
// std::abs has __int128/_BitInt(N) overloads as a libc++ extension.
// Narrow signed types stay on the abs(int) promotion path.
template <class T>
struct unpromoted_abs : std::is_same<decltype(std::abs(T(0))), T> {};
static_assert(!unpromoted_abs<signed char>::value, "");
static_assert(!unpromoted_abs<short>::value, "");
#if TEST_HAS_BITINT
// Every signed _BitInt(N) gets a same-type abs, including widths narrower than
// int. unsigned _BitInt is excluded by the signed-only contract.
template <class T, class = void>
struct has_abs : std::false_type {};
template <class T>
struct has_abs<T, decltype((void)std::abs(T(0)))> : std::true_type {};
static_assert(has_abs<signed _BitInt(2)>::value, "");
static_assert(has_abs<signed _BitInt(7)>::value, "");
static_assert(has_abs<signed _BitInt(32)>::value, "");
static_assert(has_abs<signed _BitInt(64)>::value, "");
static_assert(!has_abs<unsigned _BitInt(32)>::value, ""); // signed-only contract
static_assert(!has_abs<unsigned _BitInt(64)>::value, "");
template <int N>
void test_signed_bitint() {
typedef signed _BitInt(N) T;
ASSERT_SAME_TYPE(decltype(std::abs(T(0))), T);
assert(std::abs(T(0)) == T(0));
assert(std::abs(T(1)) == T(1));
assert(std::abs(T(42)) == T(42));
assert(std::abs(T(-1)) == T(1));
assert(std::abs(T(-42)) == T(42));
// T_MIN omitted: -T_MIN is UB, same as abs(INT_MIN).
T t_max = static_cast<T>(~static_cast<unsigned _BitInt(N)>(0) >> 1);
T t_min_plus1 = -t_max;
assert(std::abs(t_max) == t_max);
assert(std::abs(t_min_plus1) == t_max);
}
#endif // TEST_HAS_BITINT
#ifndef TEST_HAS_NO_INT128
void test_int128() {
ASSERT_SAME_TYPE(decltype(std::abs(static_cast<__int128_t>(0))), __int128_t);
assert(std::abs(static_cast<__int128_t>(0)) == 0);
assert(std::abs(static_cast<__int128_t>(42)) == 42);
assert(std::abs(static_cast<__int128_t>(-42)) == 42);
assert(std::abs(static_cast<__int128_t>(-1)) == 1);
// INT128_MIN omitted: -__x is UB, same as abs(INT_MIN).
__int128_t big_neg = -((static_cast<__int128_t>(1) << 100));
__int128_t big_pos = static_cast<__int128_t>(1) << 100;
assert(std::abs(big_neg) == big_pos);
__int128_t int128_max = static_cast<__int128_t>(~static_cast<__uint128_t>(0) >> 1);
__int128_t int128_min_plus1 = -int128_max;
assert(std::abs(int128_max) == int128_max);
assert(std::abs(int128_min_plus1) == int128_max);
}
#endif // TEST_HAS_NO_INT128
// The following is helpful to keep in mind:
// 1byte == char <= short <= int <= long <= long long
@ -68,5 +129,43 @@ int main(int, char**) {
test_big();
#if TEST_HAS_BITINT
// Non-byte-aligned _BitInt(N) has uninitialized padding bits; passing such a
// value to std::abs trips a false-positive MSan report (#204217). Gate those
// widths out under MSan; byte-aligned widths still run.
test_signed_bitint<8>();
test_signed_bitint<16>();
test_signed_bitint<32>();
test_signed_bitint<64>();
# if !TEST_HAS_FEATURE(memory_sanitizer)
test_signed_bitint<7>();
test_signed_bitint<33>();
test_signed_bitint<63>();
test_signed_bitint<65>();
# endif
# if __BITINT_MAXWIDTH__ >= 128
test_signed_bitint<128>();
# endif
# if __BITINT_MAXWIDTH__ >= 256
# if !TEST_HAS_FEATURE(memory_sanitizer)
test_signed_bitint<129>();
# endif
test_signed_bitint<256>();
// Large value: |-2^200| == 2^200.
signed _BitInt(256) v = -(static_cast<signed _BitInt(256)>(1) << 200);
signed _BitInt(256) expected = static_cast<signed _BitInt(256)>(1) << 200;
assert(std::abs(v) == expected);
# endif
# if __BITINT_MAXWIDTH__ >= 1024
test_signed_bitint<512>();
test_signed_bitint<1024>();
# endif
#endif // TEST_HAS_BITINT
#ifndef TEST_HAS_NO_INT128
test_int128();
#endif
return 0;
}