[libcxx-commits] [libcxx] fdfb99f - [libc++] std::byteswap support for _BitInt(N) (#196512)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sat May 30 02:08:11 PDT 2026
Author: Xavier Roche
Date: 2026-05-30T11:08:06+02:00
New Revision: fdfb99f6336401e534f611c4b2df5a338e26b997
URL: https://github.com/llvm/llvm-project/commit/fdfb99f6336401e534f611c4b2df5a338e26b997
DIFF: https://github.com/llvm/llvm-project/commit/fdfb99f6336401e534f611c4b2df5a338e26b997.diff
LOG: [libc++] std::byteswap support for _BitInt(N) (#196512)
Add a byte-reversal loop fallback for `std::byteswap` when `sizeof(T) >
16`,
so the function works for `_BitInt(N)` with `N > 128` and any future
wider
integer type. Without it, those calls hit `static_assert(sizeof(_Tp) ==
0)`
and fail to compile.
Reject `_BitInt(N)` where `N` is not a multiple of `CHAR_BIT`. The
existing
`__builtin_bswap{16,32,64,128}` paths swap the storage representation
including padding bits, and the resulting value's meaning is
unspecified.
A new `static_assert` catches that case and reports it. Size-1 types are
exempt from the check, since no bytes move there.
Part of the [_BitInt(N) libc++
effort](https://discourse.llvm.org/t/bitint-n-support-in-libc-investigations-possible-improvements-looking-for-guidance/90063).
Assisted-by: Claude (Anthropic)
---------
Co-authored-by: Claude Opus 4.6 <noreply at anthropic.com>
Added:
libcxx/test/std/numerics/bit/byteswap.verify.cpp
Modified:
libcxx/include/__bit/byteswap.h
libcxx/test/libcxx/transitive_includes/cxx23.csv
libcxx/test/libcxx/transitive_includes/cxx26.csv
libcxx/test/std/numerics/bit/byteswap.pass.cpp
Removed:
################################################################################
diff --git a/libcxx/include/__bit/byteswap.h b/libcxx/include/__bit/byteswap.h
index 7ce7e069b4142..43490d080910b 100644
--- a/libcxx/include/__bit/byteswap.h
+++ b/libcxx/include/__bit/byteswap.h
@@ -12,7 +12,11 @@
#include <__concepts/arithmetic.h>
#include <__config>
+#include <__type_traits/is_same.h>
+#include <__type_traits/remove_cv.h>
+#include <climits>
#include <cstdint>
+#include <limits>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -24,26 +28,41 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <integral _Tp>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp byteswap(_Tp __val) noexcept {
+ // [bit.byteswap]/Mandates: T does not have padding bits.
+ // bool is grandfathered: every shipping implementation admits it and the
+ // size-1 identity path can't shuffle padding bits into value positions.
+ // LWG 4583 proposes relaxing this to allow byte-aligned padding (e.g.
+ // _BitInt(48) where 2 whole bytes are padding); revisit once it resolves.
+ static_assert(is_same_v<remove_cv_t<_Tp>, bool> ||
+ numeric_limits<_Tp>::digits + numeric_limits<_Tp>::is_signed == sizeof(_Tp) * CHAR_BIT,
+ "std::byteswap requires T to have no padding bits");
+
if constexpr (sizeof(_Tp) == 1) {
return __val;
+# if __has_builtin(__builtin_bswapg)
+ } else {
+ return __builtin_bswapg(__val);
+ }
+# else
} else if constexpr (sizeof(_Tp) == 2) {
return __builtin_bswap16(__val);
} else if constexpr (sizeof(_Tp) == 4) {
return __builtin_bswap32(__val);
} else if constexpr (sizeof(_Tp) == 8) {
return __builtin_bswap64(__val);
-# if _LIBCPP_HAS_INT128
+# if _LIBCPP_HAS_INT128
} else if constexpr (sizeof(_Tp) == 16) {
-# if __has_builtin(__builtin_bswap128)
+# if __has_builtin(__builtin_bswap128)
return __builtin_bswap128(__val);
-# else
+# else
return (static_cast<_Tp>(byteswap(static_cast<uint64_t>(__val))) << 64) |
static_cast<_Tp>(byteswap(static_cast<uint64_t>(__val >> 64)));
-# endif // __has_builtin(__builtin_bswap128)
-# endif // _LIBCPP_HAS_INT128
+# endif // __has_builtin(__builtin_bswap128)
+# endif // _LIBCPP_HAS_INT128
} else {
static_assert(sizeof(_Tp) == 0, "byteswap is unimplemented for integral types of this size");
}
+# endif // __has_builtin(__builtin_bswapg)
}
#endif // _LIBCPP_STD_VER >= 23
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index c5cc61f06678c..073f698786117 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -42,6 +42,7 @@ barrier ctime
barrier limits
barrier ratio
barrier version
+bit climits
bit cstdint
bit limits
bit version
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index 253cf64703076..5b4ba7918ae96 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -40,6 +40,7 @@ barrier ctime
barrier limits
barrier ratio
barrier version
+bit climits
bit cstdint
bit limits
bit version
diff --git a/libcxx/test/std/numerics/bit/byteswap.pass.cpp b/libcxx/test/std/numerics/bit/byteswap.pass.cpp
index 9d4e328ed9d0f..f96af9410ead3 100644
--- a/libcxx/test/std/numerics/bit/byteswap.pass.cpp
+++ b/libcxx/test/std/numerics/bit/byteswap.pass.cpp
@@ -26,6 +26,11 @@ static_assert(!has_byteswap<float>);
static_assert(!has_byteswap<char[2]>);
static_assert(!has_byteswap<std::byte>);
+// _BitInt(N) candidacy is controlled by the `integral` constraint; the
+// padding-bit rejection (per [bit.byteswap]/Mandates) is a static_assert
+// inside the function body, not a SFINAE check. See byteswap.verify.cpp
+// for diagnostic verification of the rejected widths.
+
template <class T>
constexpr void test_num(T in, T expected) {
assert(std::byteswap(in) == expected);
@@ -92,6 +97,67 @@ constexpr bool test() {
test_implementation_defined_size<unsigned long>();
test_implementation_defined_size<long long>();
test_implementation_defined_size<unsigned long long>();
+
+#if TEST_HAS_EXTENSION(bit_int)
+ // _BitInt(N) where digits + is_signed == sizeof * CHAR_BIT (no padding
+ // bits) is accepted; other widths are rejected by the static_assert
+ // inside the function body (see byteswap.verify.cpp).
+
+ // sizeof == 1
+ test_num<unsigned _BitInt(8)>(0xAB, 0xAB);
+ test_num<signed _BitInt(8)>(0x12, 0x12);
+
+ // sizeof == 2: __builtin_bswap16 fallback or __builtin_bswapg
+ test_num<unsigned _BitInt(16)>(0xCDEF, 0xEFCD);
+ test_num<signed _BitInt(16)>(0x1234, 0x3412);
+
+ // sizeof == 4: __builtin_bswap32 fallback or __builtin_bswapg
+ test_num<unsigned _BitInt(32)>(0x01234567U, 0x67452301U);
+ test_num<signed _BitInt(32)>(0x01234567, 0x67452301);
+
+ // sizeof == 8: __builtin_bswap64 fallback or __builtin_bswapg
+ test_num<unsigned _BitInt(64)>(0x0123456789ABCDEFULL, 0xEFCDAB8967452301ULL);
+ test_num<signed _BitInt(64)>(0x0123456789ABCDEFLL, static_cast<signed _BitInt(64)>(0xEFCDAB8967452301ULL));
+
+# if __BITINT_MAXWIDTH__ >= 128
+ // sizeof == 16: __builtin_bswap128 fallback or __builtin_bswapg.
+ unsigned _BitInt(128) v128 =
+ (static_cast<unsigned _BitInt(128)>(0x0123456789ABCDEFULL) << 64) |
+ static_cast<unsigned _BitInt(128)>(0x13579BDF02468ACEULL);
+ unsigned _BitInt(128) v128_swapped =
+ (static_cast<unsigned _BitInt(128)>(0xCE8A4602DF9B5713ULL) << 64) |
+ static_cast<unsigned _BitInt(128)>(0xEFCDAB8967452301ULL);
+ test_num<unsigned _BitInt(128)>(v128, v128_swapped);
+ test_num<signed _BitInt(128)>(static_cast<signed _BitInt(128)>(v128), static_cast<signed _BitInt(128)>(v128_swapped));
+# endif
+
+# if __has_builtin(__builtin_bswapg) && __BITINT_MAXWIDTH__ >= 256
+ // sizeof > 16: only the __builtin_bswapg path supports widths beyond what
+ // __builtin_bswap16/32/64/128 cover.
+ unsigned _BitInt(256) v256 =
+ (static_cast<unsigned _BitInt(256)>(0xDEADBEEFCAFEBABEULL) << 128) |
+ (static_cast<unsigned _BitInt(256)>(0x1234567890ABCDEFULL) << 64) |
+ static_cast<unsigned _BitInt(256)>(0xFEDCBA9876543210ULL);
+ assert(std::byteswap(std::byteswap(v256)) == v256);
+ ASSERT_SAME_TYPE(decltype(std::byteswap(v256)), unsigned _BitInt(256));
+ ASSERT_NOEXCEPT(std::byteswap(v256));
+
+ // Spot check: low byte of input must end up as the high byte of the output.
+ unsigned _BitInt(256) lo_only = 0xAB;
+ auto lo_swapped = std::byteswap(lo_only);
+ assert(static_cast<unsigned char>(lo_swapped >> ((sizeof(lo_swapped) - 1) * CHAR_BIT)) == 0xAB);
+
+ // Mid-value test: a distinct byte at every position so an off-by-one in the
+ // generated code surfaces directly.
+ unsigned _BitInt(256) ramp = 0;
+ for (int i = 0; i < 32; ++i)
+ ramp |= static_cast<unsigned _BitInt(256)>(i) << (i * CHAR_BIT);
+ auto ramp_swapped = std::byteswap(ramp);
+ for (int i = 0; i < 32; ++i)
+ assert(static_cast<unsigned char>(ramp_swapped >> ((31 - i) * CHAR_BIT)) == i);
+# endif
+#endif
+
return true;
}
diff --git a/libcxx/test/std/numerics/bit/byteswap.verify.cpp b/libcxx/test/std/numerics/bit/byteswap.verify.cpp
new file mode 100644
index 0000000000000..f7ff1c6aefb11
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/byteswap.verify.cpp
@@ -0,0 +1,141 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <bit>
+
+// std::byteswap rejects integer types that have padding bits per
+// [bit.byteswap]/Mandates. The implementation uses static_assert; the
+// diagnostic comes from <__bit/byteswap.h>.
+
+#include <bit>
+
+#include "test_macros.h"
+
+#if TEST_HAS_EXTENSION(bit_int)
+
+// Sub-byte widths (sizeof == 1 but bit width below CHAR_BIT)
+void test_unsigned_1() {
+ unsigned _BitInt(1) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_unsigned_7() {
+ unsigned _BitInt(7) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_signed_7() {
+ signed _BitInt(7) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+// Non-byte-aligned widths
+void test_unsigned_13() {
+ unsigned _BitInt(13) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_unsigned_17() {
+ unsigned _BitInt(17) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_signed_33() {
+ signed _BitInt(33) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_unsigned_65() {
+ unsigned _BitInt(65) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+// Byte-aligned widths whose value bits don't fill the object representation.
+// On platforms where sizeof(_BitInt(N)) rounds up to a power of two, these
+// types have padding bits in the high-order storage positions even though
+// their value width is a multiple of CHAR_BIT.
+void test_unsigned_24() {
+ // sizeof(_BitInt(24)) == 4 on x86_64; 8 padding bits.
+ unsigned _BitInt(24) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_unsigned_40() {
+ // sizeof(_BitInt(40)) == 8 on x86_64; 24 padding bits.
+ unsigned _BitInt(40) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_unsigned_48() {
+ // sizeof(_BitInt(48)) == 8 on x86_64; 16 padding bits. Note that the value
+ // bit width (48) is a multiple of 16, so __builtin_bswapg accepts it -- the
+ // libc++ static_assert is what actually catches this case.
+ unsigned _BitInt(48) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+void test_unsigned_56() {
+ // sizeof(_BitInt(56)) == 8 on x86_64; 8 padding bits.
+ unsigned _BitInt(56) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+
+# if __BITINT_MAXWIDTH__ >= 80
+void test_unsigned_80() {
+ // sizeof(_BitInt(80)) == 16 on x86_64; 48 padding bits. Width 80 is also
+ // a multiple of 16, so bswapg would accept it without the static_assert.
+ unsigned _BitInt(80) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+# endif
+
+# if __BITINT_MAXWIDTH__ >= 96
+void test_unsigned_96() {
+ // sizeof(_BitInt(96)) == 16 on x86_64; 32 padding bits.
+ unsigned _BitInt(96) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+# endif
+
+# if __BITINT_MAXWIDTH__ >= 112
+void test_unsigned_112() {
+ // sizeof(_BitInt(112)) == 16 on x86_64; 16 padding bits.
+ unsigned _BitInt(112) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+# endif
+
+# if __BITINT_MAXWIDTH__ >= 256
+void test_unsigned_192() {
+ // sizeof(_BitInt(192)) == 32 on x86_64; 64 padding bits. Multiple of 16
+ // but not of the storage size.
+ unsigned _BitInt(192) v = 0;
+ // expected-error@*:* {{static assertion failed{{.*}}std::byteswap requires T to have no padding bits}}
+ (void)std::byteswap(v);
+}
+# endif
+
+#else
+// expected-no-diagnostics
+#endif
More information about the libcxx-commits
mailing list