| 98 | /// stop if it finds a byte aligned value start. |
| 99 | template <int kPackedBitWidth, bool kIsProlog, typename Uint> |
| 100 | ARROW_FORCE_INLINE int unpack_exact(const uint8_t* in, const uint8_t* in_end, Uint* out, |
| 101 | int batch_size, int bit_offset) { |
| 102 | static_assert(kPackedBitWidth > 0); |
| 103 | |
| 104 | // For the epilog we adapt the max spread since better alignment give shorter spreads |
| 105 | ARROW_DCHECK(kIsProlog || bit_offset == 0); |
| 106 | ARROW_DCHECK(bit_offset >= 0 && bit_offset < 8); |
| 107 | constexpr int kMaxSpreadBytes = kIsProlog ? PackedMaxSpreadBytes(kPackedBitWidth) |
| 108 | : PackedMaxSpreadBytes(kPackedBitWidth, 0); |
| 109 | using buffer_uint = SpreadBufferUint<kMaxSpreadBytes>; |
| 110 | constexpr int kBufferSize = sizeof(buffer_uint); |
| 111 | // Due to misalignment, on large bit width, the spread can be larger than the maximum |
| 112 | // size integer. For instance a 63 bit width misaligned packed integer can spread over 9 |
| 113 | // aligned bytes. |
| 114 | constexpr bool kLarge = kBufferSize < kMaxSpreadBytes; |
| 115 | constexpr buffer_uint kLowMask = |
| 116 | bit_util::LeastSignificantBitMask<buffer_uint, true>(kPackedBitWidth); |
| 117 | |
| 118 | ARROW_DCHECK_GE(bit_offset, 0); |
| 119 | ARROW_DCHECK_LE(bit_offset, 8); |
| 120 | |
| 121 | // Looping over values one by one |
| 122 | const int start_bit_term = batch_size * kPackedBitWidth + bit_offset; |
| 123 | int start_bit = bit_offset; |
| 124 | while ((start_bit < start_bit_term) && (!kIsProlog || (start_bit % 8 != 0))) { |
| 125 | const int start_byte = start_bit / 8; |
| 126 | const int spread_bytes = ((start_bit + kPackedBitWidth - 1) / 8) - start_byte + 1; |
| 127 | ARROW_DCHECK_LE(spread_bytes, kMaxSpreadBytes); |
| 128 | ARROW_COMPILER_ASSUME(spread_bytes <= kMaxSpreadBytes); |
| 129 | |
| 130 | // Reading the bytes for the current value. |
| 131 | buffer_uint buffer = 0; |
| 132 | if (ARROW_PREDICT_TRUE(in + start_byte + kBufferSize < in_end)) { |
| 133 | // Fast path we read the whole buffer. In all but few last reads (plural!) we will |
| 134 | // always have enough bytes left in the buffer to avoid out-of-bounds reads. On top |
| 135 | // of this, in Arrow, `unpack` is always called with `max_read_bytes` set, meaning |
| 136 | // this will often be the *only* path taken. |
| 137 | // We added this special case because `std::memcpy` without a compile time constant |
| 138 | // was not inlined and optimized properly by the compiler, resulting to a function |
| 139 | // call on each iteration. |
| 140 | // This also handles the `kLarge` case detailed below. |
| 141 | std::memcpy(&buffer, in + start_byte, kBufferSize); |
| 142 | } else { |
| 143 | // Slow path, we need to read exactly the correct number of bytes to avoid |
| 144 | // out-of-bounds reads. |
| 145 | if constexpr (kLarge) { |
| 146 | // We read the max possible bytes in the first pass and handle the rest after. |
| 147 | // Even though the worst spread does not happen on all iterations we can still |
| 148 | // read all bytes because we will mask them. |
| 149 | std::memcpy(&buffer, in + start_byte, std::min(kBufferSize, spread_bytes)); |
| 150 | } else { |
| 151 | std::memcpy(&buffer, in + start_byte, spread_bytes); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | buffer = bit_util::FromLittleEndian(buffer); |
| 156 | const int bit_offset = start_bit % 8; |
| 157 | buffer >>= bit_offset; |
nothing calls this directly
no test coverage detected