[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@anthropic.com>
This commit is contained in:
Xavier Roche 2026-05-30 11:08:06 +02:00 committed by GitHub
parent 02af47b605
commit fdfb99f633
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 233 additions and 5 deletions

View file

@ -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

View file

@ -42,6 +42,7 @@ barrier ctime
barrier limits
barrier ratio
barrier version
bit climits
bit cstdint
bit limits
bit version

1 algorithm cctype
42 barrier limits
43 barrier ratio
44 barrier version
45 bit climits
46 bit cstdint
47 bit limits
48 bit version

View file

@ -40,6 +40,7 @@ barrier ctime
barrier limits
barrier ratio
barrier version
bit climits
bit cstdint
bit limits
bit version

1 algorithm cctype
40 barrier limits
41 barrier ratio
42 barrier version
43 bit climits
44 bit cstdint
45 bit limits
46 bit version

View file

@ -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;
}

View file

@ -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