Skip to content

Commit

Permalink
bits.h: Add absl::endian and absl::byteswap polyfills
Browse files Browse the repository at this point in the history
I had to remove the MSVC implementation since it is
not constexpr.
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/byteswap-uint64-byteswap-ulong-byteswap-ushort?view=msvc-170

But all major compilers understand the portable implementation
is just a bswap: https://godbolt.org/z/oMnW3xsns

Closes #1198

PiperOrigin-RevId: 734626285
Change-Id: Ibca79f408f225c894f3c2b95f7086f891627db9f
  • Loading branch information
derekmauro authored and copybara-github committed Mar 7, 2025
1 parent 70ba73e commit 25bce12
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 26 deletions.
3 changes: 3 additions & 0 deletions absl/base/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,9 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' ||
//
// Checks the endianness of the platform.
//
// Prefer using `std::endian` in C++20, or `absl::endian` from
// absl/numeric/bits.h prior to C++20.
//
// Notes: uses the built in endian macros provided by GCC (since 4.6) and
// Clang (since 3.2); see
// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html.
Expand Down
48 changes: 22 additions & 26 deletions absl/base/internal/endian.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file is for Abseil internal use only.
// See //absl/numeric/bits.h for supported functions related to endian-ness.

#ifndef ABSL_BASE_INTERNAL_ENDIAN_H_
#define ABSL_BASE_INTERNAL_ENDIAN_H_
Expand All @@ -28,44 +30,38 @@
namespace absl {
ABSL_NAMESPACE_BEGIN

inline uint64_t gbswap_64(uint64_t host_int) {
constexpr uint64_t gbswap_64(uint64_t x) {
#if ABSL_HAVE_BUILTIN(__builtin_bswap64) || defined(__GNUC__)
return __builtin_bswap64(host_int);
#elif defined(_MSC_VER)
return _byteswap_uint64(host_int);
return __builtin_bswap64(x);
#else
return (((host_int & uint64_t{0xFF}) << 56) |
((host_int & uint64_t{0xFF00}) << 40) |
((host_int & uint64_t{0xFF0000}) << 24) |
((host_int & uint64_t{0xFF000000}) << 8) |
((host_int & uint64_t{0xFF00000000}) >> 8) |
((host_int & uint64_t{0xFF0000000000}) >> 24) |
((host_int & uint64_t{0xFF000000000000}) >> 40) |
((host_int & uint64_t{0xFF00000000000000}) >> 56));
return (((x & uint64_t{0xFF}) << 56) |
((x & uint64_t{0xFF00}) << 40) |
((x & uint64_t{0xFF0000}) << 24) |
((x & uint64_t{0xFF000000}) << 8) |
((x & uint64_t{0xFF00000000}) >> 8) |
((x & uint64_t{0xFF0000000000}) >> 24) |
((x & uint64_t{0xFF000000000000}) >> 40) |
((x & uint64_t{0xFF00000000000000}) >> 56));
#endif
}

inline uint32_t gbswap_32(uint32_t host_int) {
constexpr uint32_t gbswap_32(uint32_t x) {
#if ABSL_HAVE_BUILTIN(__builtin_bswap32) || defined(__GNUC__)
return __builtin_bswap32(host_int);
#elif defined(_MSC_VER)
return _byteswap_ulong(host_int);
return __builtin_bswap32(x);
#else
return (((host_int & uint32_t{0xFF}) << 24) |
((host_int & uint32_t{0xFF00}) << 8) |
((host_int & uint32_t{0xFF0000}) >> 8) |
((host_int & uint32_t{0xFF000000}) >> 24));
return (((x & uint32_t{0xFF}) << 24) |
((x & uint32_t{0xFF00}) << 8) |
((x & uint32_t{0xFF0000}) >> 8) |
((x & uint32_t{0xFF000000}) >> 24));
#endif
}

inline uint16_t gbswap_16(uint16_t host_int) {
constexpr uint16_t gbswap_16(uint16_t x) {
#if ABSL_HAVE_BUILTIN(__builtin_bswap16) || defined(__GNUC__)
return __builtin_bswap16(host_int);
#elif defined(_MSC_VER)
return _byteswap_ushort(host_int);
return __builtin_bswap16(x);
#else
return (((host_int & uint16_t{0xFF}) << 8) |
((host_int & uint16_t{0xFF00}) >> 8));
return (((x & uint16_t{0xFF}) << 8) |
((x & uint16_t{0xFF00}) >> 8));
#endif
}

Expand Down
1 change: 1 addition & 0 deletions absl/numeric/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ cc_library(
deps = [
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:endian",
],
)

Expand Down
2 changes: 2 additions & 0 deletions absl/numeric/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ absl_cc_library(
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::core_headers
absl::endian
PUBLIC
)

Expand Down
66 changes: 66 additions & 0 deletions absl/numeric/bits.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1355r2.html
// P1956R1:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1956r1.pdf
// P0463R1
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0463r1.html
// P1272R4
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1272r4.html
//
// When using a standard library that implements these functions, we use the
// standard library's implementation.
Expand All @@ -45,6 +49,7 @@
#endif

#include "absl/base/attributes.h"
#include "absl/base/internal/endian.h"
#include "absl/numeric/internal/bits.h"

namespace absl {
Expand Down Expand Up @@ -190,6 +195,67 @@ ABSL_INTERNAL_CONSTEXPR_CLZ inline

#endif

#if defined(__cpp_lib_endian) && __cpp_lib_endian >= 201907L

// https://en.cppreference.com/w/cpp/types/endian
//
// Indicates the endianness of all scalar types:
// * If all scalar types are little-endian, `absl::endian::native` equals
// absl::endian::little.
// * If all scalar types are big-endian, `absl::endian::native` equals
// `absl::endian::big`.
// * Platforms that use anything else are unsupported.
using std::endian;

#else

enum class endian {
little,
big,
#if defined(ABSL_IS_LITTLE_ENDIAN)
native = little
#elif defined(ABSL_IS_BIG_ENDIAN)
native = big
#else
#error "Endian detection needs to be set up for this platform"
#endif
};

#endif // defined(__cpp_lib_endian) && __cpp_lib_endian >= 201907L

#if defined(__cpp_lib_byteswap) && __cpp_lib_byteswap >= 202110L

// https://en.cppreference.com/w/cpp/numeric/byteswap
//
// Reverses the bytes in the given integer value `x`.
//
// `absl::byteswap` participates in overload resolution only if `T` satisfies
// integral, i.e., `T` is an integer type. The program is ill-formed if `T` has
// padding bits.
using std::byteswap;

#else

template <class T>
[[nodiscard]] constexpr T byteswap(T x) noexcept {
static_assert(std::is_integral_v<T>,
"byteswap requires an integral argument");
static_assert(
sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8,
"byteswap works only with 8, 16, 32, or 64-bit integers");
if constexpr (sizeof(T) == 1) {
return x;
} else if constexpr (sizeof(T) == 2) {
return static_cast<T>(gbswap_16(static_cast<uint16_t>(x)));
} else if constexpr (sizeof(T) == 4) {
return static_cast<T>(gbswap_32(static_cast<uint32_t>(x)));
} else if constexpr (sizeof(T) == 8) {
return static_cast<T>(gbswap_64(static_cast<uint64_t>(x)));
}
}

#endif // defined(__cpp_lib_byteswap) && __cpp_lib_byteswap >= 202110L

ABSL_NAMESPACE_END
} // namespace absl

Expand Down
56 changes: 56 additions & 0 deletions absl/numeric/bits_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "absl/numeric/bits.h"

#include <cstdint>
#include <limits>
#include <type_traits>

Expand Down Expand Up @@ -636,6 +637,61 @@ static_assert(ABSL_INTERNAL_HAS_CONSTEXPR_CLZ, "clz should be constexpr");
static_assert(ABSL_INTERNAL_HAS_CONSTEXPR_CTZ, "ctz should be constexpr");
#endif

TEST(Endian, Comparison) {
#if defined(ABSL_IS_LITTLE_ENDIAN)
static_assert(absl::endian::native == absl::endian::little);
static_assert(absl::endian::native != absl::endian::big);
#endif
#if defined(ABSL_IS_BIG_ENDIAN)
static_assert(absl::endian::native != absl::endian::little);
static_assert(absl::endian::native == absl::endian::big);
#endif
}

TEST(Byteswap, Constexpr) {
static_assert(absl::byteswap<int8_t>(0x12) == 0x12);
static_assert(absl::byteswap<int16_t>(0x1234) == 0x3412);
static_assert(absl::byteswap<int32_t>(0x12345678) == 0x78563412);
static_assert(absl::byteswap<int64_t>(0x123456789abcdef0) ==
static_cast<int64_t>(0xf0debc9a78563412));
static_assert(absl::byteswap<uint8_t>(0x21) == 0x21);
static_assert(absl::byteswap<uint16_t>(0x4321) == 0x2143);
static_assert(absl::byteswap<uint32_t>(0x87654321) == 0x21436587);
static_assert(absl::byteswap<uint64_t>(0xfedcba9876543210) ==
static_cast<uint64_t>(0x1032547698badcfe));
static_assert(absl::byteswap<int32_t>(static_cast<int32_t>(0xdeadbeef)) ==
static_cast<int32_t>(0xefbeadde));
}

TEST(Byteswap, NotConstexpr) {
int8_t a = 0x12;
int16_t b = 0x1234;
int32_t c = 0x12345678;
int64_t d = 0x123456789abcdef0;
uint8_t e = 0x21;
uint16_t f = 0x4321;
uint32_t g = 0x87654321;
uint64_t h = 0xfedcba9876543210;
EXPECT_EQ(absl::byteswap<int8_t>(a), 0x12);
EXPECT_EQ(absl::byteswap<int16_t>(b), 0x3412);
EXPECT_EQ(absl::byteswap(c), 0x78563412);
EXPECT_EQ(absl::byteswap(d), 0xf0debc9a78563412);
EXPECT_EQ(absl::byteswap<uint8_t>(e), 0x21);
EXPECT_EQ(absl::byteswap<uint16_t>(f), 0x2143);
EXPECT_EQ(absl::byteswap(g), 0x21436587);
EXPECT_EQ(absl::byteswap(h), 0x1032547698badcfe);
EXPECT_EQ(absl::byteswap(absl::byteswap<int8_t>(a)), a);
EXPECT_EQ(absl::byteswap(absl::byteswap<int16_t>(b)), b);
EXPECT_EQ(absl::byteswap(absl::byteswap(c)), c);
EXPECT_EQ(absl::byteswap(absl::byteswap(d)), d);
EXPECT_EQ(absl::byteswap(absl::byteswap<uint8_t>(e)), e);
EXPECT_EQ(absl::byteswap(absl::byteswap<uint16_t>(f)), f);
EXPECT_EQ(absl::byteswap(absl::byteswap(g)), g);
EXPECT_EQ(absl::byteswap(absl::byteswap(h)), h);
EXPECT_EQ(absl::byteswap<uint32_t>(0xdeadbeef), 0xefbeadde);
EXPECT_EQ(absl::byteswap<const uint32_t>(0xdeadbeef), 0xefbeadde);
}

} // namespace
ABSL_NAMESPACE_END
} // namespace absl

0 comments on commit 25bce12

Please sign in to comment.