Implement P1274R4
Details
- Reviewers
• Quuxplusone Mordante ldionne lebedev.ri - Group Reviewers
Restricted Project - Commits
- rG1dc62f2653f8: [libc++] Implement P1272R4 (std::byteswap)
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Doing a byteswap now instead of a bitswap - I already wondered why it was called byteswap :D
Thanks for working on this! In general looks good, but I've several minor issue.
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | We conditionally support __uint128_t. This type won't work with the current implementation. I would prefer to support that type, or constrain function where sizeof(_Tp) <= 8. The availability is guarded by _LIBCPP_HAS_NO_INT128. Note that GCC already has a __builtin_bswap128, so maybe it's interesting to also add this to clang. I wouldn't mind looking into that myself if you don't want to. | |
libcxx/test/std/numerics/bit/byteswap.pass.cpp | ||
25 | I would suggest to use '0x0123456789ABCDEF`. That way all nibbles are tested properly. | |
25 | We need to either test __int128_t / __uint128_t here if you want to support it. If you don't want to support it we need a separate validate test that rejects this type. | |
25 | I really would like see tests for all integral types. https://en.cppreference.com/w/cpp/types/is_integral, for example bool and the character types aren't tested now. | |
35 | Since the function requires to be noexcept please add an test using ASSERT_NOEXCEPT to validate it's really noexcept. | |
38 | Needed to make sure it works properly on freestanding implementations. |
LGTM % comments (most of which echo @Mordante's comments) — thanks! Requesting changes so that I'll see it again.
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | IMO:
| |
39 | Since you've used std::integral on line 24, you need to guard with #if _LIBCPP_STD_VER > 20 && !defined(_LIBCPP_HAS_NO_CONCEPTS). Also, please mark the #endif: #endif // _LIBCPP_STD_VER > 20 && !defined(_LIBCPP_HAS_NO_CONCEPTS) | |
43 | ||
libcxx/include/bit | ||
58 | This line is misindented. | |
libcxx/test/std/numerics/bit/byteswap.pass.cpp | ||
18 | Please also ASSERT_SAME_TYPE(decltype(std::byteswap(in)), decltype(in)); ASSERT_NOEXCEPT(std::byteswap(in)); | |
25 | +1 for testing all integral types, including char and bool. template<class T> concept has_byteswap = requires (T t) { std::byteswap(t); }; static_assert(!has_byteswap<void*>); static_assert(!has_byteswap<float>); static_assert(!has_byteswap<char[2]>); static_assert(!has_byteswap<std::byte>); |
libcxx/test/std/numerics/bit/byteswap.pass.cpp | ||
---|---|---|
25 | Regardless whether or not to test the 128-bit failures, I think it would be good to add a test to make sure non-integral types aren't accepted. |
Updated tests and supporting int128_t/uint128_t now
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | Right now I'll just do the two 8 byte swaps. I think I will try to add __builtin_bswap128 in a few days, but it can't be used right now anyways if I understand correctly. Would libc++15 be the earliest revision where this could be changed, assuming clang14 gets __builtin_bswap128? |
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 |
You could make it conditional immediately (#if __has_builtin(__builtin_bswap128) — and in fact perhaps that should be part of this PR!), but yeah, my understanding of the new-as-of-last-year policy is that libc++14 needs to work with both Clang14 and Clang13; libc++15 needs to work with both Clang15 and Clang14; etc. | |
libcxx/test/std/numerics/bit/byteswap.pass.cpp | ||
47–48 | I'd feel better if the test case weren't constructed such that "nybble-swap each byte" was an acceptable implementation. :) FWIW, I also find the templated make_128bit_int quite verbose. I see that you're making it a template so that you don't have to hide it under an ifdef; but still, how about just this, right above the return true? #ifndef _LIBCPP_HAS_NO_INT128 auto before = __uint128_t(0x0123456789ABCDEF) << 64 | 0x13579BDF02468ACE; auto after = __uint128_t(0xCE8A4602DF9B5713) << 64 | 0xEFCDAB8967452301); test_num<__uint128_t>(before, after); test_num<__int128_t>(__int128_t(before), __int128_t(after)); #endif | |
74 | Please add similar tests for short, int, long, long long and their unsigned counterparts. | |
libcxx/test/support/test_macros.h | ||
207–209 ↗ | (On Diff #388011) | I bet this is overkill. Are we worried that some non-libc++ vendor might fail to define byteswap for __int128_t? If we're messing with test macros related to 128-bit support, we might want to revisit libcxx/test/libcxx/input.output/filesystems/convert_file_time.pass.cpp too, I'm not sure. |
LGTM after the open comments have been addressed.
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | I also wanted to suggest to add #if __has_builtin(__builtin_bswap128) so I think it would be great to make that part of this PR. GCC already supports it so that means the code path will have test coverage. After it's available in Clang it will use it out-of-the-box. The #else branch can just use the fallback you currently use. That way we're compatible with implementations that don't have __builtin_bswap128. | |
26 | I still think it would be good to constrain the template on the maximum supported sizeof(_Tp). I prefer a compile-time error over run-time bug or error. | |
libcxx/test/support/test_macros.h | ||
207–209 ↗ | (On Diff #388011) | +1. Currently we have 121 occurrences of _LIBCPP_HAS_NO_INT128 in our test directory. |
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | Constraints are evil because they cause it to just quietly drop out of overload resolution. I don't think WG21 knows what it's doing these days by adding constraints on everything. At first it seems neato that using std::byteswap; byteswap(N::Widget()) will quietly choose ADL N::byteswap instead of being ambiguous — but really, are we encouraging people to call byteswap by ADL? I don't think so. static_assert(sizeof(_Tp) == 1 || sizeof(_Tp) == 2 || sizeof(_Tp) == 4 || sizeof(_Tp) == 8 || sizeof(_Tp) == 16); Could even refactor this function to use a ladder of if constexpr ... else if constexpr ... and then put static_assert(sizeof(_Tp) == 0, "byteswap is unimplemented for integral types of this size"); in the final else branch. | |
37 | Nit: This indentation is bizarre. I expect clang-format is to blame. #ifndef with no space, plz. :) |
Some small nits, but I'd like to see the CI green before accepting.
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | Fair point, I've no objection to static_asserts either, just as long as we make sure we don't accept invalid code. | |
29 | Or something similar to make sure we don't except 128 bit values when not supported. | |
37 | @Quuxplusone That clang-format rule changed in D109835. | |
libcxx/test/std/numerics/bit/byteswap.pass.cpp | ||
62 | I think this name is misleading. I would suggest a name like test_implementation_defined_size. |
libcxx/include/__bit/byteswap.h | ||
---|---|---|
26 | I'd also prefer the if constexpr chain with a static_assert in the false case as Arthur mentioned. |
LGTM, there are still some open comments and CI issues to address. I'll approve the patch so @Quuxplusone can give the libc++ approval once these are addressed.
LGTM mod comments. One (int->unsigned int) is functional; the others are just style improvements.
libcxx/include/__bit/byteswap.h | ||
---|---|---|
50 | Style nit: Personally, I'd add { } around each if's body, and around the else, and cuddle the else. This gives the same number of source lines, but makes it much easier to add or remove lines without major surgery. if constexpr (X) { } else if constexpr (Y) { } else { } (That said, libc++ style is generally to omit braces on single-line ifs... but that's usually because it saves a line to omit the braces. Here, it doesn't save you anything, and you've got a mix of bodies-that-need-braces and bodies-that-don't, so "consistency" comes into play too.) | |
libcxx/test/std/numerics/bit/byteswap.pass.cpp | ||
96–103 | Seems like it would be simpler to use the same approach for all of these types, wouldn't it? | |
99 | s/int/unsigned int/ IIUC. |
We conditionally support __uint128_t. This type won't work with the current implementation. I would prefer to support that type, or constrain function where sizeof(_Tp) <= 8. The availability is guarded by _LIBCPP_HAS_NO_INT128.
Note that GCC already has a __builtin_bswap128, so maybe it's interesting to also add this to clang. I wouldn't mind looking into that myself if you don't want to.