[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