This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Implement P1272R4 (std::byteswap)
ClosedPublic

Authored by philnik on Nov 17 2021, 2:33 AM.

Details

Summary

Implement P1274R4

Diff Detail

Event Timeline

philnik created this revision.Nov 17 2021, 2:33 AM
philnik requested review of this revision.Nov 17 2021, 2:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptNov 17 2021, 2:33 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript
lebedev.ri requested changes to this revision.Nov 17 2021, 2:36 AM
lebedev.ri added a subscriber: lebedev.ri.

byte!=bit, you want __builtin_bswap*()

This revision now requires changes to proceed.Nov 17 2021, 2:36 AM
philnik updated this revision to Diff 387912.Nov 17 2021, 5:23 AM

Doing a byteswap now instead of a bitswap - I already wondered why it was called byteswap :D

lebedev.ri resigned from this revision.Nov 17 2021, 5:27 AM

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.

Quuxplusone requested changes to this revision.Nov 17 2021, 9:03 AM

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:

  • Definitely add _LIBCPP_UNREACHABLE(); at the end of this function.
  • If both Clang and GCC support __builtin_bswap128 then we should just do that for case 16.
  • Otherwise, we should case 16: return _Tp(byteswap(uint64_t(__val))) | (_Tp(byteswap(uint64_t(__val >> 64))) << 64); // TODO: __builtin_bswap128 when Clang supports it.
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.
Also, please add a simple neg test. I recommend:

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>);
This revision now requires changes to proceed.Nov 17 2021, 9:03 AM
Mordante added inline comments.Nov 17 2021, 9:11 AM
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.

philnik updated this revision to Diff 388011.Nov 17 2021, 12:16 PM
philnik marked 13 inline comments as done.

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

Would libc++15 be the earliest revision where this could be changed, assuming clang14 gets __builtin_bswap128?

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. :)
How about making the input (0x0123456789ABCDEF, 0x13579BDF02468ACE)?

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.
(These can repeat the input data for int{16,32,64}_t AFAIC. We just want to make sure that we have covered whichever of long and long long is not int64_t.)

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.
I think it'd be fine to add a static_assert, though, like

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. :)

philnik updated this revision to Diff 388023.Nov 17 2021, 1:32 PM
philnik marked 4 inline comments as done.

Updated test

philnik updated this revision to Diff 388031.Nov 17 2021, 1:53 PM
philnik marked 4 inline comments as done.

Added static_assert

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.

philnik updated this revision to Diff 388283.Nov 18 2021, 11:59 AM
philnik marked 2 inline comments as done.

Added explicit casts and conditionally allowing __int128_t now

philnik updated this revision to Diff 388338.Nov 18 2021, 3:28 PM

Replaced erroneous _LIBCPP_HAS_NO_CONCEPTS with _LIBCPP_HAS_NO_IN128

jloser added a subscriber: jloser.Nov 18 2021, 5:48 PM
jloser added inline comments.
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.

Mordante accepted this revision as: Mordante.Nov 19 2021, 7:27 AM

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.

philnik updated this revision to Diff 388760.Nov 21 2021, 8:03 AM
  • Use if constexpr() now
Quuxplusone accepted this revision.Nov 21 2021, 8:32 AM

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.)
Anyway, not a blocker AFAIC.

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?
And get_test_data and get_expected could be moved into a single function std::pair<T, T> get_test_data() so that you wouldn't have to keep lines in sync that were separated by ~15 LOC.

99

s/int/unsigned int/ IIUC.

This revision is now accepted and ready to land.Nov 21 2021, 8:32 AM
philnik updated this revision to Diff 388762.Nov 21 2021, 9:46 AM
philnik marked 4 inline comments as done.

Updated test and added braces

Quuxplusone accepted this revision.Nov 21 2021, 11:18 AM

#include <utility>, wait for green CI, and ship it! :)

philnik updated this revision to Diff 388763.Nov 21 2021, 11:27 AM
  • added <utility> to test
philnik updated this revision to Diff 388769.Nov 21 2021, 1:29 PM

Rebased again

philnik updated this revision to Diff 388775.Nov 21 2021, 3:24 PM

Moved endif and corrected comment

This revision was automatically updated to reflect the committed changes.