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;
}
```

Reply via email to