libcruft-util/endian.hpp

302 lines
8.8 KiB
C++
Raw Normal View History

2011-08-29 14:36:03 +10:00
/*
2018-08-04 15:14:06 +10:00
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
2011-08-29 14:36:03 +10:00
*
* Copyright 2010-2020, Danny Robson <danny@nerdcruft.net>
2011-08-29 14:36:03 +10:00
*/
#pragma once
2011-08-29 14:36:03 +10:00
#include "std.hpp"
#include "types/bits.hpp"
#include "cast.hpp"
#include <compare>
2011-08-29 14:36:03 +10:00
#include <cstdint>
#include <cstring>
#include <iosfwd>
#include <type_traits>
namespace cruft {
//-------------------------------------------------------------------------
2017-01-04 22:38:15 +11:00
// CXX: Update using "if constexpr" when available. It doesn't seem to be
// worth the dicking about to use enable_if_t to differentiate the
// signed/unsigned cases.
#define SIGNED_BSWAP(S) \
constexpr \
int##S##_t \
bswap (int##S##_t v) noexcept { \
2017-01-04 22:38:15 +11:00
const union { \
int##S##_t s; \
uint##S##_t u; \
} vu = { v }; \
\
return __builtin_bswap##S (vu.u); \
}
SIGNED_BSWAP(16)
SIGNED_BSWAP(32)
SIGNED_BSWAP(64)
constexpr int8_t bswap ( int8_t v) noexcept { return v; }
constexpr uint8_t bswap (uint8_t v) noexcept { return v; }
constexpr uint16_t bswap (uint16_t v) noexcept { return __builtin_bswap16 (v); }
constexpr uint32_t bswap (uint32_t v) noexcept { return __builtin_bswap32 (v); }
constexpr uint64_t bswap (uint64_t v) noexcept { return __builtin_bswap64 (v); }
// use a type-punning union to punt the byte swapping operation to the
// unsigned integer code.
//
// this is debatably safe under production compilers like gcc + clang
// despite what the standard may say about unions.
constexpr float
bswap (float v) noexcept
{
union {
float f;
uint32_t u;
} temp { .f = v };
temp.u = bswap (temp.u);
return temp.f;
}
2011-08-29 14:36:03 +10:00
//-------------------------------------------------------------------------
template <typename T>
constexpr T
identity (T &&v) {
return v;
}
2011-08-29 14:36:03 +10:00
//-------------------------------------------------------------------------
#if defined(WORDS_BIGENDIAN)
template <typename T> constexpr T hton (T v) { return v; }
template <typename T> constexpr T ntoh (T v) { return v; }
2011-08-29 14:36:03 +10:00
template <typename T> constexpr T htob (T v) { return v; }
template <typename T> constexpr T htol (T v) { return bswap (v); }
template <typename T> constexpr T btoh (T v) { return v; }
template <typename T> constexpr T ltoh (T v) { return bswap (v); }
#else
template <typename T> constexpr T hton (T v) { return bswap (v); }
template <typename T> constexpr T ntoh (T v) { return bswap (v); }
template <typename T> constexpr T htob (T v) { return bswap (v); }
template <typename T> constexpr T htol (T v) { return v; }
2011-08-29 14:36:03 +10:00
template <typename T> constexpr T btoh (T v) { return bswap (v); }
template <typename T> constexpr T ltoh (T v) { return v; }
#endif
2018-05-19 18:06:14 +10:00
template <typename T> T btol (T t) { return bswap (t); }
template <typename T> T ltob (T t) { return bswap (t); }
/// read bytes from a supplied data pointer and return an integer using
/// host endian layout.
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readhe (u08 const *data)
{
std::aligned_union_t <sizeof (T), T> buffer;
memcpy (reinterpret_cast<char *> (&buffer), data, sizeof (T));
return *reinterpret_cast<const T*> (&buffer);
}
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readle (u08 const* ptr)
{
return ltoh (readhe<T> (ptr));
}
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readbe (u08 const* ptr)
{
return btoh (readhe<T> (ptr));
}
2018-05-19 18:06:14 +10:00
namespace endian {
//---------------------------------------------------------------------
// Uses the TIFF header values. Just because. Don't rely on this.
enum class scheme : uint16_t {
BIG = 0x4D4D,
LITTLE = 0x4949,
big = BIG,
little = LITTLE,
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
native = little,
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
native = big
#else
#error Unhandled endianness
#endif
};
template <scheme DstV, scheme SrcV>
struct converter;
template <> struct converter<scheme::little,scheme::big> {
template <typename T>
static T convert (T t) { return btol (t); }
};
template <> struct converter<scheme::big,scheme::little> {
template <typename T>
static T convert (T t) { return ltob (t); }
};
template <scheme SchemeV> struct converter<SchemeV, SchemeV> {
template <typename T>
static T convert (T t) { return t; }
};
2018-05-19 18:06:14 +10:00
template <scheme DstV, scheme SrcV, typename T>
T convert (T t)
{
return converter<DstV,SrcV>::convert (t);
}
}
//-------------------------------------------------------------------------
struct from_endian {
2018-05-19 18:06:14 +10:00
explicit from_endian (endian::scheme _endian):
src (_endian)
{ ; }
template <typename T>
T operator() (const T v) const {
static_assert (std::is_integral<T>::value || std::is_enum<T>::value,
"endian conversion is only defined for integrals currently");
union {
typename sized_type<T>::sint sint;
typename sized_type<T>::uint uint;
};
if (std::is_signed<T>::value)
sint = v;
else
uint = v;
2018-05-19 18:06:14 +10:00
uint = (src == endian::scheme::LITTLE) ? ltoh (uint) : btoh (uint);
if (std::is_signed<T>::value)
return T (sint);
else
return T (uint);
}
2018-05-19 18:06:14 +10:00
endian::scheme src;
};
namespace endian {
template <scheme StorageV, typename RawT>
struct value {
constexpr RawT native (void) const { return convert<StorageV,scheme::native> (raw); }
operator RawT () const
{
return native ();
}
decltype(auto) operator+ () const { return +native (); }
constexpr value operator>> (std::size_t s) const {
RawT const val = native () >> s;
return { .raw = convert<scheme::native,StorageV> (val) };
}
constexpr value operator<< (std::size_t s) const {
RawT const val = native () << s;
return { .raw = convert<scheme::native,StorageV> (val) };
}
value operator& (value rhs) const { return value { .raw = raw & rhs.raw }; }
template <typename OperandT>
value operator& (OperandT rhs) const {
RawT const val = native () & rhs;
return { .raw = convert<scheme::native,StorageV> (val) };
}
auto& operator= (RawT const &val) { raw = convert<scheme::native,StorageV> (val); return *this; }
bool operator== (value const &rhs) const { return raw == rhs.raw; }
RawT raw;
};
template <scheme StorageV, typename RawT, typename OperandT>
constexpr decltype(auto)
operator<=> (value<StorageV,RawT> const &a, OperandT const &b)
{
return a.native () <=> b;
}
template <scheme StorageV, typename RawT, typename OperandT>
constexpr decltype(auto)
operator<=> (OperandT const &a, value<StorageV,RawT> const &b)
{
return a <=> b.native ();
}
template <typename ValueT> using little = value<scheme::little,ValueT>;
template <typename ValueT>
using big = value<scheme::big,ValueT>;
template <scheme StorageV, typename ValueT>
std::ostream&
operator<< (std::ostream &os, value<StorageV,ValueT> const &val)
{
return os << +ValueT(val);
}
}
using lu16 = endian::little<u16>;
using lu32 = endian::little<u32>;
using lu64 = endian::little<u64>;
using li16 = endian::little<i16>;
using li32 = endian::little<i32>;
using li64 = endian::little<i64>;
using bu16 = endian::big<u16>;
using bu32 = endian::big<u32>;
using bu64 = endian::big<u64>;
using bi16 = endian::big<i16>;
using bi32 = endian::big<i32>;
using bi64 = endian::big<i64>;
}