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