numeric/endian: Add constexpr endian conversion functions

These are as fast as the c library versions but they are also constexpr.

Change-Id: I95bb1562a5a15a7f9c57a3483afe364a0286da48
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 5785caf..1a1eb6f 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -9,6 +9,7 @@
   'stdplus/hash.hpp',
   'stdplus/hash/array.hpp',
   'stdplus/hash/tuple.hpp',
+  'stdplus/numeric/endian.hpp',
   'stdplus/pinned.hpp',
   'stdplus/raw.hpp',
   'stdplus/signal.hpp',
diff --git a/include/stdplus/numeric/endian.hpp b/include/stdplus/numeric/endian.hpp
new file mode 100644
index 0000000..bd3199b
--- /dev/null
+++ b/include/stdplus/numeric/endian.hpp
@@ -0,0 +1,112 @@
+#pragma once
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+
+namespace stdplus
+{
+
+namespace detail
+{
+
+template <typename T, size_t s = sizeof(T)>
+struct BswapFast
+{
+    static inline constexpr bool fast = false;
+};
+
+template <typename T>
+struct BswapFast<T, 2>
+{
+    static inline constexpr bool fast = true;
+    using type = uint16_t;
+
+    static constexpr uint16_t swapAligned(uint16_t n) noexcept
+    {
+        return __builtin_bswap16(n);
+    }
+};
+
+template <typename T>
+struct BswapFast<T, 4>
+{
+    static inline constexpr bool fast = true;
+    using type = uint32_t;
+
+    static constexpr uint32_t swapAligned(uint32_t n) noexcept
+    {
+        return __builtin_bswap32(n);
+    }
+};
+
+template <typename T>
+struct BswapFast<T, 8>
+{
+    static inline constexpr bool fast = true;
+    using type = uint64_t;
+
+    static constexpr uint64_t swapAligned(uint64_t n) noexcept
+    {
+        return __builtin_bswap64(n);
+    }
+};
+
+} // namespace detail
+
+template <typename T>
+constexpr T bswap(T n) noexcept
+{
+    static_assert(std::is_trivially_copyable_v<T>);
+    using F = detail::BswapFast<T>;
+    if constexpr (sizeof(T) == 1)
+    {}
+    else if constexpr (F::fast)
+    {
+        if constexpr (alignof(T) % alignof(typename F::type) == 0)
+        {
+            n = F::swapAligned(n);
+        }
+        else
+        {
+            typename F::type v;
+            auto i = reinterpret_cast<std::byte*>(&n);
+            auto o = reinterpret_cast<std::byte*>(&v);
+            std::copy(i, i + sizeof(T), o);
+            v = F::swapAligned(v);
+            std::copy(o, o + sizeof(T), i);
+        }
+    }
+    else
+    {
+        auto b = reinterpret_cast<std::byte*>(&n);
+        std::reverse(b, b + sizeof(n));
+    }
+    return n;
+}
+
+template <typename T>
+constexpr T hton(T n) noexcept
+{
+    if constexpr (std::endian::native == std::endian::big)
+    {
+        return n;
+    }
+    else if constexpr (std::endian::native == std::endian::little)
+    {
+        return bswap(n);
+    }
+    else
+    {
+        static_assert(std::is_same_v<T, void>);
+    }
+}
+
+template <typename T>
+constexpr T ntoh(T n) noexcept
+{
+    return hton(n);
+}
+
+} // namespace stdplus
diff --git a/src/meson.build b/src/meson.build
index b367dc4..ab786c0 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -50,6 +50,7 @@
   'hash.cpp',
   'hash/array.cpp',
   'hash/tuple.cpp',
+  'numeric/endian.cpp',
   'pinned.cpp',
   'raw.cpp',
   'signal.cpp',
diff --git a/src/numeric/endian.cpp b/src/numeric/endian.cpp
new file mode 100644
index 0000000..97600cf
--- /dev/null
+++ b/src/numeric/endian.cpp
@@ -0,0 +1 @@
+#include <stdplus/numeric/endian.hpp>
diff --git a/test/meson.build b/test/meson.build
index 1e15a0e..51bc288 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -6,6 +6,7 @@
   'hash': [stdplus_dep, gtest_main_dep],
   'hash/array': [stdplus_dep, gtest_main_dep],
   'hash/tuple': [stdplus_dep, gtest_main_dep],
+  'numeric/endian': [stdplus_dep, gtest_main_dep],
   'pinned': [stdplus_dep, gtest_main_dep],
   'raw': [stdplus_dep, gmock_dep, gtest_main_dep],
   'signal': [stdplus_dep, gtest_main_dep],
diff --git a/test/numeric/endian.cpp b/test/numeric/endian.cpp
new file mode 100644
index 0000000..d8f2ae8
--- /dev/null
+++ b/test/numeric/endian.cpp
@@ -0,0 +1,24 @@
+#include <stdplus/numeric/endian.hpp>
+
+#include <array>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+TEST(Byteswap, Swap)
+{
+    EXPECT_EQ(38, bswap(uint8_t{38}));
+    EXPECT_EQ(38 << 8, bswap(uint16_t{38}));
+    EXPECT_EQ(0x240082fe, bswap(uint32_t{0xfe820024}));
+    EXPECT_EQ(0x240082fe00000000, bswap(uint64_t{0xfe820024}));
+    struct
+    {
+        std::array<char, 4> a = {1, 2, 3, 4};
+    } s;
+    EXPECT_EQ((std::array<char, 4>{4, 3, 2, 1}), bswap(s).a);
+    EXPECT_EQ(40, ntoh(hton(40)));
+}
+
+} // namespace stdplus