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