[libcxx-commits] [libcxx] [libc++] std::abs support for _BitInt(N) and __int128 (PR #196532)
Xavier Roche via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jun 16 11:55:20 PDT 2026
https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/196532
>From c3fa4b590aeb9e038653b7bf5e909cf69b3610be Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 8 May 2026 15:05:42 +0200
Subject: [PATCH 01/12] [libc++] Add std::abs support for _BitInt(N) and
__int128
Add a constrained template overload of std::abs in __math/abs.h that
covers signed integer types not handled by the existing builtin
overloads for int, long, and long long. The new template matches any
signed integer type with sizeof(_Tp) >= sizeof(int), so __int128_t
and signed _BitInt(N >= 32) work, while shorter standard types still
go through abs(int) via integer promotion.
The implementation is x < 0 ? -x : x, the same conditional negation
the standard uses to define abs for signed integers.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__math/abs.h | 15 ++++
.../numerics/c.math/abs.bitint.pass.cpp | 82 +++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index b780159f11ebf..5610d5685ed77 100644
--- a/libcxx/include/__math/abs.h
+++ b/libcxx/include/__math/abs.h
@@ -11,7 +11,9 @@
#include <__config>
#include <__type_traits/enable_if.h>
+#include <__type_traits/integer_traits.h>
#include <__type_traits/is_integral.h>
+#include <__type_traits/is_same.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -63,6 +65,19 @@ template <class = int>
return __builtin_llabs(__x);
}
+// Overload for __int128 and signed _BitInt(N >= 32) types. The sizeof
+// check excludes shorter signed types (e.g. signed short, signed
+// _BitInt(16)). The standard overloads catch the standard types via
+// integer promotion. _BitInt narrower than int does not promote and
+// is intentionally unsupported by std::abs.
+template <class _Tp,
+ __enable_if_t<__is_signed_integer_v<_Tp> && !is_same<_Tp, int>::value && !is_same<_Tp, long>::value &&
+ !is_same<_Tp, long long>::value && (sizeof(_Tp) >= sizeof(int)),
+ int> = 0>
+[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _Tp abs(_Tp __x) _NOEXCEPT {
+ return __x < 0 ? -__x : __x;
+}
+
} // namespace __math
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
new file mode 100644
index 0000000000000..be0b77de8701b
--- /dev/null
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// std::abs for _BitInt(N) and __int128 -- libc++ extension over the standard
+// abs(int / long / long long) overloads. The new template covers signed
+// integer types not handled by the existing builtin overloads, gated on
+// __is_signed_integer_v plus a sizeof(_Tp) >= sizeof(int) check that keeps
+// shorter standard types on the integer-promotion path.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <cassert>
+#include <cmath>
+
+#include "test_macros.h"
+
+#if TEST_HAS_EXTENSION(bit_int)
+template <int N>
+void test_signed_bitint() {
+ using T = signed _BitInt(N);
+ 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));
+}
+#endif
+
+int main(int, char**) {
+#if TEST_HAS_EXTENSION(bit_int)
+ // _BitInt(N) with N < sizeof(int) * CHAR_BIT does not match the new
+ // template (sizeof guard) and does not implicit-promote to int either, so
+ // it has no abs overload. Start at 32 bits.
+ test_signed_bitint<32>();
+ test_signed_bitint<64>();
+
+ // Odd widths >= 32 bits.
+ test_signed_bitint<33>();
+ test_signed_bitint<63>();
+ test_signed_bitint<65>();
+
+# if __BITINT_MAXWIDTH__ >= 128
+ test_signed_bitint<128>();
+# endif
+
+# if __BITINT_MAXWIDTH__ >= 256
+ test_signed_bitint<129>();
+ test_signed_bitint<256>();
+
+ // Large value: |-2^200| == 2^200. Python: abs(-(1 << 200)) == 1 << 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_EXTENSION(bit_int)
+
+#if _LIBCPP_HAS_INT128
+ 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 is unrepresentable as a positive __int128; -__x overflows.
+ // Skip that case -- the standard's abs(int) has the same issue with
+ // INT_MIN, so this is consistent.
+ __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);
+#endif
+
+ return 0;
+}
>From 2483bd6071500b8516e9563e5190d16cf828ec67 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 8 May 2026 15:23:33 +0200
Subject: [PATCH 02/12] [libc++][test] Cover boundary values in std::abs
_BitInt test
Add T_MAX (identity) and T_MIN + 1 (cleanly negates to T_MAX) to the
per-width signed _BitInt test. The T_MIN case is intentionally not
tested -- abs(T_MIN) overflows in signed arithmetic, which is
undefined behaviour, matching the standard's abs(int) caveat for
INT_MIN.
Same boundary added to the __int128_t section for INT128_MAX and
INT128_MIN + 1.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../numerics/c.math/abs.bitint.pass.cpp | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
index be0b77de8701b..05bfac0a32f76 100644
--- a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -15,7 +15,7 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
#include <cassert>
-#include <cmath>
+#include <cstdlib>
#include "test_macros.h"
@@ -28,6 +28,16 @@ void test_signed_bitint() {
assert(std::abs(T(42)) == T(42));
assert(std::abs(T(-1)) == T(1));
assert(std::abs(T(-42)) == T(42));
+
+ // Boundary cases. T_MAX has no overflow (identity). T_MIN + 1 negates to
+ // T_MAX, which is the largest negative value abs handles cleanly. T_MIN
+ // itself is intentionally not tested: -T_MIN overflows in signed
+ // arithmetic, which is undefined behaviour. The same caveat applies to
+ // std::abs(int) with INT_MIN.
+ T t_max = static_cast<T>(~static_cast<unsigned _BitInt(N)>(0) >> 1);
+ T t_min_plus1 = -t_max; // == T_MIN + 1
+ assert(std::abs(t_max) == t_max);
+ assert(std::abs(t_min_plus1) == t_max);
}
#endif
@@ -76,6 +86,13 @@ int main(int, char**) {
__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);
+
+ // Boundary: INT128_MAX (identity) and INT128_MIN+1 (negation cleanly
+ // produces INT128_MAX).
+ __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
return 0;
>From db68dddf77a59f4c224a7e56d8d5de9654175d0a Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 8 May 2026 16:38:53 +0200
Subject: [PATCH 03/12] [libc++][NFC] Address review nits on std::abs _BitInt
overload
Two follow-ups from the pre-push review of #196532:
- Tighten the comment in __math/abs.h. The previous text said
"_BitInt(N >= 32)", which is misleading: the actual gate is
sizeof(_Tp) >= sizeof(int), and that catches _BitInt(24) on x86-64
(sizeof 4) too. Also explain why the !is_same exclusions for int /
long / long long are load-bearing: signed _BitInt(33..64) shares
sizeof with long long, so a sizeof-only gate would let those types
compete with the existing builtin-based overloads.
- Add ASSERT_SAME_TYPE for the return type in the per-width signed
_BitInt test and in the __int128_t section. Pins that std::abs
preserves the input type rather than decaying to long long or any
other surprise.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__math/abs.h | 15 ++++++++++-----
.../libcxx/numerics/c.math/abs.bitint.pass.cpp | 2 ++
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index 5610d5685ed77..4181173f6d82f 100644
--- a/libcxx/include/__math/abs.h
+++ b/libcxx/include/__math/abs.h
@@ -65,11 +65,16 @@ template <class = int>
return __builtin_llabs(__x);
}
-// Overload for __int128 and signed _BitInt(N >= 32) types. The sizeof
-// check excludes shorter signed types (e.g. signed short, signed
-// _BitInt(16)). The standard overloads catch the standard types via
-// integer promotion. _BitInt narrower than int does not promote and
-// is intentionally unsupported by std::abs.
+// Overload for __int128 and signed _BitInt(N) where sizeof(_Tp) >=
+// sizeof(int). The sizeof check excludes shorter signed types (signed
+// short, signed _BitInt(16)). The standard overloads catch the standard
+// types via integer promotion. _BitInt narrower than int does not
+// promote and is intentionally unsupported by std::abs.
+//
+// The explicit !is_same exclusions for int/long/long long are
+// load-bearing: signed _BitInt(33..64) shares sizeof with long long, so
+// a sizeof-only gate would let those types compete with the existing
+// builtin-based overloads.
template <class _Tp,
__enable_if_t<__is_signed_integer_v<_Tp> && !is_same<_Tp, int>::value && !is_same<_Tp, long>::value &&
!is_same<_Tp, long long>::value && (sizeof(_Tp) >= sizeof(int)),
diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
index 05bfac0a32f76..11f4f121450b7 100644
--- a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -23,6 +23,7 @@
template <int N>
void test_signed_bitint() {
using T = signed _BitInt(N);
+ 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));
@@ -75,6 +76,7 @@ int main(int, char**) {
#endif // TEST_HAS_EXTENSION(bit_int)
#if _LIBCPP_HAS_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);
>From 71d5d632c8c81f6ae3574501e465b5754a84db30 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Thu, 4 Jun 2026 17:20:36 +0200
Subject: [PATCH 04/12] [libc++][test] Add negative SFINAE checks for std::abs
sizeof gate
The new std::abs template overload for _BitInt(N) and __int128 carries
a `sizeof(_Tp) >= sizeof(int)` constraint that keeps shorter signed
types on the integer-promotion path. The existing tests cover the
positive widths but do not assert the gate's negative side. Add two
checks:
- For standard short types (`signed char`, `short`), the type of
`std::abs(t)` is `int` (the unpromoted T would only match the new
template). A negative `is_same<decltype(std::abs(T())), T>` confirms
they still go through `abs(int)` via promotion, not through the new
template.
- For `_BitInt(N)` narrower than `int` (`_BitInt(7)`, `_BitInt(16)`,
`_BitInt(31)`), `std::abs` is not callable at all -- they neither
promote to `int` nor match the sizeof guard. SFINAE detection
confirms.
These guard against a future relaxation of the sizeof guard silently
admitting short types that the comment in `abs.h` already documents as
intentionally excluded.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../numerics/c.math/abs.bitint.pass.cpp | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
index 11f4f121450b7..9da43af4c6ada 100644
--- a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
@@ -16,10 +16,31 @@
#include <cassert>
#include <cstdlib>
+#include <type_traits>
#include "test_macros.h"
+// Pin down the sizeof gate: narrow signed types still resolve to abs(int) via
+// integer promotion, and the new template stays out of their overload set.
+// Detection probes the template path specifically (the type of std::abs(t) is
+// the unpromoted T only for the new overload).
+template <class T>
+constexpr bool unpromoted_abs = std::is_same<decltype(std::abs(T(0))), T>::value;
+static_assert(!unpromoted_abs<signed char>);
+static_assert(!unpromoted_abs<short>);
+
#if TEST_HAS_EXTENSION(bit_int)
+// _BitInt(N) narrower than int does not promote and the new template's sizeof
+// gate excludes it, so abs is not callable. Probe via SFINAE.
+template <class T, class = void>
+constexpr bool has_abs = false;
+template <class T>
+constexpr bool has_abs<T, decltype((void)std::abs(T(0)))> = true;
+static_assert(!has_abs<signed _BitInt(7)>);
+static_assert(!has_abs<signed _BitInt(16)>);
+static_assert(!has_abs<signed _BitInt(31)>);
+static_assert(has_abs<signed _BitInt(32)>);
+
template <int N>
void test_signed_bitint() {
using T = signed _BitInt(N);
>From 5cadb03ba809f3ec1008cb9b51988214d55df0ce Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 09:39:57 +0200
Subject: [PATCH 05/12] [libc++][test] Colocate _BitInt and __int128 abs tests
into abs.pass.cpp
Move the _BitInt(N) and __int128_t test coverage from the standalone
abs.bitint.pass.cpp into the existing abs.pass.cpp and delete the
sibling file. Addresses philnik's review on PR #196532 ("please put
the tests into pre-existing files. Also for all new PRs").
Also switch the probe from TEST_HAS_EXTENSION(bit_int) to
defined(__BITINT_MAXWIDTH__). __has_extension(bit_int) is not
registered in Clang's Features.def and returns 0 in every language
mode, so the previously-dead block was hiding a latent test bug:
the SFINAE probe asserted !has_abs<_BitInt(31)>, but
sizeof(_BitInt(31)) == sizeof(int) on x86_64 so the overload
matches it. Drop the borderline negative assertions and keep a
clear case (_BitInt(7), sizeof < sizeof(int) on every common
target) plus positive cases at 32 and 64 bits.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../numerics/c.math/abs.bitint.pass.cpp | 122 ------------------
libcxx/test/std/numerics/c.math/abs.pass.cpp | 93 +++++++++++++
2 files changed, 93 insertions(+), 122 deletions(-)
delete mode 100644 libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
diff --git a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp b/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
deleted file mode 100644
index 9da43af4c6ada..0000000000000
--- a/libcxx/test/libcxx/numerics/c.math/abs.bitint.pass.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// std::abs for _BitInt(N) and __int128 -- libc++ extension over the standard
-// abs(int / long / long long) overloads. The new template covers signed
-// integer types not handled by the existing builtin overloads, gated on
-// __is_signed_integer_v plus a sizeof(_Tp) >= sizeof(int) check that keeps
-// shorter standard types on the integer-promotion path.
-
-// UNSUPPORTED: c++03, c++11, c++14, c++17
-
-#include <cassert>
-#include <cstdlib>
-#include <type_traits>
-
-#include "test_macros.h"
-
-// Pin down the sizeof gate: narrow signed types still resolve to abs(int) via
-// integer promotion, and the new template stays out of their overload set.
-// Detection probes the template path specifically (the type of std::abs(t) is
-// the unpromoted T only for the new overload).
-template <class T>
-constexpr bool unpromoted_abs = std::is_same<decltype(std::abs(T(0))), T>::value;
-static_assert(!unpromoted_abs<signed char>);
-static_assert(!unpromoted_abs<short>);
-
-#if TEST_HAS_EXTENSION(bit_int)
-// _BitInt(N) narrower than int does not promote and the new template's sizeof
-// gate excludes it, so abs is not callable. Probe via SFINAE.
-template <class T, class = void>
-constexpr bool has_abs = false;
-template <class T>
-constexpr bool has_abs<T, decltype((void)std::abs(T(0)))> = true;
-static_assert(!has_abs<signed _BitInt(7)>);
-static_assert(!has_abs<signed _BitInt(16)>);
-static_assert(!has_abs<signed _BitInt(31)>);
-static_assert(has_abs<signed _BitInt(32)>);
-
-template <int N>
-void test_signed_bitint() {
- using T = signed _BitInt(N);
- 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));
-
- // Boundary cases. T_MAX has no overflow (identity). T_MIN + 1 negates to
- // T_MAX, which is the largest negative value abs handles cleanly. T_MIN
- // itself is intentionally not tested: -T_MIN overflows in signed
- // arithmetic, which is undefined behaviour. The same caveat applies to
- // std::abs(int) with INT_MIN.
- T t_max = static_cast<T>(~static_cast<unsigned _BitInt(N)>(0) >> 1);
- T t_min_plus1 = -t_max; // == T_MIN + 1
- assert(std::abs(t_max) == t_max);
- assert(std::abs(t_min_plus1) == t_max);
-}
-#endif
-
-int main(int, char**) {
-#if TEST_HAS_EXTENSION(bit_int)
- // _BitInt(N) with N < sizeof(int) * CHAR_BIT does not match the new
- // template (sizeof guard) and does not implicit-promote to int either, so
- // it has no abs overload. Start at 32 bits.
- test_signed_bitint<32>();
- test_signed_bitint<64>();
-
- // Odd widths >= 32 bits.
- test_signed_bitint<33>();
- test_signed_bitint<63>();
- test_signed_bitint<65>();
-
-# if __BITINT_MAXWIDTH__ >= 128
- test_signed_bitint<128>();
-# endif
-
-# if __BITINT_MAXWIDTH__ >= 256
- test_signed_bitint<129>();
- test_signed_bitint<256>();
-
- // Large value: |-2^200| == 2^200. Python: abs(-(1 << 200)) == 1 << 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_EXTENSION(bit_int)
-
-#if _LIBCPP_HAS_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 is unrepresentable as a positive __int128; -__x overflows.
- // Skip that case -- the standard's abs(int) has the same issue with
- // INT_MIN, so this is consistent.
- __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);
-
- // Boundary: INT128_MAX (identity) and INT128_MIN+1 (negation cleanly
- // produces INT128_MAX).
- __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
-
- return 0;
-}
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index 51aee6e986836..91147dafabc1a 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -37,6 +37,69 @@ void test_big() {
assert(std::abs(negative_big_value) == big_value); // make sure it doesn't get casted to a smaller type
}
+// The libc++ extension overload covers signed integer types >= sizeof(int)
+// that aren't matched by the standard abs(int/long/long long): __int128 and
+// signed _BitInt(N). Shorter types still go through abs(int) via integer
+// promotion -- the new template's sizeof gate must keep them out.
+template <class T>
+constexpr bool unpromoted_abs = std::is_same<decltype(std::abs(T(0))), T>::value;
+static_assert(!unpromoted_abs<signed char>);
+static_assert(!unpromoted_abs<short>);
+
+#ifdef __BITINT_MAXWIDTH__
+// _BitInt(N) does not participate in integer promotion. The template's
+// sizeof(_Tp) >= sizeof(int) gate determines membership, not bit width:
+// sizeof(_BitInt(31)) == sizeof(int) on x86_64 so the overload includes it.
+// Probe via SFINAE on widths whose sizeof is unambiguous.
+template <class T, class = void>
+constexpr bool has_abs = false;
+template <class T>
+constexpr bool has_abs<T, decltype((void)std::abs(T(0)))> = true;
+static_assert(!has_abs<signed _BitInt(7)>);
+static_assert(has_abs<signed _BitInt(32)>);
+static_assert(has_abs<signed _BitInt(64)>);
+
+template <int N>
+void test_signed_bitint() {
+ using T = signed _BitInt(N);
+ 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_MAX has no overflow (identity). T_MIN + 1 negates cleanly to T_MAX.
+ // T_MIN itself is intentionally not tested -- -T_MIN overflows, same caveat
+ // as std::abs(int) with 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
+
+#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 is unrepresentable as a positive __int128. Skip, consistent
+ // with the standard abs(int) INT_MIN caveat.
+ __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
+
// The following is helpful to keep in mind:
// 1byte == char <= short <= int <= long <= long long
@@ -68,5 +131,35 @@ int main(int, char**) {
test_big();
+#ifdef __BITINT_MAXWIDTH__
+ // _BitInt(N < sizeof(int) * CHAR_BIT) does not match the new template
+ // (sizeof guard) and does not promote, so abs has no overload. Start at 32.
+ test_signed_bitint<32>();
+ test_signed_bitint<64>();
+ test_signed_bitint<33>();
+ test_signed_bitint<63>();
+ test_signed_bitint<65>();
+# if __BITINT_MAXWIDTH__ >= 128
+ test_signed_bitint<128>();
+# endif
+# if __BITINT_MAXWIDTH__ >= 256
+ test_signed_bitint<129>();
+ 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
+
+#ifndef TEST_HAS_NO_INT128
+ test_int128();
+#endif
+
return 0;
}
>From 186e7bdcc2639307b9d3530cdf13d3acff7ce8d0 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 09:48:43 +0200
Subject: [PATCH 06/12] [libc++][test] Pin signed-only contract for std::abs
_BitInt SFINAE
Add static_asserts that unsigned _BitInt(32) and unsigned _BitInt(64)
do NOT match the new abs template. Without these, a regression in
__is_signed_integer_v that started accepting unsigned _BitInt would
slip past every other assertion in the file. Caught by an independent
review pass that asked: "what bug would all existing tests miss?"
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/test/std/numerics/c.math/abs.pass.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index 91147dafabc1a..ea6df2a348415 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -58,6 +58,10 @@ constexpr bool has_abs<T, decltype((void)std::abs(T(0)))> = true;
static_assert(!has_abs<signed _BitInt(7)>);
static_assert(has_abs<signed _BitInt(32)>);
static_assert(has_abs<signed _BitInt(64)>);
+// Pin the signed-only contract of __is_signed_integer_v: a regression that
+// accepted unsigned _BitInt would otherwise slip past every other test here.
+static_assert(!has_abs<unsigned _BitInt(32)>);
+static_assert(!has_abs<unsigned _BitInt(64)>);
template <int N>
void test_signed_bitint() {
>From c2de399a37441ee08b77426cf7695c08cfdf34d5 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 09:52:33 +0200
Subject: [PATCH 07/12] [libc++][NFC] Tighten comments on std::abs _BitInt
overload and tests
Cut the 10-line abs.h block to 4 (just the load-bearing exclusion rationale)
and drop restatement-style narration from the test file. libc++ headers and
tests use 1-2 line comment blocks as the norm; the verbose blocks here were
re-explaining the spec/code instead of carrying a non-obvious WHY.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__math/abs.h | 14 ++++--------
libcxx/test/std/numerics/c.math/abs.pass.cpp | 23 +++++---------------
2 files changed, 9 insertions(+), 28 deletions(-)
diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index 4181173f6d82f..d4695c43c3a1c 100644
--- a/libcxx/include/__math/abs.h
+++ b/libcxx/include/__math/abs.h
@@ -65,16 +65,10 @@ template <class = int>
return __builtin_llabs(__x);
}
-// Overload for __int128 and signed _BitInt(N) where sizeof(_Tp) >=
-// sizeof(int). The sizeof check excludes shorter signed types (signed
-// short, signed _BitInt(16)). The standard overloads catch the standard
-// types via integer promotion. _BitInt narrower than int does not
-// promote and is intentionally unsupported by std::abs.
-//
-// The explicit !is_same exclusions for int/long/long long are
-// load-bearing: signed _BitInt(33..64) shares sizeof with long long, so
-// a sizeof-only gate would let those types compete with the existing
-// builtin-based overloads.
+// Covers __int128 and signed _BitInt(N) with sizeof >= sizeof(int).
+// The int/long/long long exclusions are load-bearing: _BitInt(33..64)
+// shares sizeof with long long and would otherwise compete with the
+// builtin overload.
template <class _Tp,
__enable_if_t<__is_signed_integer_v<_Tp> && !is_same<_Tp, int>::value && !is_same<_Tp, long>::value &&
!is_same<_Tp, long long>::value && (sizeof(_Tp) >= sizeof(int)),
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index ea6df2a348415..284246f523fa2 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -37,20 +37,14 @@ void test_big() {
assert(std::abs(negative_big_value) == big_value); // make sure it doesn't get casted to a smaller type
}
-// The libc++ extension overload covers signed integer types >= sizeof(int)
-// that aren't matched by the standard abs(int/long/long long): __int128 and
-// signed _BitInt(N). Shorter types still go through abs(int) via integer
-// promotion -- the new template's sizeof gate must keep them out.
+// Narrow signed types stay on the abs(int) promotion path.
template <class T>
constexpr bool unpromoted_abs = std::is_same<decltype(std::abs(T(0))), T>::value;
static_assert(!unpromoted_abs<signed char>);
static_assert(!unpromoted_abs<short>);
#ifdef __BITINT_MAXWIDTH__
-// _BitInt(N) does not participate in integer promotion. The template's
-// sizeof(_Tp) >= sizeof(int) gate determines membership, not bit width:
-// sizeof(_BitInt(31)) == sizeof(int) on x86_64 so the overload includes it.
-// Probe via SFINAE on widths whose sizeof is unambiguous.
+// Gate is sizeof, not bit width: sizeof(_BitInt(31)) == sizeof(int) on x86_64.
template <class T, class = void>
constexpr bool has_abs = false;
template <class T>
@@ -58,9 +52,7 @@ constexpr bool has_abs<T, decltype((void)std::abs(T(0)))> = true;
static_assert(!has_abs<signed _BitInt(7)>);
static_assert(has_abs<signed _BitInt(32)>);
static_assert(has_abs<signed _BitInt(64)>);
-// Pin the signed-only contract of __is_signed_integer_v: a regression that
-// accepted unsigned _BitInt would otherwise slip past every other test here.
-static_assert(!has_abs<unsigned _BitInt(32)>);
+static_assert(!has_abs<unsigned _BitInt(32)>); // signed-only contract
static_assert(!has_abs<unsigned _BitInt(64)>);
template <int N>
@@ -73,9 +65,7 @@ void test_signed_bitint() {
assert(std::abs(T(-1)) == T(1));
assert(std::abs(T(-42)) == T(42));
- // T_MAX has no overflow (identity). T_MIN + 1 negates cleanly to T_MAX.
- // T_MIN itself is intentionally not tested -- -T_MIN overflows, same caveat
- // as std::abs(int) with INT_MIN.
+ // 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);
@@ -91,8 +81,7 @@ void test_int128() {
assert(std::abs(static_cast<__int128_t>(-42)) == 42);
assert(std::abs(static_cast<__int128_t>(-1)) == 1);
- // INT128_MIN is unrepresentable as a positive __int128. Skip, consistent
- // with the standard abs(int) INT_MIN caveat.
+ // 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);
@@ -136,8 +125,6 @@ int main(int, char**) {
test_big();
#ifdef __BITINT_MAXWIDTH__
- // _BitInt(N < sizeof(int) * CHAR_BIT) does not match the new template
- // (sizeof guard) and does not promote, so abs has no overload. Start at 32.
test_signed_bitint<32>();
test_signed_bitint<64>();
test_signed_bitint<33>();
>From 9d8cb1a903cef27c145fd22e15b26c10e9c60c14 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 10:04:44 +0200
Subject: [PATCH 08/12] [libc++][test] Gate std::abs _BitInt/__int128 cases on
C++17+
The variable templates and decltype-SFINAE probes are C++14+ syntax;
the new abs.h overload's __is_signed_integer_v trait also requires
C++14+ for the SFINAE to evaluate correctly. Without the gate,
generic-cxx03 and frozen-cxx03-headers fail to compile the additions
and also report ambiguous std::abs(__int128_t) calls under frozen
headers. The dropped abs.bitint.pass.cpp file used
// UNSUPPORTED: c++03,c++11,c++14,c++17 for the same reason.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/test/std/numerics/c.math/abs.pass.cpp | 35 ++++++++++++--------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index 284246f523fa2..818fc187271c0 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -37,13 +37,17 @@ void test_big() {
assert(std::abs(negative_big_value) == big_value); // make sure it doesn't get casted to a smaller type
}
+// The _BitInt/__int128 extensions and their SFINAE probes require C++17+
+// (variable templates, decltype-SFINAE) and the new abs.h template's
+// __is_signed_integer_v trait.
+#if TEST_STD_VER >= 17
// Narrow signed types stay on the abs(int) promotion path.
template <class T>
constexpr bool unpromoted_abs = std::is_same<decltype(std::abs(T(0))), T>::value;
static_assert(!unpromoted_abs<signed char>);
static_assert(!unpromoted_abs<short>);
-#ifdef __BITINT_MAXWIDTH__
+# ifdef __BITINT_MAXWIDTH__
// Gate is sizeof, not bit width: sizeof(_BitInt(31)) == sizeof(int) on x86_64.
template <class T, class = void>
constexpr bool has_abs = false;
@@ -71,9 +75,9 @@ void test_signed_bitint() {
assert(std::abs(t_max) == t_max);
assert(std::abs(t_min_plus1) == t_max);
}
-#endif
+# endif // __BITINT_MAXWIDTH__
-#ifndef TEST_HAS_NO_INT128
+# 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);
@@ -91,7 +95,8 @@ void test_int128() {
assert(std::abs(int128_max) == int128_max);
assert(std::abs(int128_min_plus1) == int128_max);
}
-#endif
+# endif // TEST_HAS_NO_INT128
+#endif // TEST_STD_VER >= 17
// The following is helpful to keep in mind:
// 1byte == char <= short <= int <= long <= long long
@@ -124,16 +129,17 @@ int main(int, char**) {
test_big();
-#ifdef __BITINT_MAXWIDTH__
+#if TEST_STD_VER >= 17
+# ifdef __BITINT_MAXWIDTH__
test_signed_bitint<32>();
test_signed_bitint<64>();
test_signed_bitint<33>();
test_signed_bitint<63>();
test_signed_bitint<65>();
-# if __BITINT_MAXWIDTH__ >= 128
+# if __BITINT_MAXWIDTH__ >= 128
test_signed_bitint<128>();
-# endif
-# if __BITINT_MAXWIDTH__ >= 256
+# endif
+# if __BITINT_MAXWIDTH__ >= 256
test_signed_bitint<129>();
test_signed_bitint<256>();
@@ -141,16 +147,17 @@ int main(int, char**) {
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
+# endif
+# if __BITINT_MAXWIDTH__ >= 1024
test_signed_bitint<512>();
test_signed_bitint<1024>();
-# endif
-#endif
+# endif
+# endif // __BITINT_MAXWIDTH__
-#ifndef TEST_HAS_NO_INT128
+# ifndef TEST_HAS_NO_INT128
test_int128();
-#endif
+# endif
+#endif // TEST_STD_VER >= 17
return 0;
}
>From 34d9ac248e1e6ffff10326f725636455dcd4ee7d Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 11:43:28 +0200
Subject: [PATCH 09/12] [libc++][test] Gate non-byte-aligned _BitInt abs cases
under MSan
MSan does not track _BitInt padding bits, so _BitInt(33), _BitInt(63),
_BitInt(65), and _BitInt(129) trigger false-positive
use-of-uninitialized-value reports when their value is read through
numeric_limits or printed. Same mitigation as PR #203876 applies to
max/min.pass.cpp: gate the non-byte-aligned widths on
!TEST_HAS_FEATURE(memory_sanitizer). Byte-aligned widths (32, 64, 128,
256, 512, 1024) still run under MSan.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/test/std/numerics/c.math/abs.pass.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index 818fc187271c0..001a64220cb47 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -131,16 +131,22 @@ int main(int, char**) {
#if TEST_STD_VER >= 17
# ifdef __BITINT_MAXWIDTH__
+ // MSan does not track _BitInt padding bits; non-byte-aligned widths trigger
+ // false-positive use-of-uninitialized-value reports through numeric_limits.
test_signed_bitint<32>();
test_signed_bitint<64>();
+# if !TEST_HAS_FEATURE(memory_sanitizer)
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.
>From b03790467e7f8ba61b3bfe2d0b28d0bcf7af0951 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 19:47:27 +0200
Subject: [PATCH 10/12] [libc++] Split std::abs _BitInt/__int128 into explicit
overloads
Replace the single SFINAE template (__is_signed_integer_v plus is_same
exclusions and a sizeof gate) with an explicit abs(__int128_t) overload and
an explicit abs(signed _BitInt(_Np)) that deduces the width. The signed-only
and sizeof contract is unchanged; the !is_same exclusions and the
integer_traits.h/is_same.h includes are no longer needed.
The _BitInt overload sits behind __BITINT_MAXWIDTH__, which only Clang
defines in C++, so the gcc CI config skips it.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__math/abs.h | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index d4695c43c3a1c..e7769d5f0a04f 100644
--- a/libcxx/include/__math/abs.h
+++ b/libcxx/include/__math/abs.h
@@ -11,9 +11,7 @@
#include <__config>
#include <__type_traits/enable_if.h>
-#include <__type_traits/integer_traits.h>
#include <__type_traits/is_integral.h>
-#include <__type_traits/is_same.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -65,17 +63,18 @@ template <class = int>
return __builtin_llabs(__x);
}
-// Covers __int128 and signed _BitInt(N) with sizeof >= sizeof(int).
-// The int/long/long long exclusions are load-bearing: _BitInt(33..64)
-// shares sizeof with long long and would otherwise compete with the
-// builtin overload.
-template <class _Tp,
- __enable_if_t<__is_signed_integer_v<_Tp> && !is_same<_Tp, int>::value && !is_same<_Tp, long>::value &&
- !is_same<_Tp, long long>::value && (sizeof(_Tp) >= sizeof(int)),
- int> = 0>
-[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _Tp abs(_Tp __x) _NOEXCEPT {
+#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__)
+// Narrower widths (e.g. _BitInt(7)) stay unsupported, like signed types narrower
+// than int with no same-type abs. The gate is on sizeof, not bit width.
+template <int _Np, __enable_if_t<(sizeof(signed _BitInt(_Np)) >= sizeof(int)), int> = 0>
+[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI signed _BitInt(_Np) abs(signed _BitInt(_Np) __x) _NOEXCEPT {
return __x < 0 ? -__x : __x;
}
+#endif
} // namespace __math
>From 14c3dfc523d6f8791d32ef70892352a4cbb6b00e Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 19:47:27 +0200
Subject: [PATCH 11/12] [libc++][test] Link MSan _BitInt false-positive report
Point the MSan width gate at #204217 (MSan flags the uninitialized padding
bits of a non-byte-aligned _BitInt passed by value) and describe the real
mechanism instead of numeric_limits. Drop the stale __is_signed_integer_v
reference from the extension comment.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/test/std/numerics/c.math/abs.pass.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index 001a64220cb47..128dec7c265b3 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -37,9 +37,8 @@ void test_big() {
assert(std::abs(negative_big_value) == big_value); // make sure it doesn't get casted to a smaller type
}
-// The _BitInt/__int128 extensions and their SFINAE probes require C++17+
-// (variable templates, decltype-SFINAE) and the new abs.h template's
-// __is_signed_integer_v trait.
+// std::abs has __int128/_BitInt(N) overloads as a libc++ extension. The SFINAE
+// probes below need C++17+ (variable templates, decltype-SFINAE).
#if TEST_STD_VER >= 17
// Narrow signed types stay on the abs(int) promotion path.
template <class T>
@@ -131,8 +130,9 @@ int main(int, char**) {
#if TEST_STD_VER >= 17
# ifdef __BITINT_MAXWIDTH__
- // MSan does not track _BitInt padding bits; non-byte-aligned widths trigger
- // false-positive use-of-uninitialized-value reports through numeric_limits.
+ // 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<32>();
test_signed_bitint<64>();
# if !TEST_HAS_FEATURE(memory_sanitizer)
>From 2ae827d689e5cd14ff66ed950d99ef4aefb1bf42 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Tue, 16 Jun 2026 20:52:37 +0200
Subject: [PATCH 12/12] [libc++] Drop sizeof gate on std::abs _BitInt overload
The sizeof(_BitInt(_Np)) >= sizeof(int) constraint was a leftover from
the earlier single-template form, where it kept signed char and short on
the integer-promotion path to abs(int). The explicit abs(signed
_BitInt(_Np)) overload only ever binds _BitInt, so that protection is no
longer needed.
Its only remaining effect was on narrow _BitInt. Those do not
integer-promote, so std::abs on a width below sizeof(int) was an
ambiguous call against abs(int/long/long long) instead of returning the
same type. Cover all signed _BitInt(N) widths and extend the test down
to the narrow ones.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__math/abs.h | 6 +++---
libcxx/test/std/numerics/c.math/abs.pass.cpp | 9 +++++++--
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/libcxx/include/__math/abs.h b/libcxx/include/__math/abs.h
index e7769d5f0a04f..6069d17519d33 100644
--- a/libcxx/include/__math/abs.h
+++ b/libcxx/include/__math/abs.h
@@ -68,9 +68,9 @@ template <class = int>
#endif
#if defined(__BITINT_MAXWIDTH__)
-// Narrower widths (e.g. _BitInt(7)) stay unsupported, like signed types narrower
-// than int with no same-type abs. The gate is on sizeof, not bit width.
-template <int _Np, __enable_if_t<(sizeof(signed _BitInt(_Np)) >= sizeof(int)), int> = 0>
+// _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 signed _BitInt(_Np) abs(signed _BitInt(_Np) __x) _NOEXCEPT {
return __x < 0 ? -__x : __x;
}
diff --git a/libcxx/test/std/numerics/c.math/abs.pass.cpp b/libcxx/test/std/numerics/c.math/abs.pass.cpp
index 128dec7c265b3..57bcd9a01223c 100644
--- a/libcxx/test/std/numerics/c.math/abs.pass.cpp
+++ b/libcxx/test/std/numerics/c.math/abs.pass.cpp
@@ -47,12 +47,14 @@ static_assert(!unpromoted_abs<signed char>);
static_assert(!unpromoted_abs<short>);
# ifdef __BITINT_MAXWIDTH__
-// Gate is sizeof, not bit width: sizeof(_BitInt(31)) == sizeof(int) on x86_64.
+// 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>
constexpr bool has_abs = false;
template <class T>
constexpr bool has_abs<T, decltype((void)std::abs(T(0)))> = true;
-static_assert(!has_abs<signed _BitInt(7)>);
+static_assert(has_abs<signed _BitInt(2)>);
+static_assert(has_abs<signed _BitInt(7)>);
static_assert(has_abs<signed _BitInt(32)>);
static_assert(has_abs<signed _BitInt(64)>);
static_assert(!has_abs<unsigned _BitInt(32)>); // signed-only contract
@@ -133,9 +135,12 @@ int main(int, char**) {
// 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>();
More information about the libcxx-commits
mailing list