libcruft-util/endian.hpp
Danny Robson b0311f9cd4 endian: remove templates from bswap
It's too hard to trace problems with callers if we use templates here
(most of the callgraph gets truncated so we can't even tell who's
calling us).
2018-12-15 15:38:01 +11:00

205 lines
6.0 KiB
C++

/*
* 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/.
*
* Copyright 2010-2014 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_ENDIAN_HPP
#define __UTIL_ENDIAN_HPP
#include "types/bits.hpp"
#include <cstring>
#include <cstdint>
#include <type_traits>
namespace cruft {
//-------------------------------------------------------------------------
// 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 { \
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;
}
//-------------------------------------------------------------------------
template <typename T>
constexpr T
identity (T &&v) {
return v;
}
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readle (const void *_data)
{
auto data = reinterpret_cast<const uint8_t*> (_data);
T value = 0;
for (size_t i = 0; i < sizeof (value); ++i)
value |= T{data[i]} << (i * 8);
return value;
}
/// 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 (const std::uint8_t *data)
{
std::aligned_union_t <sizeof (T), T> buffer;
memcpy (reinterpret_cast<char *> (&buffer), data, sizeof (T));
return *reinterpret_cast<const T*> (&buffer);
}
//-------------------------------------------------------------------------
#if defined(WORDS_BIGENDIAN)
template <typename T> constexpr T hton (T v) { return v; }
template <typename T> constexpr T ntoh (T v) { return v; }
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; }
template <typename T> constexpr T btoh (T v) { return bswap (v); }
template <typename T> constexpr T ltoh (T v) { return v; }
#endif
template <typename T> T btol (T t) { return bswap (t); }
template <typename T> T ltob (T t) { return bswap (t); }
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 DstV, scheme SrcV, typename T>
T convert (T t)
{
return converter<DstV,SrcV>::convert (t);
}
}
//-------------------------------------------------------------------------
struct from_endian {
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;
uint = (src == endian::scheme::LITTLE) ? ltoh (uint) : btoh (uint);
if (std::is_signed<T>::value)
return T (sint);
else
return T (uint);
}
endian::scheme src;
};
}
#endif