[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:
parent
02af47b605
commit
fdfb99f633
5 changed files with 233 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ barrier ctime
|
|||
barrier limits
|
||||
barrier ratio
|
||||
barrier version
|
||||
bit climits
|
||||
bit cstdint
|
||||
bit limits
|
||||
bit version
|
||||
|
|
|
|||
|
|
|
@ -40,6 +40,7 @@ barrier ctime
|
|||
barrier limits
|
||||
barrier ratio
|
||||
barrier version
|
||||
bit climits
|
||||
bit cstdint
|
||||
bit limits
|
||||
bit version
|
||||
|
|
|
|||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
141
libcxx/test/std/numerics/bit/byteswap.verify.cpp
Normal file
141
libcxx/test/std/numerics/bit/byteswap.verify.cpp
Normal 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
|
||||
Loading…
Reference in a new issue