blob: 14b120c6714c320dcc0ee2db5e7c2a84c80a7d50 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/yet_another_bit_vector.h"
#include <algorithm>
#include <utility>
#include "util/osp_logging.h"
namespace openscreen {
namespace {
// Returns a bitmask where all the bits whose positions are in the range
// [begin,begin+count) are set, and all other bits are cleared.
constexpr uint64_t MakeBitmask(int begin, int count) {
// Form a contiguous sequence of bits by subtracting one from the appropriate
// power of 2. Set all the bits if count >= 64.
const uint64_t bits_in_wrong_position =
(count >= std::numeric_limits<uint64_t>::digits)
? std::numeric_limits<uint64_t>::max()
: ((uint64_t{1} << count) - 1);
// Now shift the contiguous sequence of bits into the correct position.
return bits_in_wrong_position << begin;
}
} // namespace
YetAnotherBitVector::YetAnotherBitVector() : size_(0), bits_{.as_integer = 0} {}
YetAnotherBitVector::YetAnotherBitVector(int size,
YetAnotherBitVector::Fill fill) {
InitializeForNewSize(size, fill);
}
YetAnotherBitVector::~YetAnotherBitVector() {
if (using_array_storage()) {
delete[] bits_.as_array;
}
}
YetAnotherBitVector::YetAnotherBitVector(YetAnotherBitVector&& other)
: size_(other.size_), bits_(other.bits_) {
other.size_ = 0;
other.bits_.as_integer = 0;
}
YetAnotherBitVector& YetAnotherBitVector::operator=(
YetAnotherBitVector&& other) {
if (this == &other) {
return *this;
}
if (using_array_storage()) {
delete[] bits_.as_array;
}
size_ = other.size_;
bits_ = other.bits_;
other.size_ = 0;
other.bits_.as_integer = 0;
return *this;
}
bool YetAnotherBitVector::IsSet(int pos) const {
OSP_DCHECK_LT(pos, size_);
const uint64_t* const elem = Select(&pos);
return (*elem & (uint64_t{1} << pos)) != 0;
}
void YetAnotherBitVector::Set(int pos) {
OSP_DCHECK_LT(pos, size_);
uint64_t* const elem = const_cast<uint64_t*>(Select(&pos));
*elem |= (uint64_t{1} << pos);
}
void YetAnotherBitVector::Clear(int pos) {
OSP_DCHECK_LT(pos, size_);
uint64_t* const elem = const_cast<uint64_t*>(Select(&pos));
*elem &= ~(uint64_t{1} << pos);
}
void YetAnotherBitVector::Resize(int new_size, YetAnotherBitVector::Fill fill) {
if (using_array_storage()) {
delete[] bits_.as_array;
}
InitializeForNewSize(new_size, fill);
}
void YetAnotherBitVector::SetAll() {
// Implementation note: Set all bits except those in the last integer that are
// outside the defined range. This allows the other operations to become
// simpler and more efficient because they can assume the bits moving into the
// valid range are not set.
if (using_array_storage()) {
const int last_index = array_size() - 1;
uint64_t* const last = &bits_.as_array[last_index];
std::fill(&bits_.as_array[0], last, kAllBitsSet);
*last = MakeBitmask(0, size_ - (last_index * kBitsPerInteger));
} else {
bits_.as_integer = MakeBitmask(0, size_);
}
}
void YetAnotherBitVector::ClearAll() {
if (using_array_storage()) {
std::fill(&bits_.as_array[0], &bits_.as_array[array_size()], kNoBitsSet);
} else {
bits_.as_integer = kNoBitsSet;
}
}
void YetAnotherBitVector::ShiftRight(int steps) {
// Negative |steps| should probably mean "shift left," but this is not
// implemented.
OSP_DCHECK_GE(steps, 0);
OSP_DCHECK_LE(steps, size_);
if (using_array_storage()) {
// If |steps| is greater than one integer's worth of bits, first shift the
// array elements right. This is effectively shifting all the bits right by
// some multiple of 64.
const int num_integers = array_size();
if (steps >= kBitsPerInteger) {
const int integer_steps = steps / kBitsPerInteger;
for (int i = integer_steps; i < num_integers; ++i) {
bits_.as_array[i - integer_steps] = bits_.as_array[i];
}
std::fill(&bits_.as_array[num_integers - integer_steps],
&bits_.as_array[num_integers], kNoBitsSet);
steps %= kBitsPerInteger;
}
// With |steps| now less than 64, shift the bits right within each array
// element. Start from the back of the array, working towards the front, and
// propagating any bits that are moving across array elements.
uint64_t incoming_carry_bits = 0;
const uint64_t outgoing_mask = MakeBitmask(0, steps);
for (int i = num_integers; i-- > 0;) {
const uint64_t outgoing_carry_bits = bits_.as_array[i] & outgoing_mask;
bits_.as_array[i] >>= steps;
bits_.as_array[i] |= (incoming_carry_bits << (kBitsPerInteger - steps));
incoming_carry_bits = outgoing_carry_bits;
}
} else {
if (steps < kBitsPerInteger) {
bits_.as_integer >>= steps;
} else {
bits_.as_integer = 0;
}
}
}
int YetAnotherBitVector::FindFirstSet() const {
// Almost all processors provide a single instruction to "count trailing
// zeros" in an integer, which is great because this is the same as the
// 0-based index of the first set bit. So, have the compiler use that
// whenever it's available. However, note that the intrinsic (and the CPU
// instruction used) provides undefined results when operating on zero; and
// so that special case is checked and handled.
#if defined(__clang__) || defined(__GNUC__)
#define CountTrailingZeros(bits) __builtin_ctzll(bits)
#else
const auto CountTrailingZeros = [](uint64_t bits) -> int {
// Based on one of the public domain "Bit Twiddling Hacks" heuristics:
// https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightParallel
// clang-format off
bits &= ~bits + 1;
int count = kBitsPerInteger;
if (bits) --count;
if (bits & UINT64_C(0x00000000ffffffff)) count -= 32;
if (bits & UINT64_C(0x0000ffff0000ffff)) count -= 16;
if (bits & UINT64_C(0x00ff00ff00ff00ff)) count -= 8;
if (bits & UINT64_C(0x0f0f0f0f0f0f0f0f)) count -= 4;
if (bits & UINT64_C(0x3333333333333333)) count -= 2;
if (bits & UINT64_C(0x5555555555555555)) count -= 1;
return count;
// clang-format on
};
#endif
if (using_array_storage()) {
for (int i = 0, end = array_size(); i < end; ++i) {
if (bits_.as_array[i] != 0) {
return (i * kBitsPerInteger) + CountTrailingZeros(bits_.as_array[i]);
}
}
return size_; // All bits are not set.
}
return (bits_.as_integer != 0) ? CountTrailingZeros(bits_.as_integer) : size_;
}
int YetAnotherBitVector::CountBitsSet(int begin, int end) const {
OSP_DCHECK_LE(0, begin);
OSP_DCHECK_LE(begin, end);
OSP_DCHECK_LE(end, size_);
// Almost all processors provide a single instruction to "count the number of
// bits set" in an integer. So, have the compiler use that whenever it's
// available.
#if defined(__clang__) || defined(__GNUC__)
#define PopCount(bits) __builtin_popcountll(bits)
#else
const auto PopCount = [](uint64_t bits) -> int {
// Based on one of the public domain "Bit Twiddling Hacks" heuristics:
// https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
constexpr int kBitsPerByte = 8;
bits = bits - ((bits >> 1) & (kAllBitsSet / 3));
bits = (bits & (kAllBitsSet / 15 * 3)) +
((bits >> 2) & (kAllBitsSet / 15 * 3));
bits = (bits + (bits >> 4)) & (kAllBitsSet / 255 * 15);
const uint64_t count =
(bits * (kAllBitsSet / 255)) >> ((sizeof(uint64_t) - 1) * kBitsPerByte);
return static_cast<int>(count);
};
#endif
int count;
if (using_array_storage()) {
const int first = begin / kBitsPerInteger;
const int last = (end - 1) / kBitsPerInteger;
if (first == last) {
count = PopCount(bits_.as_array[first] &
MakeBitmask(begin % kBitsPerInteger, end - begin));
} else if (first < last) {
// Count a subset of the bits in the first and last integers (according to
// |begin| and |end|), and all of the bits in the integers in-between.
const uint64_t* p = &bits_.as_array[first];
count = PopCount((*p) &
MakeBitmask(begin % kBitsPerInteger, kBitsPerInteger));
for (++p; p != &bits_.as_array[last]; ++p) {
count += PopCount(*p);
}
count += PopCount((*p) & MakeBitmask(0, end - (last * kBitsPerInteger)));
} else {
count = 0;
}
} else {
count = PopCount(bits_.as_integer & MakeBitmask(begin, end - begin));
}
return count;
}
void YetAnotherBitVector::InitializeForNewSize(int new_size, Fill fill) {
OSP_DCHECK_GE(new_size, 0);
size_ = new_size;
if (using_array_storage()) {
bits_.as_array = new uint64_t[array_size()];
}
if (fill == SET) {
SetAll();
} else {
ClearAll();
}
}
const uint64_t* YetAnotherBitVector::Select(int* pos) const {
if (using_array_storage()) {
const int index = *pos / kBitsPerInteger;
*pos %= kBitsPerInteger;
return &bits_.as_array[index];
}
return &bits_.as_integer;
}
// NOTE: These declarations can be removed when C++17 compliance is mandatory
// for all embedders, as static constexpr members can be declared inline.
constexpr int YetAnotherBitVector::kBitsPerInteger;
constexpr uint64_t YetAnotherBitVector::kAllBitsSet;
constexpr uint64_t YetAnotherBitVector::kNoBitsSet;
} // namespace openscreen