openMSX
endian.hh
Go to the documentation of this file.
1#ifndef ENDIAN_HH
2#define ENDIAN_HH
3
4#include "inline.hh"
5#include "narrow.hh"
6#include <array>
7#include <bit>
8#include <cassert>
9#include <concepts>
10#include <cstdint>
11#include <cstring>
12
13namespace Endian {
14
15inline constexpr bool BIG = std::endian::native == std::endian::big;
16inline constexpr bool LITTLE = std::endian::native == std::endian::little;
17static_assert(BIG || LITTLE, "mixed endian not supported");
18
19// Reverse bytes in a 16-bit number: 0x1234 becomes 0x3412
20[[nodiscard]] static inline uint16_t byteswap16(uint16_t x)
21{
22 // This sequence generates 'optimal' code on a wide range of gcc/clang
23 // versions (a single rotate instruction on x86). The newer compiler
24 // versions also do 'the right thing' for the simpler expression below.
25 // Those newer compilers also support __builtin_bswap16() but that
26 // doesn't generate better code (and is less portable).
27 return uint16_t(((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8));
28 //return (x << 8) | (x >> 8);
29}
30
31// Reverse bytes in a 32-bit number: 0x12345678 becomes 0x78563412
32[[nodiscard]] static inline uint32_t byteswap32(uint32_t x)
33{
34#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3))
35 // Starting from gcc-4.3 there's a builtin function for this.
36 // E.g. on x86 this is translated to a single 'bswap' instruction.
37 return __builtin_bswap32(x);
38#else
39 return (x << 24) |
40 ((x << 8) & 0x00ff0000) |
41 ((x >> 8) & 0x0000ff00) |
42 (x >> 24);
43#endif
44}
45
46// Reverse bytes in a 64-bit value: 0x1122334455667788 becomes 0x8877665544332211
47[[nodiscard]] static inline uint64_t byteswap64(uint64_t x)
48{
49#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3))
50 // Starting from gcc-4.3 there's a builtin function for this.
51 // E.g. on x86 this is translated to a single 'bswap' instruction.
52 return __builtin_bswap64(x);
53#else
54 return (uint64_t(byteswap32(narrow_cast<uint32_t>(x >> 0))) << 32) |
55 (uint64_t(byteswap32(narrow_cast<uint32_t>(x >> 32))) << 0);
56#endif
57}
58
59// Use overloading to get a (statically) polymorphic byteswap() function.
60[[nodiscard]] static inline uint16_t byteswap(uint16_t x) { return byteswap16(x); }
61[[nodiscard]] static inline uint32_t byteswap(uint32_t x) { return byteswap32(x); }
62[[nodiscard]] static inline uint64_t byteswap(uint64_t x) { return byteswap64(x); }
63
64
65// Identity operator, simply returns the given value.
66struct Ident {
67 [[nodiscard]] inline auto operator()(std::integral auto t) const { return t; }
68};
69
70// Byte-swap operator, swap bytes in the given value (16 or 32 bit).
71struct ByteSwap {
72 [[nodiscard]] inline auto operator()(std::integral auto t) const { return byteswap(t); }
73};
74
75// Helper class that stores a value and allows to read/write that value. Though
76// right before it is loaded/stored the value is transformed by a configurable
77// operation.
78// TODO If needed this can be extended with stuff like operator+= ....
79template<std::integral T, std::invocable<T> Op> class EndianT {
80public:
81 EndianT() = default; // leave uninitialized
82 EndianT(T t_) { Op op; t = op(t_); }
83 [[nodiscard]] inline operator T() const { Op op; return op(t); }
84 inline EndianT& operator=(T a) { Op op; t = op(a); return *this; }
85private:
86 T t;
87};
88
89// Define the types B16, B32, L16, L32.
90//
91// Typically these types are used to define the layout of external structures
92// For example:
93//
94// struct FATDirectoryEntry {
95// char filename[8];
96// char extension[3];
97// ...
98// Endian::L32 size; // 32-bit little endian value
99// };
100// ...
101// unsigned s = myDirEntry.size; // Possibly performs endianess conversion.
102// yourDirEntry.size = s; // If native endianess is already correct
103// // this has no extra overhead.
104//
105// You can assign and read values in native endianess to values of these types.
106// So basically in a single location define the structure with the correct
107// endianess and in all other places use the value as-if it were a native type.
108//
109// Note that these types should still be correctly aligned (e.g. L32 should be
110// 4-byte aligned). For unaligned access use the functions below.
111//
112template<bool> struct ConvBig;
113template<> struct ConvBig <true > : Ident {};
114template<> struct ConvBig <false> : ByteSwap {};
115template<bool> struct ConvLittle;
116template<> struct ConvLittle<true > : ByteSwap {};
117template<> struct ConvLittle<false> : Ident {};
124static_assert(sizeof(B16) == 2, "must have size 2");
125static_assert(sizeof(L16) == 2, "must have size 2");
126static_assert(sizeof(B32) == 4, "must have size 4");
127static_assert(sizeof(L32) == 4, "must have size 4");
128static_assert(sizeof(B64) == 8, "must have size 8");
129static_assert(sizeof(L64) == 8, "must have size 8");
130static_assert(alignof(B16) <= 2, "may have alignment 2");
131static_assert(alignof(L16) <= 2, "may have alignment 2");
132static_assert(alignof(B32) <= 4, "may have alignment 4");
133static_assert(alignof(L32) <= 4, "may have alignment 4");
134static_assert(alignof(B64) <= 8, "may have alignment 8");
135static_assert(alignof(L64) <= 8, "may have alignment 8");
136
137
138// Helper functions to read/write aligned 16/32 bit values.
139inline void writeB16(void* p, uint16_t x)
140{
141 *reinterpret_cast<B16*>(p) = x;
142}
143inline void writeL16(void* p, uint16_t x)
144{
145 *reinterpret_cast<L16*>(p) = x;
146}
147inline void writeB32(void* p, uint32_t x)
148{
149 *reinterpret_cast<B32*>(p) = x;
150}
151inline void writeL32(void* p, uint32_t x)
152{
153 *reinterpret_cast<L32*>(p) = x;
154}
155
156[[nodiscard]] inline uint16_t readB16(const void* p)
157{
158 return *reinterpret_cast<const B16*>(p);
159}
160[[nodiscard]] inline uint16_t readL16(const void* p)
161{
162 return *reinterpret_cast<const L16*>(p);
163}
164[[nodiscard]] inline uint32_t readB32(const void* p)
165{
166 return *reinterpret_cast<const B32*>(p);
167}
168[[nodiscard]] inline uint32_t readL32(const void* p)
169{
170 return *reinterpret_cast<const L32*>(p);
171}
172
173// Read/write big/little 16/24/32/64-bit values to/from a (possibly) unaligned
174// memory location. If the host architecture supports unaligned load/stores
175// (e.g. x86), these functions perform a single load/store (with possibly an
176// adjust operation on the value if the endianess is different from the host
177// endianess). If the architecture does not support unaligned memory operations
178// (e.g. early ARM architectures), the operation is split into byte accesses.
179
180template<bool SWAP> static ALWAYS_INLINE void write_UA(void* p, std::integral auto x)
181{
182 if constexpr (SWAP) x = byteswap(x);
183 memcpy(p, &x, sizeof(x));
184}
185ALWAYS_INLINE void write_UA_B16(void* p, uint16_t x)
186{
187 write_UA<LITTLE>(p, x);
188}
189ALWAYS_INLINE void write_UA_L16(void* p, uint16_t x)
190{
191 write_UA<BIG>(p, x);
192}
193ALWAYS_INLINE void write_UA_L24(void* p, uint32_t x)
194{
195 assert(x < 0x1000000);
196 auto* v = static_cast<uint8_t*>(p);
197 v[0] = (x >> 0) & 0xff;
198 v[1] = (x >> 8) & 0xff;
199 v[2] = (x >> 16) & 0xff;
200}
201ALWAYS_INLINE void write_UA_B32(void* p, uint32_t x)
202{
203 write_UA<LITTLE>(p, x);
204}
205ALWAYS_INLINE void write_UA_L32(void* p, uint32_t x)
206{
207 write_UA<BIG>(p, x);
208}
209ALWAYS_INLINE void write_UA_B64(void* p, uint64_t x)
210{
211 write_UA<LITTLE>(p, x);
212}
213ALWAYS_INLINE void write_UA_L64(void* p, uint64_t x)
214{
215 write_UA<BIG>(p, x);
216}
217
218template<bool SWAP, std::integral T> [[nodiscard]] static ALWAYS_INLINE T read_UA(const void* p)
219{
220 T x;
221 memcpy(&x, p, sizeof(x));
222 if constexpr (SWAP) x = byteswap(x);
223 return x;
224}
225[[nodiscard]] ALWAYS_INLINE uint16_t read_UA_B16(const void* p)
226{
227 return read_UA<LITTLE, uint16_t>(p);
228}
229[[nodiscard]] ALWAYS_INLINE uint16_t read_UA_L16(const void* p)
230{
231 return read_UA<BIG, uint16_t>(p);
232}
233[[nodiscard]] ALWAYS_INLINE uint32_t read_UA_L24(const void* p)
234{
235 const auto* v = static_cast<const uint8_t*>(p);
236 return (v[0] << 0) | (v[1] << 8) | (v[2] << 16);
237}
238[[nodiscard]] ALWAYS_INLINE uint32_t read_UA_B32(const void* p)
239{
240 return read_UA<LITTLE, uint32_t>(p);
241}
242[[nodiscard]] ALWAYS_INLINE uint32_t read_UA_L32(const void* p)
243{
244 return read_UA<BIG, uint32_t>(p);
245}
246[[nodiscard]] ALWAYS_INLINE uint64_t read_UA_B64(const void* p)
247{
248 return read_UA<LITTLE, uint64_t>(p);
249}
250[[nodiscard]] ALWAYS_INLINE uint64_t read_UA_L64(const void* p)
251{
252 return read_UA<BIG, uint64_t>(p);
253}
254
255
256// Like the types above, but these don't need to be aligned.
257
258class UA_B16 {
259public:
260 [[nodiscard]] inline operator uint16_t() const { return read_UA_B16(x.data()); }
261 inline UA_B16& operator=(uint16_t a) { write_UA_B16(x.data(), a); return *this; }
262private:
263 std::array<uint8_t, 2> x;
264};
265
266class UA_L16 {
267public:
268 [[nodiscard]] inline operator uint16_t() const { return read_UA_L16(x.data()); }
269 inline UA_L16& operator=(uint16_t a) { write_UA_L16(x.data(), a); return *this; }
270private:
271 std::array<uint8_t, 2> x;
272};
273
274class UA_L24 {
275public:
276 inline operator uint32_t() const { return read_UA_L24(x.data()); }
277 inline UA_L24& operator=(uint32_t a) { write_UA_L24(x.data(), a); return *this; }
278private:
279 std::array<uint8_t, 3> x;
280};
281
282class UA_B32 {
283public:
284 [[nodiscard]] inline operator uint32_t() const { return read_UA_B32(x.data()); }
285 inline UA_B32& operator=(uint32_t a) { write_UA_B32(x.data(), a); return *this; }
286private:
287 std::array<uint8_t, 4> x;
288};
289
290class UA_L32 {
291public:
292 [[nodiscard]] inline operator uint32_t() const { return read_UA_L32(x.data()); }
293 inline UA_L32& operator=(uint32_t a) { write_UA_L32(x.data(), a); return *this; }
294private:
295 std::array<uint8_t, 4> x;
296};
297
298static_assert(sizeof(UA_B16) == 2, "must have size 2");
299static_assert(sizeof(UA_L16) == 2, "must have size 2");
300static_assert(sizeof(UA_L24) == 3, "must have size 3");
301static_assert(sizeof(UA_B32) == 4, "must have size 4");
302static_assert(sizeof(UA_L32) == 4, "must have size 4");
303static_assert(alignof(UA_B16) == 1, "must have alignment 1");
304static_assert(alignof(UA_L16) == 1, "must have alignment 1");
305static_assert(alignof(UA_L24) == 1, "must have alignment 1");
306static_assert(alignof(UA_B32) == 1, "must have alignment 1");
307static_assert(alignof(UA_L32) == 1, "must have alignment 1");
308
309// Template meta-programming.
310// Get a type of the same size of the given type that stores the value in a
311// specific endianess. Typically used in template functions that can work on
312// either 16 or 32 bit values.
313// usage:
314// using LE_T = typename Endian::Little<T>::type;
315// The type LE_T is now a type that stores values of the same size as 'T'
316// in little endian format (independent of host endianess).
317template<typename> struct Little;
318template<> struct Little<uint8_t > { using type = uint8_t; };
319template<> struct Little<uint16_t> { using type = L16; };
320template<> struct Little<uint32_t> { using type = L32; };
321template<typename> struct Big;
322template<> struct Big<uint8_t > { using type = uint8_t; };
323template<> struct Big<uint16_t> { using type = B16; };
324template<> struct Big<uint32_t> { using type = B32; };
325
326} // namespace Endian
327
328#endif
TclObject t
EndianT(T t_)
Definition endian.hh:82
EndianT & operator=(T a)
Definition endian.hh:84
EndianT()=default
UA_B16 & operator=(uint16_t a)
Definition endian.hh:261
UA_B32 & operator=(uint32_t a)
Definition endian.hh:285
UA_L16 & operator=(uint16_t a)
Definition endian.hh:269
UA_L24 & operator=(uint32_t a)
Definition endian.hh:277
UA_L32 & operator=(uint32_t a)
Definition endian.hh:293
#define ALWAYS_INLINE
Definition inline.hh:16
uint16_t readB16(const void *p)
Definition endian.hh:156
ALWAYS_INLINE uint32_t read_UA_L24(const void *p)
Definition endian.hh:233
ALWAYS_INLINE uint16_t read_UA_B16(const void *p)
Definition endian.hh:225
void writeB16(void *p, uint16_t x)
Definition endian.hh:139
ALWAYS_INLINE void write_UA_B32(void *p, uint32_t x)
Definition endian.hh:201
ALWAYS_INLINE void write_UA_B64(void *p, uint64_t x)
Definition endian.hh:209
uint32_t readL32(const void *p)
Definition endian.hh:168
ALWAYS_INLINE void write_UA_L24(void *p, uint32_t x)
Definition endian.hh:193
ALWAYS_INLINE void write_UA_B16(void *p, uint16_t x)
Definition endian.hh:185
void writeL32(void *p, uint32_t x)
Definition endian.hh:151
ALWAYS_INLINE uint64_t read_UA_L64(const void *p)
Definition endian.hh:250
ALWAYS_INLINE uint32_t read_UA_B32(const void *p)
Definition endian.hh:238
void writeB32(void *p, uint32_t x)
Definition endian.hh:147
ALWAYS_INLINE void write_UA_L64(void *p, uint64_t x)
Definition endian.hh:213
constexpr bool LITTLE
Definition endian.hh:16
ALWAYS_INLINE uint16_t read_UA_L16(const void *p)
Definition endian.hh:229
ALWAYS_INLINE uint64_t read_UA_B64(const void *p)
Definition endian.hh:246
EndianT< uint16_t, ConvBig< BIG > > B16
Definition endian.hh:118
ALWAYS_INLINE void write_UA_L32(void *p, uint32_t x)
Definition endian.hh:205
EndianT< uint32_t, ConvBig< BIG > > B32
Definition endian.hh:120
ALWAYS_INLINE void write_UA_L16(void *p, uint16_t x)
Definition endian.hh:189
EndianT< uint32_t, ConvLittle< BIG > > L32
Definition endian.hh:121
uint16_t readL16(const void *p)
Definition endian.hh:160
uint32_t readB32(const void *p)
Definition endian.hh:164
EndianT< uint16_t, ConvLittle< BIG > > L16
Definition endian.hh:119
EndianT< uint64_t, ConvBig< BIG > > B64
Definition endian.hh:122
constexpr bool BIG
Definition endian.hh:15
ALWAYS_INLINE uint32_t read_UA_L32(const void *p)
Definition endian.hh:242
EndianT< uint64_t, ConvLittle< BIG > > L64
Definition endian.hh:123
void writeL16(void *p, uint16_t x)
Definition endian.hh:143
auto operator()(std::integral auto t) const
Definition endian.hh:72
auto operator()(std::integral auto t) const
Definition endian.hh:67