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