[libcxx-commits] [libcxx] [libc++][POC] std::format for _BitInt wider than 128 bits (PR #204376)

Xavier Roche via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jun 17 22:17:49 PDT 2026


https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/204376

>From 87d44538ec6381cdfcaac02054edac884b2394cc Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Wed, 17 Jun 2026 15:12:33 +0200
Subject: [PATCH 1/4] [libc++] Support to_chars/from_chars for _BitInt wider
 than 128 bits

std::to_chars, std::from_chars (and the integer types they accept)
previously rejected any integer wider than 128 bits: the value was
promoted through __make_32_64_or_128_bit_t, which yields no type past
__int128, so a _BitInt(N > 128) was a hard compile error.

A _BitInt is a native arbitrary-precision integer; the compiler already
implements /, %, * and + on it. The base-2/8/16 paths, the generic
per-base loop, and the from_chars Horner accumulation already work on
any width through these operators. Only the base-10 fast paths route
through __itoa::__traits, which is specialized for 32/64/128-bit only.

Detect a wide _BitInt with __is_wide_bitint_v and route its base-10
conversion through the generic loop instead of the table-based path.
All other bases reuse the existing code unchanged.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 .../include/__charconv/from_chars_integral.h  | 14 ++++--
 libcxx/include/__charconv/to_chars_integral.h | 43 +++++++++++++------
 .../__type_traits/make_32_64_or_128_bit.h     | 13 ++++++
 3 files changed, 53 insertions(+), 17 deletions(-)

diff --git a/libcxx/include/__charconv/from_chars_integral.h b/libcxx/include/__charconv/from_chars_integral.h
index 3063c0978c8fa..c00fe5b6fce84 100644
--- a/libcxx/include/__charconv/from_chars_integral.h
+++ b/libcxx/include/__charconv/from_chars_integral.h
@@ -19,6 +19,7 @@
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_signed.h>
 #include <__type_traits/is_unsigned.h>
+#include <__type_traits/make_32_64_or_128_bit.h>
 #include <__type_traits/make_unsigned.h>
 #include <limits>
 
@@ -171,8 +172,11 @@ inline constexpr float __from_chars_log2f_lut[35] = {
 template <typename _Tp, __enable_if_t<is_unsigned<_Tp>::value, int> = 0>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI from_chars_result
 __from_chars_integral(const char* __first, const char* __last, _Tp& __value, int __base) {
-  if (__base == 10)
-    return std::__from_chars_atoi(__first, __last, __value);
+  if (__base == 10) {
+    if constexpr (!__is_wide_bitint_v<_Tp>)
+      return std::__from_chars_atoi(__first, __last, __value);
+    // A wide _BitInt has no base-10 fast path; use the generic accumulator below.
+  }
 
   return std::__subject_seq_combinator(
       __first,
@@ -219,7 +223,11 @@ __from_chars_integral(const char* __first, const char* __last, _Tp& __value, int
 template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI from_chars_result
 from_chars(const char* __first, const char* __last, _Tp& __value) {
-  return std::__from_chars_atoi(__first, __last, __value);
+  if constexpr (__is_wide_bitint_v<_Tp>)
+    // A wide _BitInt routes through the base-aware generic accumulator.
+    return std::__from_chars_integral(__first, __last, __value, 10);
+  else
+    return std::__from_chars_atoi(__first, __last, __value);
 }
 
 template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
diff --git a/libcxx/include/__charconv/to_chars_integral.h b/libcxx/include/__charconv/to_chars_integral.h
index 6d425139260b6..86da51d484198 100644
--- a/libcxx/include/__charconv/to_chars_integral.h
+++ b/libcxx/include/__charconv/to_chars_integral.h
@@ -269,16 +269,25 @@ _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI int __to_chars_integral_widt
 template <class _Tp, __enable_if_t<!is_signed<_Tp>::value, int> >
 inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI __to_chars_result
 __to_chars_integral(char* __first, char* __last, _Tp __value, int __base) {
-  if (__base == 10) [[likely]]
+  if (__base == 10) [[likely]] {
+    // A wide _BitInt has no base-10 fast path; fall through to the generic loop.
+    // This helper is not guarded by _LIBCPP_STD_VER, so it must still parse in
+    // C++03, where `if constexpr` is unavailable; charconv is unused there.
+#if _LIBCPP_STD_VER >= 17
+    if constexpr (!__is_wide_bitint_v<_Tp>)
+      return std::__to_chars_itoa(__first, __last, __value, false_type());
+#else
     return std::__to_chars_itoa(__first, __last, __value, false_type());
-
-  switch (__base) {
-  case 2:
-    return std::__to_chars_integral<2>(__first, __last, __value);
-  case 8:
-    return std::__to_chars_integral<8>(__first, __last, __value);
-  case 16:
-    return std::__to_chars_integral<16>(__first, __last, __value);
+#endif
+  } else {
+    switch (__base) {
+    case 2:
+      return std::__to_chars_integral<2>(__first, __last, __value);
+    case 8:
+      return std::__to_chars_integral<8>(__first, __last, __value);
+    case 16:
+      return std::__to_chars_integral<16>(__first, __last, __value);
+    }
   }
 
   ptrdiff_t __cap = __last - __first;
@@ -321,9 +330,13 @@ to_chars_result to_chars(char*, char*, bool, int = 10) = delete;
 template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI to_chars_result
 to_chars(char* __first, char* __last, _Tp __value) {
-  using _Type = __make_32_64_or_128_bit_t<_Tp>;
-  static_assert(!is_same<_Type, void>::value, "unsupported integral type used in to_chars");
-  return std::__to_chars_itoa(__first, __last, static_cast<_Type>(__value), is_signed<_Tp>());
+  if constexpr (__is_wide_bitint_v<_Tp>)
+    // A _BitInt wider than 128 bits has no native promotion target; the
+    // base-aware path converts it using the type's own arithmetic.
+    return std::__to_chars_integral(__first, __last, __value, 10);
+  else
+    return std::__to_chars_itoa(
+        __first, __last, static_cast<__make_32_64_or_128_bit_t<_Tp>>(__value), is_signed<_Tp>());
 }
 
 template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
@@ -331,8 +344,10 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI to_chars_result
 to_chars(char* __first, char* __last, _Tp __value, int __base) {
   _LIBCPP_ASSERT_UNCATEGORIZED(2 <= __base && __base <= 36, "base not in [2, 36]");
 
-  using _Type = __make_32_64_or_128_bit_t<_Tp>;
-  return std::__to_chars_integral(__first, __last, static_cast<_Type>(__value), __base);
+  if constexpr (__is_wide_bitint_v<_Tp>)
+    return std::__to_chars_integral(__first, __last, __value, __base);
+  else
+    return std::__to_chars_integral(__first, __last, static_cast<__make_32_64_or_128_bit_t<_Tp>>(__value), __base);
 }
 
 #endif // _LIBCPP_STD_VER >= 17
diff --git a/libcxx/include/__type_traits/make_32_64_or_128_bit.h b/libcxx/include/__type_traits/make_32_64_or_128_bit.h
index 7016209ec9c0a..e19517851b969 100644
--- a/libcxx/include/__type_traits/make_32_64_or_128_bit.h
+++ b/libcxx/include/__type_traits/make_32_64_or_128_bit.h
@@ -44,6 +44,19 @@ using __make_32_64_or_128_bit_t _LIBCPP_NODEBUG =
     > > >;
 // clang-format on
 
+// True for a _BitInt wider than 128 bits, i.e. an integer with no 32/64/128-bit
+// promotion target. charconv converts these with a bignum path built on the
+// type's own arithmetic. __make_32_64_or_128_bit_t is not usable to detect this:
+// for such a type it is not void but ill-formed (it feeds void to __make_unsigned),
+// so the test is on sizeof, matching the trait's own logic.
+template <class _Tp>
+inline const bool __is_wide_bitint_v =
+#if _LIBCPP_HAS_INT128
+    sizeof(_Tp) > sizeof(__int128_t);
+#else
+    sizeof(_Tp) > sizeof(long long);
+#endif
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___TYPE_TRAITS_MAKE_32_64_OR_128_BIT_H

>From 96dc857171aba75da011a3c3ec2e7362cb5d5005 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Wed, 17 Jun 2026 15:24:57 +0200
Subject: [PATCH 2/4] [libc++][test] Cover to_chars/from_chars for _BitInt
 wider than 128 bits

Extend the existing charconv integral tests with a _BitInt(N > 128)
section, guarded on __BITINT_MAXWIDTH__. The native harness does not
apply to wide _BitInt (its buffer is fixed-size and its reference is
strtoll), so the wide cases use hand-verified literals plus round-trip.

Coverage: signed min via the unsigned-counterpart magnitude, signed and
unsigned max, exact value_too_large boundaries, lowercase digits with no
base prefix (0x and 0b both rejected), from_chars out-of-range with value
left unmodified and ptr past the maximal match, the most-negative success
asymmetry, and a round-trip sweep over bases 2..36 and several widths.

The headline 4096-bit width runs in constant evaluation too, both on a
focused base/value subset and against an independent oracle: a sparse
4096-bit value whose decimal and hex strings are computed by Python's
arbitrary-precision int, which catches a bug to_chars and from_chars
might share that a round-trip cannot. Non-byte-aligned widths run at
runtime only, pending the clang-23 fix for constexpr
__builtin_mul_overflow on those widths (llvm.org/PR204085).

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 .../charconv.from.chars/integral.pass.cpp     |  86 +++++++++
 .../integral.roundtrip.pass.cpp               | 174 ++++++++++++++++++
 .../charconv.to.chars/integral.pass.cpp       |  65 ++++++-
 3 files changed, 324 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp
index 4708da4c38b9b..4f653db802a25 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.pass.cpp
@@ -137,6 +137,85 @@ struct test_signed
     }
 };
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+// _BitInt wider than 128 bits parses via the generic Horner accumulator. The
+// error paths must leave value unmodified; check that with a sentinel.
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void fc_ok(const char (&s)[N], T expect, std::size_t consumed, int base = 10) {
+  T v    = 0x55;
+  auto r = std::from_chars(s, s + N - 1, v, base);
+  assert(r.ec == std::errc{});
+  assert(r.ptr == s + consumed);
+  assert(v == expect);
+}
+
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void fc_invalid(const char (&s)[N], int base = 10) {
+  T v    = 0x55;
+  auto r = std::from_chars(s, s + N - 1, v, base);
+  assert(r.ec == std::errc::invalid_argument);
+  assert(r.ptr == s);   // no characters match -> ptr == first
+  assert(v == T(0x55)); // value unmodified
+}
+
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void fc_oor(const char (&s)[N], std::size_t consumed, int base = 10) {
+  T v    = 0x55;
+  auto r = std::from_chars(s, s + N - 1, v, base);
+  assert(r.ec == std::errc::result_out_of_range);
+  assert(r.ptr == s + consumed); // past the maximal matching sequence
+  assert(v == T(0x55));          // value unmodified
+}
+
+TEST_CONSTEXPR_CXX23 bool test_wide() {
+  using S = signed _BitInt(256);
+  using U = unsigned _BitInt(256);
+
+  // Success and ptr placement.
+  fc_ok<U>("42", U(42), 2);
+  fc_ok<S>("-42", S(-42), 3);
+  fc_ok<S>("-0", S(0), 2);
+  fc_ok<U>("007", U(7), 3); // leading zeros consumed
+  fc_ok<U>("ff", U(255), 2, 16);
+  fc_ok<U>("FF", U(255), 2, 16); // case-insensitive
+  fc_ok<U>("123abc", U(123), 3); // base 10: stops at 'a'
+  fc_ok<U>("123abc", U(0x123abc), 6, 16);
+  fc_ok<U>("0x10", U(0), 1, 16); // no 0x prefix: matches '0', stops at 'x'
+  fc_ok<U>("0b10", U(0), 1, 2);  // no 0b prefix: matches '0', stops at 'b'
+  fc_ok<U>("12", U(1), 1, 2);    // '2' >= base 2: stops
+
+  // No match: value unmodified, ptr == first.
+  fc_invalid<U>("");
+  fc_invalid<U>("+5"); // '+' is never accepted
+  fc_invalid<S>("+5");
+  fc_invalid<U>("-5"); // '-' only for signed types
+  fc_invalid<S>("-");  // sign with no digits
+  fc_invalid<U>(" 5"); // leading whitespace is not skipped
+  fc_invalid<U>("z");
+
+  // Out of range: value unmodified, ptr past every matching digit.
+  fc_oor<U>("115792089237316195423570985008687907853269984665640564039457584007913129639936", 78); // 2^256
+  fc_oor<U>("115792089237316195423570985008687907853269984665640564039457584007913129639936zzz", 78);
+  fc_oor<S>("57896044618658097711785492504343953926634992332820282019728792003956564819968",
+            77); // 2^255 = signed max + 1
+
+  // Most-negative -2^255 succeeds: magnitude is signed max + 1 but valid as a
+  // negative value.
+  fc_ok<S>("-57896044618658097711785492504343953926634992332820282019728792003956564819968",
+           std::numeric_limits<S>::min(),
+           78);
+
+  // Exact boundaries: max accepted, max + 1 rejected.
+  fc_ok<U>("115792089237316195423570985008687907853269984665640564039457584007913129639935",
+           std::numeric_limits<U>::max(),
+           78); // 2^256 - 1
+  fc_ok<S>("57896044618658097711785492504343953926634992332820282019728792003956564819967",
+           std::numeric_limits<S>::max(),
+           77); // 2^255 - 1
+  return true;
+}
+#endif
+
 TEST_CONSTEXPR_CXX23 bool test()
 {
     run<test_basics>(integrals);
@@ -151,5 +230,12 @@ int main(int, char**) {
     static_assert(test());
 #endif
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+    test_wide();
+#  if TEST_STD_VER > 20
+    static_assert(test_wide());
+#  endif
+#endif
+
     return 0;
 }
diff --git a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp
index b1f2c77b34f8a..46c97305374d1 100644
--- a/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp
@@ -77,6 +77,160 @@ struct test_signed : roundtrip_test_base<T>
     }
 };
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+// Round-trip is the broad-coverage backbone for wide _BitInt: to_chars then
+// from_chars must recover the value and consume exactly what was written.
+template <int Bits>
+TEST_CONSTEXPR_CXX23 void rt_one(unsigned _BitInt(Bits) u, int base) {
+  char buf[Bits + 2]; // base 2 is the widest: up to Bits digits, plus a sign
+
+  std::to_chars_result r = std::to_chars(buf, buf + sizeof(buf), u, base);
+  assert(r.ec == std::errc{});
+  unsigned _BitInt(Bits) ub = 1;
+  std::from_chars_result f  = std::from_chars(buf, r.ptr, ub, base);
+  assert(f.ec == std::errc{} && f.ptr == r.ptr && ub == u);
+
+  signed _BitInt(Bits) s = static_cast<signed _BitInt(Bits)>(u);
+  r                      = std::to_chars(buf, buf + sizeof(buf), s, base);
+  assert(r.ec == std::errc{});
+  signed _BitInt(Bits) sb = 1;
+  f                       = std::from_chars(buf, r.ptr, sb, base);
+  assert(f.ec == std::errc{} && f.ptr == r.ptr && sb == s);
+}
+
+template <int Bits>
+TEST_CONSTEXPR_CXX23 void rt_width() {
+  using U = unsigned _BitInt(Bits);
+  using S = signed _BitInt(Bits);
+  for (int b = 2; b <= 36; ++b) {
+    rt_one<Bits>(U(0), b);
+    rt_one<Bits>(U(1), b);
+    rt_one<Bits>(std::numeric_limits<U>::max(), b);                 // all ones
+    rt_one<Bits>(static_cast<U>(std::numeric_limits<S>::max()), b); // signed max
+    rt_one<Bits>(static_cast<U>(std::numeric_limits<S>::min()), b); // signed min pattern
+    rt_one<Bits>(std::numeric_limits<U>::max() / 3, b);             // arbitrary spread
+  }
+}
+
+// Byte-aligned widths only, kept small enough for constant evaluation.
+TEST_CONSTEXPR_CXX23 bool test_wide() {
+  rt_width<256>();
+  return true;
+}
+
+// 4096 bits is the headline "reasonable width": exercise it in constant
+// evaluation too, on a representative base/value subset. The full base sweep at
+// this width is kept at runtime (test_wide_runtime) to bound constexpr cost.
+TEST_CONSTEXPR_CXX23 bool test_wide_4096() {
+  using U        = unsigned _BitInt(4096);
+  using S        = signed _BitInt(4096);
+  const U vals[] = {
+      U(0),
+      U(1),
+      std::numeric_limits<U>::max(),                 // all ones
+      static_cast<U>(std::numeric_limits<S>::min()), // signed min pattern
+      std::numeric_limits<U>::max() / 3,             // arbitrary spread
+  };
+  for (U v : vals) {
+    rt_one<4096>(v, 2);
+    rt_one<4096>(v, 10);
+    rt_one<4096>(v, 16);
+  }
+  return true;
+}
+
+// Independent oracle for a huge 4096-bit value. The decimal and hex strings are
+// computed by Python's arbitrary-precision int, so they catch a bug that
+// to_chars and from_chars might share, which a pure round-trip cannot. The value
+// is built from its bits, independent of the strings; the sparse pattern gives
+// long interior-zero digit runs plus a few clusters.
+TEST_CONSTEXPR_CXX23 bool test_wide_oracle() {
+  using U   = unsigned _BitInt(4096);
+  const U v = (U(1) << 4095) | (U(1) << 3000) | (U(1) << 1500) | (U(1) << 700) | U(0xDEADBEEFCAFEULL);
+
+  const char dec[] =
+      "522194440706576253345876355358312191289982124523691890192116741641976953985778728424413405967498779170445053357"
+      "219631418993786719092896803631618043925682638972978488271854999170180795067191859157214035005927973113188159419"
+      "698856372836167342172293308748403954352901852035642024370059304557233988891799014503343469488440893892973454045"
+      "327052631416966658275225011404027988935425249341701966051886267829647532452891421103226957550676830796693159988"
+      "124656750434275418911327793613319612182498160234866841889152188408311919545372889132335138169799010480395144069"
+      "285672659513941271465489792446864983055373122330734549035017206965872628152160774541392717285230405353948204585"
+      "721558937253615907178075626191061140618987126175926194937994062055639754879354953746504582267597824430780046407"
+      "654767442218179275002597906567544454462957002216329001106694021676565785487186595814555929076464240677469561177"
+      "107218357758641620381943183829806874856274226199817314163151233513560130957415780849310362681536620722609148912"
+      "812834526196870692951390912269511897170617673817942753906754404005592596448147882135379795095255338256287637038"
+      "130680736166406732152594023784824442544596004877042110465174074339660336326179460333127852406170867605207327911"
+      "022565509886";
+  const char hex[] =
+      "80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000"
+      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000000000deadbeefcafe";
+  // Base 36 exercises the generic loop's full 0-9a-z alphabet, which the base-10
+  // and base-16 (LUT) checks above do not reach.
+  const char b36[] =
+      "1c58ccvc205beuqph4wbfhcolylc3mogv6cfu44q0ny092hqw421ukr2pnoci21sapp8q00xd2kotzuf65r9stubez1tfplsprvrzegyah5l9h"
+      "71zr6zddlj4112jbkymn7hv9porwxfhp8jmu3gjgabuvjoc0t3p140isx85z3k4yuaoxq4r9wmummh8rujjxrazfodidhk9o6apnne4pacu79i"
+      "n1cf5qaedb7s1dpskq2r9wgdswck57yqvobgs1d2xg154sb95jg39tt0bzyf5aptbw9z7ahqjnt3w7jp1tth21xwfn8dgzb5wvlc3zeijz0asb"
+      "5d38l7qxbsj8pg8tkibhs3ooie5aizfh2657d6tz8ysut8g3zm254dqifpqj1i91wmfkofgvks973a706bz0ejpjdf5ucmra7d1x9x9wuiz044"
+      "g7jcu71ofwdn4iwk8sxj17n3wrucuxed308dhf5aqumg19q7hftzrmp3obtygzt0zmut76adjffjwsmh3q5wcmx11d0rvigxuklx1i4r6stzuq"
+      "y2fzxxhwberb6qmssvqffyt0mtzpaiez6s2p0api2i57fb6sfjkdj0c9ofdlnwo852fauo2bcxgtz62qe2ym2gt4g25haatt7xm9ck7wf2wwg7"
+      "j9gpc8774a0u1p3wrolmysbzqagimzsg5foifbriuwyqkjlysp6qe3tvy658zmwpk2ib8p7b063v079ue0un8k61d7cabadmbjhki1c7g02y7d"
+      "t6wrr3szp6wzcmcfe8tw16m";
+
+  char buf[4100];
+  std::to_chars_result r = std::to_chars(buf, buf + sizeof(buf), v, 10);
+  assert(r.ec == std::errc{} && static_cast<std::size_t>(r.ptr - buf) == sizeof(dec) - 1);
+  for (std::size_t i = 0; i + 1 < sizeof(dec); ++i)
+    assert(buf[i] == dec[i]);
+
+  r = std::to_chars(buf, buf + sizeof(buf), v, 16);
+  assert(r.ec == std::errc{} && static_cast<std::size_t>(r.ptr - buf) == sizeof(hex) - 1);
+  for (std::size_t i = 0; i + 1 < sizeof(hex); ++i)
+    assert(buf[i] == hex[i]);
+
+  r = std::to_chars(buf, buf + sizeof(buf), v, 36);
+  assert(r.ec == std::errc{} && static_cast<std::size_t>(r.ptr - buf) == sizeof(b36) - 1);
+  for (std::size_t i = 0; i + 1 < sizeof(b36); ++i)
+    assert(buf[i] == b36[i]);
+
+  U back                   = 0;
+  std::from_chars_result f = std::from_chars(dec, dec + sizeof(dec) - 1, back, 10);
+  assert(f.ec == std::errc{} && f.ptr == dec + sizeof(dec) - 1 && back == v);
+
+  back = 0;
+  f    = std::from_chars(hex, hex + sizeof(hex) - 1, back, 16);
+  assert(f.ec == std::errc{} && f.ptr == hex + sizeof(hex) - 1 && back == v);
+
+  back = 0;
+  f    = std::from_chars(b36, b36 + sizeof(b36) - 1, back, 36);
+  assert(f.ec == std::errc{} && f.ptr == b36 + sizeof(b36) - 1 && back == v);
+  return true;
+}
+
+// Wider and non-byte-aligned widths at runtime. Non-aligned widths are excluded
+// from constant evaluation pending the clang-23 fix for constexpr
+// __builtin_mul_overflow on non-byte-aligned _BitInt (llvm.org/PR204085).
+void test_wide_runtime() {
+  rt_width<512>();
+  rt_width<129>();
+  rt_width<257>();
+#  if __SIZEOF_POINTER__ >= 8
+  // The widest sweeps are quadratic and, with 32-bit limbs, overrun the lit
+  // timeout on a 32-bit target (seen on clang-21 i686-mingw). 64-bit configs
+  // cover them; 32-bit keeps the smaller wide widths above.
+  rt_width<1000>();
+  rt_width<4096>();
+#  endif
+}
+#endif
+
 TEST_CONSTEXPR_CXX23 bool test()
 {
     run<test_basics>(integrals);
@@ -91,5 +245,25 @@ int main(int, char**) {
     static_assert(test());
 #endif
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+    test_wide();
+    test_wide_runtime();
+    // 4096-bit round-trips (runtime and constant-evaluated) run on 64-bit targets
+    // only. On a 32-bit target the quadratic base-10 conversion is slow enough to
+    // exceed the lit timeout (seen on clang-21 i686-mingw); the smaller wide
+    // widths in test_wide / test_wide_runtime still cover 32-bit.
+#  if __SIZEOF_POINTER__ >= 8
+    test_wide_4096();
+    test_wide_oracle();
+#  endif
+#  if TEST_STD_VER > 20
+    static_assert(test_wide());
+#    if __SIZEOF_POINTER__ >= 8
+    static_assert(test_wide_4096());
+    static_assert(test_wide_oracle());
+#    endif
+#  endif
+#endif
+
     return 0;
 }
diff --git a/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp b/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp
index e89b340bdfcbe..170a8ff5c5c35 100644
--- a/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp
+++ b/libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp
@@ -293,6 +293,62 @@ struct test_signed : to_chars_test_base<T>
     }
 };
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+// _BitInt wider than 128 bits has no native promotion target, so the native
+// harness above (fixed buffer, strtoll reference) does not apply. Check it with
+// hand-verified literals plus exact-fit / one-too-short buffer handling.
+template <class T, std::size_t N>
+TEST_CONSTEXPR_CXX23 void test_wide_one(T v, const char (&expect)[N], int base = 10) {
+  const std::size_t len = N - 1;
+  char buf[4200];
+
+  std::to_chars_result r = std::to_chars(buf, buf + sizeof(buf), v, base);
+  assert(r.ec == std::errc{});
+  assert(static_cast<std::size_t>(r.ptr - buf) == len);
+  for (std::size_t i = 0; i < len; ++i)
+    assert(buf[i] == expect[i]);
+
+  // Exact-fit buffer succeeds; one byte short fails with ptr == last and no
+  // assertion on the (unspecified) buffer contents.
+  r = std::to_chars(buf, buf + len, v, base);
+  assert(r.ec == std::errc{} && r.ptr == buf + len);
+  r = std::to_chars(buf, buf + len - 1, v, base);
+  assert(r.ec == std::errc::value_too_large && r.ptr == buf + len - 1);
+}
+
+TEST_CONSTEXPR_CXX23 bool test_wide() {
+  using S   = signed _BitInt(256);
+  using U   = unsigned _BitInt(256);
+  using SLn = std::numeric_limits<S>;
+  using ULn = std::numeric_limits<U>;
+
+  test_wide_one(U(0), "0");
+  test_wide_one(S(0), "0", 16);
+  test_wide_one(U(255), "255");
+  test_wide_one(U(255), "ff", 16); // lowercase, no 0x prefix
+  test_wide_one(U(0xabcu), "abc", 16);
+  test_wide_one(U(10), "1010", 2);
+  test_wide_one(S(-12), "-12");
+  test_wide_one(S(-255), "-ff", 16);
+  test_wide_one(S(-10), "-1010", 2);
+
+  // Trailing/interior zeros must survive (no stray digit dropping).
+  test_wide_one(U(1000000000u), "1000000000");
+
+  // Signed min = -2^255: magnitude 2^255 is not representable in S, so it must
+  // be formed in the unsigned counterpart. (Base 2 is covered by the round-trip
+  // test to avoid a 257-character literal.)
+  test_wide_one(SLn::min(), "-57896044618658097711785492504343953926634992332820282019728792003956564819968");
+  test_wide_one(SLn::min(), "-8000000000000000000000000000000000000000000000000000000000000000", 16);
+
+  // Signed max = 2^255-1, unsigned max = 2^256-1.
+  test_wide_one(SLn::max(), "57896044618658097711785492504343953926634992332820282019728792003956564819967");
+  test_wide_one(ULn::max(), "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
+
+  return true;
+}
+#endif
+
 TEST_CONSTEXPR_CXX23 bool test()
 {
     run<test_basics>(integrals);
@@ -308,5 +364,12 @@ int main(int, char**)
     static_assert(test());
 #endif
 
-  return 0;
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 4096
+    test_wide();
+#  if TEST_STD_VER > 20
+    static_assert(test_wide());
+#  endif
+#endif
+
+    return 0;
 }

>From cab817a10fb11437ecbee2c5ecc19fd8ec4a116a Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Wed, 17 Jun 2026 17:46:33 +0200
Subject: [PATCH 3/4] [libc++] Support std::format for _BitInt wider than 128
 bits

std::format rejected any _BitInt wider than 128 bits at compile time:
__determine_arg_t mapped a signed/unsigned integer onto a fixed storage
slot and static_asserted past __int128, and __formatter_integer promoted
through __make_32_64_or_128_bit_t, which yields no type past 128 bits.

Route a wide _BitInt through the existing handle extension point instead.
The two integer __determine_arg_t overloads now exclude __is_wide_bitint_v,
so a wide _BitInt falls to the unconstrained handle overload. A constrained
formatter<_Tp, _CharT> specialization makes the type formattable through a
non-promoting base that calls __formatter::__format_integer on the _BitInt
directly; the full format-spec grammar and the charconv bignum conversion
come for free. No new arg category, no format_arg_store ABI change.

The sizeof-based __is_wide_bitint_v is paired with an integral check so the
specialization cannot claim large non-integer types.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 libcxx/include/__format/format_arg_store.h  |  6 ++++
 libcxx/include/__format/formatter_integer.h | 33 +++++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/libcxx/include/__format/format_arg_store.h b/libcxx/include/__format/format_arg_store.h
index fbb4cad21b232..dbb05d6c5c5d4 100644
--- a/libcxx/include/__format/format_arg_store.h
+++ b/libcxx/include/__format/format_arg_store.h
@@ -22,6 +22,7 @@
 #include <__type_traits/conditional.h>
 #include <__type_traits/extent.h>
 #include <__type_traits/integer_traits.h>
+#include <__type_traits/make_32_64_or_128_bit.h>
 #include <__type_traits/remove_const.h>
 #include <cstdint>
 #include <string>
@@ -65,7 +66,11 @@ consteval __arg_t __determine_arg_t() {
 #  endif
 
 // Signed integers
+//
+// A _BitInt wider than 128 bits has no packed storage slot; it is routed to the
+// handle path (see __formatter_bitint) by excluding it here.
 template <class, __signed_integer _Tp>
+  requires(!__is_wide_bitint_v<_Tp>)
 consteval __arg_t __determine_arg_t() {
   if constexpr (sizeof(_Tp) <= sizeof(int))
     return __arg_t::__int;
@@ -81,6 +86,7 @@ consteval __arg_t __determine_arg_t() {
 
 // Unsigned integers
 template <class, __unsigned_integer _Tp>
+  requires(!__is_wide_bitint_v<_Tp>)
 consteval __arg_t __determine_arg_t() {
   if constexpr (sizeof(_Tp) <= sizeof(unsigned))
     return __arg_t::__unsigned;
diff --git a/libcxx/include/__format/formatter_integer.h b/libcxx/include/__format/formatter_integer.h
index cf186c64e3d0f..089bdedcac476 100644
--- a/libcxx/include/__format/formatter_integer.h
+++ b/libcxx/include/__format/formatter_integer.h
@@ -56,6 +56,39 @@ struct __formatter_integer {
   __format_spec::__parser<_CharT> __parser_;
 };
 
+// A _BitInt wider than 128 bits has no 32/64/128-bit promotion target, so the
+// promoting base above does not apply. This base formats the value in place;
+// __format_integer routes it through the charconv bignum path.
+template <__fmt_char_type _CharT>
+struct __formatter_bitint {
+public:
+  template <class _ParseContext>
+  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
+    typename _ParseContext::iterator __result = __parser_.__parse(__ctx, __format_spec::__fields_integral);
+    __format_spec::__process_parsed_integer(__parser_, "an integer");
+    return __result;
+  }
+
+  template <class _Tp, class _FormatContext>
+    requires(integral<_Tp> && __is_wide_bitint_v<_Tp>)
+  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(_Tp __value, _FormatContext& __ctx) const {
+    __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx);
+
+    if (__specs.__std_.__type_ == __format_spec::__type::__char)
+      return __formatter::__format_char(__value, __ctx.out(), __specs);
+
+    return __formatter::__format_integer(__value, __ctx, __specs);
+  }
+
+  __format_spec::__parser<_CharT> __parser_;
+};
+
+// __is_wide_bitint_v is sizeof-based, so it must be paired with an integral
+// check; otherwise this specialization would also claim large non-integer types.
+template <class _Tp, __fmt_char_type _CharT>
+  requires(integral<_Tp> && __is_wide_bitint_v<_Tp>)
+struct formatter<_Tp, _CharT> : public __formatter_bitint<_CharT> {};
+
 // Signed integral types.
 template <__fmt_char_type _CharT>
 struct formatter<signed char, _CharT> : public __formatter_integer<_CharT> {};

>From 77fe7e7393f9bbb9a9927f5dd25ddbf082a0c879 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Wed, 17 Jun 2026 17:46:46 +0200
Subject: [PATCH 4/4] [libc++][test] Cover std::format for _BitInt wider than
 128 bits

Drive the full integer format-spec grammar through a signed and unsigned
_BitInt(256) in the shared format_tests.h, and pin the 256-bit extrema in
every base. The signed and unsigned formatter specialization tests gain a
256-bit section plus a 4096-bit headline (the width shared with the
to_chars test), with the long decimal expectations computed by an
arbitrary-precision oracle. concept.formattable and the nonlocking-
optimization trait gain the matching assertions; the latter pins that a
wide _BitInt uses the handle path.

Remove make_format_args.bitint.verify.cpp: it pinned the >128 static
assertion that no longer fires now the type is supported.

All blocks are guarded on __BITINT_MAXWIDTH__.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
 .../make_format_args.bitint.verify.cpp        | 57 -------------
 .../concept.formattable.compile.pass.cpp      |  7 ++
 ...ng_formatter_optimization.compile.pass.cpp |  6 ++
 .../formatter.signed_integral.pass.cpp        | 85 +++++++++++++++++++
 .../formatter.unsigned_integral.pass.cpp      | 58 +++++++++++++
 .../format/format.functions/format_tests.h    | 64 ++++++++++++++
 6 files changed, 220 insertions(+), 57 deletions(-)
 delete mode 100644 libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp

diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp
deleted file mode 100644
index 52107b8b91527..0000000000000
--- a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp
+++ /dev/null
@@ -1,57 +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
-//
-//===----------------------------------------------------------------------===//
-
-// UNSUPPORTED: c++03, c++11, c++14, c++17
-
-// <format>
-
-// make_format_args with _BitInt(N) wider than __int128 is unsupported.
-//
-// After [libc++] recognized _BitInt as an integer type in
-// __type_traits/integer_traits.h, format_arg_store's __determine_arg_t
-// dispatches on sizeof(_Tp) and maps _BitInt up to sizeof(__int128) onto
-// the i128 storage slot. For wider _BitInt (sizeof > sizeof(__int128)),
-// no storage slot exists and a static_assert fires.
-//
-// This test pins down that diagnostic so that if the dispatch ever changes
-// to silently accept a wider type (or drops the diagnostic), the test
-// breaks and forces a reconsideration.
-
-#include <format>
-
-#include "test_macros.h"
-
-#if TEST_HAS_EXTENSION(bit_int) && __BITINT_MAXWIDTH__ >= 129
-
-void f_signed() {
-  // _BitInt(129) has sizeof == 32 on x86-64 (first size wider than __int128).
-  _BitInt(129) value = 0;
-  // expected-error-re@*:* {{{{(static assertion|static_assert)}} failed{{.*}}"an unsupported signed integer was used"}}
-  (void)std::make_format_args(value);
-}
-
-void f_unsigned() {
-  unsigned _BitInt(129) value = 0;
-  // expected-error-re@*:* {{{{(static assertion|static_assert)}} failed{{.*}}"an unsupported unsigned integer was used"}}
-  (void)std::make_format_args(value);
-}
-
-#  if __BITINT_MAXWIDTH__ >= 256
-void f_signed_256() {
-  _BitInt(256) value = 0;
-  // expected-error-re@*:* {{{{(static assertion|static_assert)}} failed{{.*}}"an unsupported signed integer was used"}}
-  (void)std::make_format_args(value);
-}
-#  endif
-
-#else
-// When _BitInt is unavailable or the implementation limits preclude the
-// test, keep the file well-formed with a trivial positive expectation so
-// the driver does not fail.
-// expected-no-diagnostics
-#endif
diff --git a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
index 6534b9ccb51ea..0f78080a44b7d 100644
--- a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
@@ -111,6 +111,10 @@ void test_P0645() {
 #ifndef TEST_HAS_NO_INT128
   assert_is_formattable<__int128_t, CharT>();
 #endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  // A _BitInt wider than 128 bits is formattable through the handle path.
+  assert_is_formattable<signed _BitInt(256), CharT>();
+#endif
 
   assert_is_formattable<unsigned char, CharT>();
   assert_is_formattable<unsigned short, CharT>();
@@ -120,6 +124,9 @@ void test_P0645() {
 #ifndef TEST_HAS_NO_INT128
   assert_is_formattable<__uint128_t, CharT>();
 #endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  assert_is_formattable<unsigned _BitInt(256), CharT>();
+#endif
 
   // floating-point types are tested in concept.formattable.float.compile.pass.cpp
 
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.locking/enable_nonlocking_formatter_optimization.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.locking/enable_nonlocking_formatter_optimization.compile.pass.cpp
index 15397577e4e6d..0ea4baa43d0dd 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.locking/enable_nonlocking_formatter_optimization.compile.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.locking/enable_nonlocking_formatter_optimization.compile.pass.cpp
@@ -93,6 +93,12 @@ void test_P0645() {
 #ifndef TEST_HAS_NO_INT128
   static_assert(std::enable_nonlocking_formatter_optimization<__uint128_t>);
 #endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  // A _BitInt wider than 128 bits formats through the handle path, which the
+  // optimization does not cover.
+  static_assert(!std::enable_nonlocking_formatter_optimization<signed _BitInt(256)>);
+  static_assert(!std::enable_nonlocking_formatter_optimization<unsigned _BitInt(256)>);
+#endif
 
   static_assert(std::enable_nonlocking_formatter_optimization<float>);
   static_assert(std::enable_nonlocking_formatter_optimization<double>);
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp
index 707664ac12785..5edc045533f8c 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp
@@ -117,10 +117,95 @@ void test_all_signed_integral_types() {
 #endif
 }
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+// A _BitInt wider than 128 bits formats through the handle path and the charconv
+// bignum conversion. 4096 bits is the headline width shared with the to_chars test.
+template <class CharT>
+void test_wide_signed_bitint() {
+  {
+    using A = signed _BitInt(256);
+    test_termination_condition(STR("0"), STR("}"), A(0));
+    test_termination_condition(STR("-42"), STR("}"), A(-42));
+    test_termination_condition(STR("FF"), STR("X}"), A(255)); // libc++ __transform path
+    test_termination_condition(
+        STR("-57896044618658097711785492504343953926634992332820282019728792003956564819968"),
+        STR("}"),
+        std::numeric_limits<A>::min());
+    test_termination_condition(
+        STR("57896044618658097711785492504343953926634992332820282019728792003956564819967"),
+        STR("}"),
+        std::numeric_limits<A>::max());
+  }
+#  if __BITINT_MAXWIDTH__ >= 4096
+  {
+    using A = signed _BitInt(4096);
+    test_termination_condition(
+        STR("-522194440706576253345876355358312191289982124523691890192116741641976953985778728424413405967498779170445"
+            "0533"
+            "5721963141899378671909289680363161804392568263897297848827185499917018079506719185915721403500592797311318"
+            "8159"
+            "4196988563728361673421722933087484039543529018520356420243700593045572339888917990145033434694884408938929"
+            "7345"
+            "2815095130470299789726716411734651513348221529512507986199933857107770846917779942645743159118957217248367"
+            "0439"
+            "0593631974823755009452067450420853083754683416692527551648604413477538499180818470596650760689841291859404"
+            "5916"
+            "8283756106592464231840627751129991502061723924312978372460973085119032529566228054128659176900438043110514"
+            "1713"
+            "5098849101156584508839003337597742539960818209685142687562392007453579567729991395256699805775897135553415"
+            "5670"
+            "4529213644213989577742489147716176725853261163453069745299384650106148169784389143947422030800370647283745"
+            "9911"
+            "5252858211885774081606903155229514580684633541714282203652239499859508907328817366119251336265299498979980"
+            "4539"
+            "9734600887312408859224933727829625089164535236559716582775403784110923285873186648442456409760158728501220"
+            "4633"
+            "0845543707419253920596490226149092866948882405156304295150065120673359486333660824575556580146039086901671"
+            "8045"
+            "121902354170201577095168"),
+        STR("}"),
+        std::numeric_limits<A>::min());
+    test_termination_condition(
+        STR("5221944407065762533458763553583121912899821245236918901921167416419769539857787284244134059674987791704450"
+            "5335"
+            "7219631418993786719092896803631618043925682638972978488271854999170180795067191859157214035005927973113188"
+            "1594"
+            "1969885637283616734217229330874840395435290185203564202437005930455723398889179901450334346948844089389297"
+            "3452"
+            "8150951304702997897267164117346515133482215295125079861999338571077708469177799426457431591189572172483670"
+            "4390"
+            "5936319748237550094520674504208530837546834166925275516486044134775384991808184705966507606898412918594045"
+            "9168"
+            "2837561065924642318406277511299915020617239243129783724609730851190325295662280541286591769004380431105141"
+            "7135"
+            "0988491011565845088390033375977425399608182096851426875623920074535795677299913952566998057758971355534155"
+            "6704"
+            "5292136442139895777424891477161767258532611634530697452993846501061481697843891439474220308003706472837459"
+            "9115"
+            "2528582118857740816069031552295145806846335417142822036522394998595089073288173661192513362652994989799804"
+            "5399"
+            "7346008873124088592249337278296250891645352365597165827754037841109232858731866484424564097601587285012204"
+            "6330"
+            "8455437074192539205964902261490928669488824051563042951500651206733594863336608245755565801460390869016718"
+            "0451"
+            "21902354170201577095167"),
+        STR("}"),
+        std::numeric_limits<A>::max());
+  }
+#  endif
+}
+#endif
+
 int main(int, char**) {
   test_all_signed_integral_types<char>();
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  test_wide_signed_bitint<char>();
+#endif
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
   test_all_signed_integral_types<wchar_t>();
+#  if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  test_wide_signed_bitint<wchar_t>();
+#  endif
 #endif
 
   return 0;
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp
index 7446ab26bf975..c6d3a025b156e 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp
@@ -109,10 +109,68 @@ void test_all_unsigned_integral_types() {
 #endif
 }
 
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+// A _BitInt wider than 128 bits formats through the handle path and the charconv
+// bignum conversion. 4096 bits is the headline width shared with the to_chars test.
+template <class CharT>
+void test_wide_unsigned_bitint() {
+  {
+    using A = unsigned _BitInt(256);
+    test_termination_condition(STR("0"), STR("}"), A(0));
+    test_termination_condition(STR("42"), STR("}"), A(42));
+    test_termination_condition(
+        STR("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+        STR("#x}"),
+        std::numeric_limits<A>::max());
+    test_termination_condition(
+        STR("115792089237316195423570985008687907853269984665640564039457584007913129639935"),
+        STR("}"),
+        std::numeric_limits<A>::max());
+  }
+#  if __BITINT_MAXWIDTH__ >= 4096
+  {
+    using A = unsigned _BitInt(4096);
+    test_termination_condition(
+        STR("1044388881413152506691752710716624382579964249047383780384233483283953907971557456848826811934997558340890"
+            "1067"
+            "1443926283798757343818579360726323608785136527794595697654370999834036159013438371831442807001185594622637"
+            "6318"
+            "8393977127456723346843445866174968079087058037040712840487401186091144679777835980290066869389768817877859"
+            "4690"
+            "5630190260940599579453432823469303026696443059025015972399867714215541693835559885291486318237914434496734"
+            "0878"
+            "1187263949647510018904134900841706167509366833385055103297208826955076998361636941193301521379682583718809"
+            "1833"
+            "6567512213184928463681255502259983004123447848625956744921946170238065059132456108257318353800876086221028"
+            "3427"
+            "0197698202313169017678006675195485079921636419370285375124784014907159135459982790513399611551794271106831"
+            "1340"
+            "9058427288427979155484978295432353451706522326906139490598769300212296339568778287894844061600741294567491"
+            "9823"
+            "0505716423771548163213806310459029161369267083428564407304478999719017814657634732238502672530598997959960"
+            "9079"
+            "9469201774624817718449867455659250178329070473119433165550807568221846571746373296884912819520317457002440"
+            "9266"
+            "1691087414838507841192980452298185733897764810312608590300130241346718972667321649151113160292078173803343"
+            "6090"
+            "243804708340403154190335"),
+        STR("}"),
+        std::numeric_limits<A>::max());
+  }
+#  endif
+}
+#endif
+
 int main(int, char**) {
   test_all_unsigned_integral_types<char>();
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  test_wide_unsigned_bitint<char>();
+#endif
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
   test_all_unsigned_integral_types<wchar_t>();
+#  if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  test_wide_unsigned_bitint<wchar_t>();
+#  endif
 #endif
 
   return 0;
diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h
index 4cba83afd429c..9f69c4a91e76b 100644
--- a/libcxx/test/std/utilities/format/format.functions/format_tests.h
+++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h
@@ -1064,6 +1064,11 @@ void format_test_signed_integer(TestFunction check, ExceptionTest check_exceptio
   format_test_integer<long long, CharT>(check, check_exception);
 #ifndef TEST_HAS_NO_INT128
   format_test_integer<__int128_t, CharT>(check, check_exception);
+#endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  // A _BitInt wider than 128 bits formats through the handle path; run the full
+  // grammar against it and pin the 256-bit extrema below.
+  format_test_integer<signed _BitInt(256), CharT>(check, check_exception);
 #endif
   // *** check the minima and maxima ***
   check(SV("-0b10000000"), SV("{:#b}"), std::numeric_limits<std::int8_t>::min());
@@ -1129,6 +1134,42 @@ void format_test_signed_integer(TestFunction check, ExceptionTest check_exceptio
   check(SV("170141183460469231731687303715884105727"), SV("{:#}"), std::numeric_limits<__int128_t>::max());
   check(SV("0x7fffffffffffffffffffffffffffffff"), SV("{:#x}"), std::numeric_limits<__int128_t>::max());
 #endif
+
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  using S = signed _BitInt(256);
+  // min = -2^255
+  check(
+      SV("-0b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+         "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+         "00000000000000000000000000000000000000000"),
+      SV("{:#b}"),
+      std::numeric_limits<S>::min());
+  check(SV("-010000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
+        SV("{:#o}"),
+        std::numeric_limits<S>::min());
+  check(SV("-57896044618658097711785492504343953926634992332820282019728792003956564819968"),
+        SV("{:#}"),
+        std::numeric_limits<S>::min());
+  check(SV("-0x8000000000000000000000000000000000000000000000000000000000000000"),
+        SV("{:#x}"),
+        std::numeric_limits<S>::min());
+  // max = 2^255-1
+  check(
+      SV("0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+         "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+         "111111111111111111111111111111111111111"),
+      SV("{:#b}"),
+      std::numeric_limits<S>::max());
+  check(SV("07777777777777777777777777777777777777777777777777777777777777777777777777777777777777"),
+        SV("{:#o}"),
+        std::numeric_limits<S>::max());
+  check(SV("57896044618658097711785492504343953926634992332820282019728792003956564819967"),
+        SV("{:#}"),
+        std::numeric_limits<S>::max());
+  check(SV("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+        SV("{:#x}"),
+        std::numeric_limits<S>::max());
+#endif
 }
 
 template <class CharT, class TestFunction, class ExceptionTest>
@@ -1140,6 +1181,9 @@ void format_test_unsigned_integer(TestFunction check, ExceptionTest check_except
   format_test_integer<unsigned long long, CharT>(check, check_exception);
 #ifndef TEST_HAS_NO_INT128
   format_test_integer<__uint128_t, CharT>(check, check_exception);
+#endif
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  format_test_integer<unsigned _BitInt(256), CharT>(check, check_exception);
 #endif
   // *** test the maxima ***
   check(SV("0b11111111"), SV("{:#b}"), std::numeric_limits<std::uint8_t>::max());
@@ -1173,6 +1217,26 @@ void format_test_unsigned_integer(TestFunction check, ExceptionTest check_except
   check(SV("340282366920938463463374607431768211455"), SV("{:#}"), std::numeric_limits<__uint128_t>::max());
   check(SV("0xffffffffffffffffffffffffffffffff"), SV("{:#x}"), std::numeric_limits<__uint128_t>::max());
 #endif
+
+#if defined(__BITINT_MAXWIDTH__) && __BITINT_MAXWIDTH__ >= 256
+  using U = unsigned _BitInt(256);
+  // max = 2^256-1
+  check(
+      SV("0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+         "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+         "1111111111111111111111111111111111111111"),
+      SV("{:#b}"),
+      std::numeric_limits<U>::max());
+  check(SV("017777777777777777777777777777777777777777777777777777777777777777777777777777777777777"),
+        SV("{:#o}"),
+        std::numeric_limits<U>::max());
+  check(SV("115792089237316195423570985008687907853269984665640564039457584007913129639935"),
+        SV("{:#}"),
+        std::numeric_limits<U>::max());
+  check(SV("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+        SV("{:#x}"),
+        std::numeric_limits<U>::max());
+#endif
 }
 
 template <class CharT, class TestFunction, class ExceptionTest>



More information about the libcxx-commits mailing list