https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93165
Avi Kivity <avi at scylladb dot com> changed:
What |Removed |Added
----------------------------------------------------------------------------
CC| |avi at scylladb dot com
--- Comment #8 from Avi Kivity <avi at scylladb dot com> ---
Here's another example. Here I want to use variable-length serialization
without resorting to a loop or branches, but gcc refuses.
```
#include <cstdint>
#include <bit>
#include <algorithm>
#if !__has_builtin(__builtin_unpredictable)
#define __builtin_unpredictable(x) __builtin_expect_with_probability(x, 1, 0.5)
#endif
template <typename T>
inline
void
write_be(char* p, T datum) noexcept {
datum = std::byteswap(datum);
std::copy_n(reinterpret_cast<const char*>(&datum), sizeof(T), p);
}
static
unsigned serialized_size_from_first_byte(int8_t first_byte) {
return 1 + std::countl_zero(static_cast<uint8_t>(~first_byte));
}
static uint64_t first_byte_value_mask(unsigned extra_bytes_size) {
// Include the sentinel zero bit in the mask.
return uint64_t(0xff) >> extra_bytes_size;
}
static
unsigned serialized_size(uint64_t value) noexcept {
// No need for the overhead of checking that all bits are zero.
//
// A signed quantity, to allow the case of `magnitude == 0` to result in a
value of 9 below.
const auto magnitude = static_cast<int64_t>(std::countl_zero(value |
uint64_t(1)));
return unsigned(9) - unsigned((magnitude - 1) / 7);
}
unsigned serialize(uint64_t value, char* out) {
const auto size = serialized_size(value);
// `size` is always in the range [1, 9].
auto extra_bytes_size = size - 1;
*out++ = ((value >> (extra_bytes_size * 8)) & 0xff) |
~first_byte_value_mask(extra_bytes_size);
char garbage[8];
// Encode the remaining bytes in big-endian order, directing unneeded bytes
into a garbage array.
// This avoids conditional branches.
auto* dest64 = __builtin_unpredictable(extra_bytes_size == 8) ? out :
garbage;
auto delta64 = __builtin_unpredictable(extra_bytes_size == 8) ? 8 : 0;
write_be<uint64_t>(dest64, value);
extra_bytes_size -= delta64;
out += delta64;
auto* dest32 = __builtin_unpredictable(extra_bytes_size >= 4) ? out :
garbage;
auto delta32 = __builtin_unpredictable(extra_bytes_size >= 4) ? 4 : 0;
write_be<uint32_t>(dest32, value);
extra_bytes_size -= delta32;
out += delta32;
value >>= delta32 * 8;
auto* dest16 = __builtin_unpredictable(extra_bytes_size >= 2) ? out :
garbage;
auto delta16 = __builtin_unpredictable(extra_bytes_size >= 2) ? 2 : 0;
write_be<uint16_t>(dest16, value);
extra_bytes_size -= delta16;
out += delta16;
value >>= delta16 * 8;
auto* dest8 = __builtin_unpredictable(extra_bytes_size >= 1) ? out :
garbage;
auto delta8 = __builtin_unpredictable(extra_bytes_size >= 1) ? 1 : 0;
write_be<uint8_t>(dest8, value);
extra_bytes_size -= delta8;
out += delta8;
return size;
}
```