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