HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
endian.hpp
1// Copyright Take Vos 2019-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "memory.hpp"
8
9#ifdef HI_HAS_SSE
10#include <immintrin.h>
11#endif
12#ifdef HI_HAS_SSE2
13#include <emmintrin.h>
14#endif
15#ifdef HI_HAS_SSE4_1
16#include <smmintrin.h>
17#endif
18#if HI_COMPILER == HI_CC_MSVC
19#include <stdlib.h>
20#endif
21#include <bit>
22#include <concepts>
23
24hi_warning_push();
25// C26472: Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow
26// (type.1).
27// static_cast are used to cheaply cast integers to unsigned and back, for byteswapping.
28hi_warning_ignore_msvc(26472);
29
30namespace hi::inline v1 {
31
32template<std::unsigned_integral T>
33[[nodiscard]] constexpr T byte_swap(T x) noexcept
34{
35 if (not std::is_constant_evaluated()) {
36#if HI_COMPILER == HI_CC_CLANG || HI_COMPILER == HI_CC_GCC
37 if constexpr (sizeof(T) == sizeof(uint64_t)) {
38 return static_cast<T>(__builtin_bswap64(static_cast<uint64_t>(x)));
39 } else if constexpr (sizeof(T) == sizeof(uint32_t)) {
40 return static_cast<T>(__builtin_bswap32(static_cast<uint32_t>(x)));
41 } else if constexpr (sizeof(T) == sizeof(uint16_t)) {
42 return static_cast<T>(__builtin_bswap16(static_cast<uint16_t>(x)));
43 }
44#elif HI_COMPILER == HI_CC_MSVC
45 if constexpr (sizeof(T) == sizeof(uint64_t)) {
46 return static_cast<T>(_byteswap_uint64(static_cast<uint64_t>(x)));
47 } else if constexpr (sizeof(T) == sizeof(unsigned long)) {
48 return static_cast<T>(_byteswap_ulong(static_cast<unsigned long>(x)));
49 } else if constexpr (sizeof(T) == sizeof(unsigned short)) {
50 return static_cast<T>(_byteswap_ushort(static_cast<unsigned short>(x)));
51 }
52#endif
53 }
54
55 if constexpr (sizeof(T) == 1) {
56 return x;
57 } else {
58 auto r = T{};
59 for (auto i = 0_uz; i != sizeof(T); ++i) {
60 r <<= 8;
61 r |= static_cast<uint8_t>(x);
62 x >>= 8;
63 }
64 return r;
65 }
66}
67
68template<std::signed_integral T>
69[[nodiscard]] constexpr T byte_swap(T x) noexcept
70{
71 return static_cast<T>(byte_swap(static_cast<std::make_unsigned_t<T>>(x)));
72}
73
74template<std::floating_point T>
75[[nodiscard]] constexpr T byte_swap(T x) noexcept
76{
77 if constexpr (std::is_same_v<T, float>) {
78 auto utmp = std::bit_cast<uint32_t>(x);
79 utmp = byte_swap(utmp);
80 return std::bit_cast<float>(x);
81 } else if constexpr (std::is_same_v<T, double>) {
82 auto utmp = std::bit_cast<uint64_t>(x);
83 utmp = byte_swap(utmp);
84 return std::bit_cast<double>(x);
85 } else {
87 }
88}
89
92template<std::integral T>
93[[nodiscard]] constexpr T little_to_native(T x)
94{
95 if constexpr (std::endian::native == std::endian::little) {
96 return x;
97 } else {
98 return byte_swap(x);
99 }
100}
101
104template<std::integral T>
105[[nodiscard]] constexpr T big_to_native(T x)
106{
107 if constexpr (std::endian::native == std::endian::big) {
108 return x;
109 } else {
110 return byte_swap(x);
111 }
112}
113
116template<std::integral T>
117[[nodiscard]] constexpr T native_to_little(T x)
118{
119 if constexpr (std::endian::native == std::endian::little) {
120 return x;
121 } else {
122 return byte_swap(x);
123 }
124}
125
128template<std::integral T>
129[[nodiscard]] constexpr T native_to_big(T x)
130{
131 if constexpr (std::endian::native == std::endian::big) {
132 return x;
133 } else {
134 return byte_swap(x);
135 }
136}
137
145template<numeric T, std::endian Endian = std::endian::native>
146[[nodiscard]] constexpr T load(T const *src) noexcept
147{
148 auto value = *src;
149 if constexpr (Endian != std::endian::native) {
150 value = byte_swap(value);
151 }
152 return value;
153}
154
162template<numeric T, std::endian Endian = std::endian::native, byte_like B>
163[[nodiscard]] constexpr T load(B const *src) noexcept
164{
165 auto value = unaligned_load<T>(src);
166 if constexpr (Endian != std::endian::native) {
167 value = byte_swap(value);
168 }
169 return value;
170}
171
179template<numeric T, std::endian Endian = std::endian::native>
180[[nodiscard]] inline T load(void const *src) noexcept
181{
182 auto value = unaligned_load<T>(src);
183 if constexpr (Endian != std::endian::native) {
184 value = byte_swap(value);
185 }
186 return value;
187}
188
195template<numeric T>
196[[nodiscard]] constexpr T load_le(T const *src) noexcept
197{
198 return load<T, std::endian::little>(src);
199}
200
207template<numeric T, byte_like B>
208[[nodiscard]] constexpr T load_le(B const *src) noexcept
209{
210 return load<T, std::endian::little>(src);
211}
212
219template<numeric T>
220[[nodiscard]] inline T load_le(void const *src) noexcept
221{
222 return load<T, std::endian::little>(src);
223}
224
231template<numeric T>
232[[nodiscard]] constexpr T load_be(T const *src) noexcept
233{
234 return load<T, std::endian::big>(src);
235}
236
243template<numeric T, byte_like B>
244[[nodiscard]] constexpr T load_be(B const *src) noexcept
245{
246 return load<T, std::endian::big>(src);
247}
248
255template<numeric T>
256[[nodiscard]] inline T load_be(void const *src) noexcept
257{
258 return load<T, std::endian::big>(src);
259}
260
276template<unsigned int NumBits, byte_like B>
277[[nodiscard]] constexpr auto load_bits_be(B const *src, size_t bit_index) noexcept
278{
279 static_assert(NumBits <= sizeof(unsigned long long) * CHAR_BIT);
280
281 constexpr auto num_bits = NumBits;
282 constexpr auto num_bytes = (num_bits + CHAR_BIT - 1) / CHAR_BIT;
283
284 // Determine an unsigned type that can be used to read NumBits in a single `load_be()` on every bit offset.
285 // clang-format off
286 using value_type =
287 std::conditional_t<num_bytes < sizeof(unsigned short), unsigned short,
288 std::conditional_t<num_bytes < sizeof(unsigned int), unsigned int,
289 std::conditional_t<num_bytes < sizeof(unsigned long), unsigned long, unsigned long long>>>;
290 // clang-format on
291
292 constexpr auto value_bits = sizeof(value_type) * CHAR_BIT;
293
294 hilet byte_offset = bit_index / CHAR_BIT;
295 hilet bit_offset = bit_index % CHAR_BIT;
296
297 // Optimization of reading a byte, aligned to a byte.
298 if constexpr (num_bits == CHAR_BIT) {
299 if (bit_offset == 0) {
300 return char_cast<value_type>(src[byte_offset]);
301 }
302 }
303
304 // load_be allows unaligned reads.
305 auto r = load_be<value_type>(std::addressof(src[byte_offset]));
306
307 // Align to most significant bit. In preparation for loading
308 // one more byte.
309 r <<= bit_offset;
310
311 if constexpr (num_bytes == sizeof(value_type)) {
312 // In this case it is possible we could not read the whole value in one go.
313 // We may need to read one more byte.
314
315 auto bits_done = value_bits - bit_offset;
316 if (bits_done < num_bits) {
317 auto rest = char_cast<value_type>(src[byte_offset + sizeof(value_type)]);
318 rest >>= CHAR_BIT - bit_offset;
319 r |= rest;
320 }
321 }
322
323 // Align number to least significant bit.
324 r >>= value_bits - num_bits;
325 return r;
326}
327
328
329
330template<std::endian Endian = std::endian::native, numeric T, byte_like B>
331constexpr void store(T value, B const *dst) noexcept
332{
333 if constexpr (Endian != std::endian::native) {
334 value = byte_swap(value);
335 }
336 unaligned_store<T>(value, dst);
337}
338
339template<std::endian Endian = std::endian::native, numeric T>
340constexpr void store(T value, void const *dst) noexcept
341{
342 if constexpr (Endian != std::endian::native) {
343 value = byte_swap(value);
344 }
345 unaligned_store<T>(value, dst);
346}
347
348template<numeric T, byte_like B>
349constexpr void store_le(T value, B const *dst) noexcept
350{
351 store<std::endian::little>(value, dst);
352}
353
354template<numeric T>
355inline void store_le(T value, void const *dst) noexcept
356{
357 store<std::endian::little>(value, dst);
358}
359
360template<numeric T, byte_like B>
361constexpr void store_be(T value, B const *dst) noexcept
362{
363 store<std::endian::big>(value, dst);
364}
365
366template<numeric T>
367inline void store_be(T value, void const *dst) noexcept
368{
369 store<std::endian::big>(value, dst);
370}
371
372
373template<typename T, std::endian E, std::size_t A = alignof(T)>
375 using value_type = T;
376 constexpr static std::endian endian = E;
377 constexpr static std::size_t alignment = A;
378
379 alignas(A) std::byte _value[sizeof(T)];
380
381 [[nodiscard]] constexpr value_type operator*() const noexcept
382 {
383 return load<value_type, endian>(_value);
384 }
385
386 constexpr endian_buf_t& operator=(value_type x) noexcept
387 {
388 store<endian>(x, _value);
389 return *this;
390 }
391};
392
411
430
431} // namespace hi::inline v1
432
433hi_warning_pop();
#define hi_static_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:323
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:13
constexpr T big_to_native(T x)
Convert an integral from big-to-native endian.
Definition endian.hpp:105
constexpr T little_to_native(T x)
Convert an integral from little-to-native endian.
Definition endian.hpp:93
constexpr T native_to_big(T x)
Convert an integral from native-to-big endian.
Definition endian.hpp:129
constexpr T load_le(T const *src) noexcept
Load of a numeric value encoded in little-endian format.
Definition endian.hpp:196
constexpr T native_to_little(T x)
Convert an integral from native-to-little endian.
Definition endian.hpp:117
constexpr T load_be(T const *src) noexcept
Load of a numeric value encoded in big-endian format.
Definition endian.hpp:232
constexpr auto load_bits_be(B const *src, size_t bit_index) noexcept
Unaligned load bits from a big-endian buffer at a bit-offset.
Definition endian.hpp:277
constexpr T load(T const *src) noexcept
Load a numeric value from memory.
Definition endian.hpp:146
Horizontal/Vertical alignment combination.
Definition alignment.hpp:238
Definition endian.hpp:374
T addressof(T... args)