// Bit memory region is a bit offset subregion of a normal memoryregion. This is useful for // abstracting away the bit start offset to avoid needing passing as an argument everywhere. class BitMemoryRegion final : public ValueObject { public: // Ensure all loads are naturally-aligned by aligning down the region's data pointer according to // the largest data type that will be loaded via LoadBits (as StackMap BitTable uses over 8 // varints in the header, this is uint64_t). using MaxSingleLoadType = uint64_t; static constexpr size_t kMaxSingleLoadBytes = sizeof(MaxSingleLoadType);
// Load a single bit in the region. The bit at offset 0 is the least // significant bit in the first byte.
ALWAYS_INLINE bool LoadBit(size_t bit_offset) const {
DCHECK_LT(bit_offset, bit_size_);
size_t index = (bit_start_ + bit_offset) / kBitsPerByte;
size_t shift = (bit_start_ + bit_offset) % kBitsPerByte; return ((data_[index] >> shift) & 1) != 0;
}
// Load `bit_length` bits from `data` starting at given `bit_offset`. // The least significant bit is stored in the smallest memory offset. template<typename Result = size_t>
ATTRIBUTE_NO_SANITIZE_ADDRESS // We might touch extra bytes due to the alignment.
ATTRIBUTE_NO_SANITIZE_HWADDRESS // The hwasan uses different attribute.
ALWAYS_INLINE Result LoadBits(size_t bit_offset, size_t bit_length) const {
static_assert(std::is_integral_v<Result>, "Result must be integral");
static_assert(std::is_unsigned_v<Result>, "Result must be unsigned");
static_assert(sizeof(Result) <= kMaxSingleLoadBytes);
DCHECK(IsAligned<sizeof(Result)>(data_));
DCHECK_LE(bit_offset, bit_size_);
DCHECK_LE(bit_length, bit_size_ - bit_offset);
DCHECK_LE(bit_length, BitSizeOf<Result>()); if (bit_length == 0) { return0;
} // Load naturally-aligned value which contains the least significant bit.
Result* data = reinterpret_cast<Result*>(data_);
size_t width = BitSizeOf<Result>();
size_t index = (bit_start_ + bit_offset) / width;
size_t shift = (bit_start_ + bit_offset) % width;
Result value = data[index] >> shift; // Load extra value containing the most significant bit (it might be the same one). // We can not just load the following value as that could potentially cause SIGSEGV.
Result extra = data[index + (shift + (bit_length - 1)) / width]; // Mask to clear unwanted bits (the 1s are needed to avoid avoid undefined shift).
Result clear = (std::numeric_limits<Result>::max() << 1) << (bit_length - 1); // Prepend the extra value. We add explicit '& (width - 1)' so that the shift is defined. // It is a no-op for `shift != 0` and if `shift == 0` then `value == extra` because of // bit_length <= width causing the `value` and `extra` to be read from the same location. // The '& (width - 1)' is implied by the shift instruction on ARM and removed by compiler. return (value | (extra << ((width - shift) & (width - 1)))) & ~clear;
}
// Store `bit_length` bits in `data` starting at given `bit_offset`. // The least significant bit is stored in the smallest memory offset.
ALWAYS_INLINE void StoreBits(size_t bit_offset, size_t value, size_t bit_length) {
DCHECK_LE(bit_offset, bit_size_);
DCHECK_LE(bit_length, bit_size_ - bit_offset);
DCHECK_LE(bit_length, BitSizeOf<size_t>());
DCHECK_LE(value, MaxInt<size_t>(bit_length)); if (bit_length == 0) { return;
} // Write data byte by byte to avoid races with other threads // on bytes that do not overlap with this region.
size_t mask = std::numeric_limits<size_t>::max() >> (BitSizeOf<size_t>() - bit_length);
size_t index = (bit_start_ + bit_offset) / kBitsPerByte;
size_t shift = (bit_start_ + bit_offset) % kBitsPerByte;
data_[index] &= ~(mask << shift); // Clear bits.
data_[index] |= (value << shift); // Set bits.
size_t finished_bits = kBitsPerByte - shift; for (int i = 1; finished_bits < bit_length; i++, finished_bits += kBitsPerByte) {
data_[index + i] &= ~(mask >> finished_bits); // Clear bits.
data_[index + i] |= (value >> finished_bits); // Set bits.
}
DCHECK_EQ(value, LoadBits(bit_offset, bit_length));
}
// Copy bits from other bit region.
ALWAYS_INLINE void CopyBits(const BitMemoryRegion& src) {
DCHECK_EQ(size_in_bits(), src.size_in_bits()); // Hopefully, the loads of the unused `value` shall be optimized away.
VisitChunks([this, &src](size_t offset, size_t num_bits, [[maybe_unused]] size_t value)
ALWAYS_INLINE {
StoreChunk(offset, src.LoadBits(offset, num_bits), num_bits); returntrue;
});
}
// And bits from other bit region.
ALWAYS_INLINE void AndBits(const BitMemoryRegion& src) {
DCHECK_EQ(size_in_bits(), src.size_in_bits());
VisitChunks([this, &src](size_t offset, size_t num_bits, size_t value) ALWAYS_INLINE {
StoreChunk(offset, value & src.LoadBits(offset, num_bits), num_bits); returntrue;
});
}
// Or bits from other bit region.
ALWAYS_INLINE void OrBits(const BitMemoryRegion& src) {
DCHECK_EQ(size_in_bits(), src.size_in_bits());
VisitChunks([this, &src](size_t offset, size_t num_bits, size_t value) ALWAYS_INLINE {
StoreChunk(offset, value | src.LoadBits(offset, num_bits), num_bits); returntrue;
});
}
// Xor bits from other bit region.
ALWAYS_INLINE void XorBits(const BitMemoryRegion& src) {
DCHECK_EQ(size_in_bits(), src.size_in_bits());
VisitChunks([this, &src](size_t offset, size_t num_bits, size_t value) ALWAYS_INLINE {
StoreChunk(offset, value ^ src.LoadBits(offset, num_bits), num_bits); returntrue;
});
}
// Count the number of set bits within this region.
ALWAYS_INLINE size_t PopCount() const {
size_t result = 0u;
VisitChunks([&]([[maybe_unused]] size_t offset, [[maybe_unused]] size_t num_bits, size_t value)
ALWAYS_INLINE {
result += POPCOUNT(value); returntrue;
}); return result;
}
// Count the number of set bits within the given bit range.
ALWAYS_INLINE size_t PopCount(size_t bit_offset, size_t bit_length) const { return Subregion(bit_offset, bit_length).PopCount();
}
// Check if this region has all bits clear.
ALWAYS_INLINE bool HasAllBitsClear() const { return VisitChunks(
[]([[maybe_unused]] size_t offset, [[maybe_unused]] size_t num_bits, size_t value)
ALWAYS_INLINE { return value == 0u; });
}
// Check if this region has any bit set.
ALWAYS_INLINE bool HasSomeBitSet() const { return !HasAllBitsClear();
}
// Check if there is any bit set within the given bit range.
ALWAYS_INLINE bool HasSomeBitSet(size_t bit_offset, size_t bit_length) const { return Subregion(bit_offset, bit_length).HasSomeBitSet();
}
staticint Compare(const BitMemoryRegion& lhs, const BitMemoryRegion& rhs) { if (lhs.size_in_bits() != rhs.size_in_bits()) { return (lhs.size_in_bits() < rhs.size_in_bits()) ? -1 : 1;
} int result = 0; bool equals = lhs.VisitChunks(
[&](size_t offset, size_t num_bits, size_t lhs_value) ALWAYS_INLINE {
size_t rhs_value = rhs.LoadBits(offset, num_bits); if (lhs_value == rhs_value) { returntrue;
} // We have found a difference. To avoid the comparison being dependent on how the region // is split into chunks, check the lowest bit that differs. (Android is little-endian.) int bit = CTZ(lhs_value ^ rhs_value);
result = ((rhs_value >> bit) & 1u) != 0u ? 1 : -1; returnfalse; // Stop iterating.
});
DCHECK_EQ(equals, result == 0); return result;
}
uint8_t* data_ = nullptr; // The pointer is aligned down to kMaxSingleLoadBytes.
size_t bit_start_ = 0;
size_t bit_size_ = 0;
};
// Minimum number of bits used for varint. A varint represents either a value stored "inline" or // the number of bytes that are required to encode the value.
constexpr uint32_t kVarintBits = 4; // Maximum value which is stored "inline". We use the rest of the values to encode the number of // bytes required to encode the value when the value is greater than kVarintMax. // We encode any value less than or equal to 11 inline. We use 12, 13, 14 and 15 // to represent that the value is encoded in 1, 2, 3 and 4 bytes respectively. // // For example if we want to encode 1, 15, 16, 7, 11, 256: // // Low numbers (1, 7, 11) are encoded inline. 15 and 12 are set with 12 to show // we need to load one byte for each to have their real values (15 and 12), and // 256 is set with 13 to show we need to load two bytes. This is done to // compress the values in the bit array and keep the size down. Where the actual value // is read from depends on the use case. // // Values greater than kVarintMax could be encoded as a separate list referred // to as InterleavedVarints (see ReadInterleavedVarints / WriteInterleavedVarints). // This is used when there are fixed number of fields like CodeInfo headers. // In our example the interleaved encoding looks like below: // // Meaning: 1--- 15-- 12-- 7--- 11-- 256- 15------- 12------- 256---------------- // Bits: 0001 1100 1100 0111 1011 1101 0000 1111 0000 1100 0000 0001 0000 0000 // // In other cases the value is recorded just following the size encoding. This is // referred as consecutive encoding (See ReadVarint / WriteVarint). In our // example the consecutively encoded varints looks like below: // // Meaning: 1--- 15-- 15------- 12-- 12------- 7--- 11-- 256- 256---------------- // Bits: 0001 1100 0000 1100 1100 0000 1100 0111 1011 1101 0000 0001 0000 0000
constexpr uint32_t kVarintMax = 11;
class BitMemoryReader { public:
BitMemoryReader(BitMemoryReader&&) = default; explicit BitMemoryReader(BitMemoryRegion data)
: finished_region_(data.Subregion(0, 0) /* set the length to zero */ ) {
} explicit BitMemoryReader(const uint8_t* data, ssize_t bit_offset = 0)
: finished_region_(const_cast<uint8_t*>(data), bit_offset, /* bit_length */ 0) {
}
// Read variable-length bit-packed integer. // The first four bits determine the variable length of the encoded integer: // Values 0..11 represent the result as-is, with no further following bits. // Values 12..15 mean the result is in the next 8/16/24/32-bits respectively.
ALWAYS_INLINE uint32_t ReadVarint() {
uint32_t x = ReadBits(kVarintBits); return (x <= kVarintMax) ? x : ReadBits((x - kVarintMax) * kBitsPerByte);
}
// Read N 'interleaved' varints (different to just reading consecutive varints). // All small values are stored first and the large values are stored after them. // This requires fewer bit-reads compared to indidually storing the varints. template<size_t N>
ALWAYS_INLINE std::array<uint32_t, N> ReadInterleavedVarints() {
static_assert(N * kVarintBits <= BitMemoryRegion::kMaxSingleLoadBytes * kBitsPerByte, "N too big");
std::array<uint32_t, N> values; // StackMap BitTable uses over 8 varints in the header, so we need uint64_t.
uint64_t data = ReadBits<uint64_t>(N * kVarintBits); for (size_t i = 0; i < N; i++) {
values[i] = BitFieldExtract(data, i * kVarintBits, kVarintBits);
} // Do the second part in its own loop as that seems to produce better code in clang. for (size_t i = 0; i < N; i++) { if (UNLIKELY(values[i] > kVarintMax)) {
values[i] = ReadBits((values[i] - kVarintMax) * kBitsPerByte);
}
} return values;
}
private: // Represents all of the bits which were read so far. There is no upper bound. // Therefore, by definition, the "cursor" is always at the end of the region.
BitMemoryRegion finished_region_;
template<size_t N>
ALWAYS_INLINE void WriteInterleavedVarints(std::array<uint32_t, N> values) { // Write small values (or the number of bytes needed for the large values). for (uint32_t value : values) { if (value > kVarintMax) {
WriteBits(kVarintMax + BitsToBytesRoundUp(MinimumBitsToStore(value)), kVarintBits);
} else {
WriteBits(value, kVarintBits);
}
} // Write large values. for (uint32_t value : values) { if (value > kVarintMax) {
WriteBits(value, BitsToBytesRoundUp(MinimumBitsToStore(value)) * kBitsPerByte);
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.