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