/* * 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-2019 Danny Robson */ #pragma once #include "std.hpp" #include "types/bits.hpp" #include "cast.hpp" #include #include #include #include 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 constexpr T identity (T &&v) { return v; } template < typename T, typename = std::enable_if_t< std::is_integral_v > > T readle (const void *_data) { auto data = reinterpret_cast (_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 readhe (const std::uint8_t *data) { std::aligned_union_t buffer; memcpy (reinterpret_cast (&buffer), data, sizeof (T)); return *reinterpret_cast (&buffer); } //------------------------------------------------------------------------- #if defined(WORDS_BIGENDIAN) template constexpr T hton (T v) { return v; } template constexpr T ntoh (T v) { return v; } template constexpr T htob (T v) { return v; } template constexpr T htol (T v) { return bswap (v); } template constexpr T btoh (T v) { return v; } template constexpr T ltoh (T v) { return bswap (v); } #else template constexpr T hton (T v) { return bswap (v); } template constexpr T ntoh (T v) { return bswap (v); } template constexpr T htob (T v) { return bswap (v); } template constexpr T htol (T v) { return v; } template constexpr T btoh (T v) { return bswap (v); } template constexpr T ltoh (T v) { return v; } #endif template T btol (T t) { return bswap (t); } template 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 struct converter; template <> struct converter { template static T convert (T t) { return btol (t); } }; template <> struct converter { template static T convert (T t) { return ltob (t); } }; template T convert (T t) { return converter::convert (t); } } //------------------------------------------------------------------------- struct from_endian { explicit from_endian (endian::scheme _endian): src (_endian) { ; } template T operator() (const T v) const { static_assert (std::is_integral::value || std::is_enum::value, "endian conversion is only defined for integrals currently"); union { typename sized_type::sint sint; typename sized_type::uint uint; }; if (std::is_signed::value) sint = v; else uint = v; uint = (src == endian::scheme::LITTLE) ? ltoh (uint) : btoh (uint); if (std::is_signed::value) return T (sint); else return T (uint); } endian::scheme src; }; namespace endian { template struct value { constexpr RawT native (void) const { return convert (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 (val) }; } constexpr value operator<< (std::size_t s) const { RawT const val = native () << s; return { .raw = convert (val) }; } value operator& (value rhs) const { return value { .raw = raw & rhs.raw }; } template value operator& (OperandT rhs) const { RawT const val = native () & rhs; return { .raw = convert (val) }; } auto& operator= (RawT const &val) { raw = convert (val); return *this; } bool operator== (value const &rhs) const { return raw == rhs.raw; } RawT raw; }; template constexpr decltype(auto) operator== (value const &a, OperandT const &b) { return a.native () == b; } template constexpr decltype(auto) operator== (OperandT const &a, value const &b) { return a == b.native (); } template using little = value; template using big = value; template std::ostream& operator<< (std::ostream &os, value const &val) { return os << +ValueT(val); } } using bu16 = endian::big; using bu32 = endian::big; using bu64 = endian::big; using bi16 = endian::big; using bi32 = endian::big; using bi64 = endian::big; }