|
|
@@ -1,4 +1,4 @@
|
|
|
-// Copyright (c) 2016 Google Inc.
|
|
|
+// Copyright (c) 2023 Google Inc.
|
|
|
//
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
@@ -12,195 +12,438 @@
|
|
|
// See the License for the specific language governing permissions and
|
|
|
// limitations under the License.
|
|
|
|
|
|
-#ifndef SOURCE_ENUM_SET_H_
|
|
|
-#define SOURCE_ENUM_SET_H_
|
|
|
-
|
|
|
+#include <cassert>
|
|
|
#include <cstdint>
|
|
|
#include <functional>
|
|
|
-#include <memory>
|
|
|
-#include <set>
|
|
|
-#include <utility>
|
|
|
+#include <initializer_list>
|
|
|
+#include <limits>
|
|
|
+#include <type_traits>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
+#ifndef SOURCE_ENUM_SET_H_
|
|
|
+#define SOURCE_ENUM_SET_H_
|
|
|
|
|
|
#include "source/latest_version_spirv_header.h"
|
|
|
-#include "source/util/make_unique.h"
|
|
|
|
|
|
namespace spvtools {
|
|
|
|
|
|
-// A set of values of a 32-bit enum type.
|
|
|
-// It is fast and compact for the common case, where enum values
|
|
|
-// are at most 63. But it can represent enums with larger values,
|
|
|
-// as may appear in extensions.
|
|
|
-template <typename EnumType>
|
|
|
+// This container is optimized to store and retrieve unsigned enum values.
|
|
|
+// The base model for this implementation is an open-addressing hashtable with
|
|
|
+// linear probing. For small enums (max index < 64), all operations are O(1).
|
|
|
+//
|
|
|
+// - Enums are stored in buckets (64 contiguous values max per bucket)
|
|
|
+// - Buckets ranges don't overlap, but don't have to be contiguous.
|
|
|
+// - Enums are packed into 64-bits buckets, using 1 bit per enum value.
|
|
|
+//
|
|
|
+// Example:
|
|
|
+// - MyEnum { A = 0, B = 1, C = 64, D = 65 }
|
|
|
+// - 2 buckets are required:
|
|
|
+// - bucket 0, storing values in the range [ 0; 64[
|
|
|
+// - bucket 1, storing values in the range [64; 128[
|
|
|
+//
|
|
|
+// - Buckets are stored in a sorted vector (sorted by bucket range).
|
|
|
+// - Retrieval is done by computing the theoretical bucket index using the enum
|
|
|
+// value, and
|
|
|
+// doing a linear scan from this position.
|
|
|
+// - Insertion is done by retrieving the bucket and either:
|
|
|
+// - inserting a new bucket in the sorted vector when no buckets has a
|
|
|
+// compatible range.
|
|
|
+// - setting the corresponding bit in the bucket.
|
|
|
+// This means insertion in the middle/beginning can cause a memmove when no
|
|
|
+// bucket is available. In our case, this happens at most 23 times for the
|
|
|
+// largest enum we have (Opcodes).
|
|
|
+template <typename T>
|
|
|
class EnumSet {
|
|
|
private:
|
|
|
- // The ForEach method will call the functor on enum values in
|
|
|
- // enum value order (lowest to highest). To make that easier, use
|
|
|
- // an ordered set for the overflow values.
|
|
|
- using OverflowSetType = std::set<uint32_t>;
|
|
|
+ using BucketType = uint64_t;
|
|
|
+ using ElementType = std::underlying_type_t<T>;
|
|
|
+ static_assert(std::is_enum_v<T>, "EnumSets only works with enums.");
|
|
|
+ static_assert(std::is_signed_v<ElementType> == false,
|
|
|
+ "EnumSet doesn't supports signed enums.");
|
|
|
+
|
|
|
+ // Each bucket can hold up to `kBucketSize` distinct, contiguous enum values.
|
|
|
+ // The first value a bucket can hold must be aligned on `kBucketSize`.
|
|
|
+ struct Bucket {
|
|
|
+ // bit mask to store `kBucketSize` enums.
|
|
|
+ BucketType data;
|
|
|
+ // 1st enum this bucket can represent.
|
|
|
+ T start;
|
|
|
+
|
|
|
+ friend bool operator==(const Bucket& lhs, const Bucket& rhs) {
|
|
|
+ return lhs.start == rhs.start && lhs.data == rhs.data;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // How many distinct values can a bucket hold? 1 bit per value.
|
|
|
+ static constexpr size_t kBucketSize = sizeof(BucketType) * 8ULL;
|
|
|
|
|
|
public:
|
|
|
- // Construct an empty set.
|
|
|
- EnumSet() {}
|
|
|
- // Construct an set with just the given enum value.
|
|
|
- explicit EnumSet(EnumType c) { Add(c); }
|
|
|
- // Construct an set from an initializer list of enum values.
|
|
|
- EnumSet(std::initializer_list<EnumType> cs) {
|
|
|
- for (auto c : cs) Add(c);
|
|
|
- }
|
|
|
- EnumSet(uint32_t count, const EnumType* ptr) {
|
|
|
- for (uint32_t i = 0; i < count; ++i) Add(ptr[i]);
|
|
|
- }
|
|
|
- // Copy constructor.
|
|
|
- EnumSet(const EnumSet& other) { *this = other; }
|
|
|
- // Move constructor. The moved-from set is emptied.
|
|
|
- EnumSet(EnumSet&& other) {
|
|
|
- mask_ = other.mask_;
|
|
|
- overflow_ = std::move(other.overflow_);
|
|
|
- other.mask_ = 0;
|
|
|
- other.overflow_.reset(nullptr);
|
|
|
- }
|
|
|
- // Assignment operator.
|
|
|
- EnumSet& operator=(const EnumSet& other) {
|
|
|
- if (&other != this) {
|
|
|
- mask_ = other.mask_;
|
|
|
- overflow_.reset(other.overflow_ ? new OverflowSetType(*other.overflow_)
|
|
|
- : nullptr);
|
|
|
+ class Iterator {
|
|
|
+ public:
|
|
|
+ typedef Iterator self_type;
|
|
|
+ typedef T value_type;
|
|
|
+ typedef T& reference;
|
|
|
+ typedef T* pointer;
|
|
|
+ typedef std::forward_iterator_tag iterator_category;
|
|
|
+ typedef size_t difference_type;
|
|
|
+
|
|
|
+ Iterator(const Iterator& other)
|
|
|
+ : set_(other.set_),
|
|
|
+ bucketIndex_(other.bucketIndex_),
|
|
|
+ bucketOffset_(other.bucketOffset_) {}
|
|
|
+
|
|
|
+ Iterator& operator++() {
|
|
|
+ do {
|
|
|
+ if (bucketIndex_ >= set_->buckets_.size()) {
|
|
|
+ bucketIndex_ = set_->buckets_.size();
|
|
|
+ bucketOffset_ = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bucketOffset_ + 1 == kBucketSize) {
|
|
|
+ bucketOffset_ = 0;
|
|
|
+ ++bucketIndex_;
|
|
|
+ } else {
|
|
|
+ ++bucketOffset_;
|
|
|
+ }
|
|
|
+
|
|
|
+ } while (bucketIndex_ < set_->buckets_.size() &&
|
|
|
+ !set_->HasEnumAt(bucketIndex_, bucketOffset_));
|
|
|
+ return *this;
|
|
|
}
|
|
|
- return *this;
|
|
|
- }
|
|
|
|
|
|
- friend bool operator==(const EnumSet& a, const EnumSet& b) {
|
|
|
- if (a.mask_ != b.mask_) {
|
|
|
- return false;
|
|
|
+ Iterator operator++(int) {
|
|
|
+ Iterator old = *this;
|
|
|
+ operator++();
|
|
|
+ return old;
|
|
|
}
|
|
|
|
|
|
- if (a.overflow_ == nullptr && b.overflow_ == nullptr) {
|
|
|
- return true;
|
|
|
+ T operator*() const {
|
|
|
+ assert(set_->HasEnumAt(bucketIndex_, bucketOffset_) &&
|
|
|
+ "operator*() called on an invalid iterator.");
|
|
|
+ return GetValueFromBucket(set_->buckets_[bucketIndex_], bucketOffset_);
|
|
|
}
|
|
|
|
|
|
- if (a.overflow_ == nullptr || b.overflow_ == nullptr) {
|
|
|
- return false;
|
|
|
+ bool operator!=(const Iterator& other) const {
|
|
|
+ return set_ != other.set_ || bucketOffset_ != other.bucketOffset_ ||
|
|
|
+ bucketIndex_ != other.bucketIndex_;
|
|
|
}
|
|
|
|
|
|
- return *a.overflow_ == *b.overflow_;
|
|
|
+ bool operator==(const Iterator& other) const {
|
|
|
+ return !(operator!=(other));
|
|
|
+ }
|
|
|
+
|
|
|
+ Iterator& operator=(const Iterator& other) {
|
|
|
+ set_ = other.set_;
|
|
|
+ bucketIndex_ = other.bucketIndex_;
|
|
|
+ bucketOffset_ = other.bucketOffset_;
|
|
|
+ return *this;
|
|
|
+ }
|
|
|
+
|
|
|
+ private:
|
|
|
+ Iterator(const EnumSet* set, size_t bucketIndex, ElementType bucketOffset)
|
|
|
+ : set_(set), bucketIndex_(bucketIndex), bucketOffset_(bucketOffset) {}
|
|
|
+
|
|
|
+ private:
|
|
|
+ const EnumSet* set_ = nullptr;
|
|
|
+ // Index of the bucket in the vector.
|
|
|
+ size_t bucketIndex_ = 0;
|
|
|
+ // Offset in bits in the current bucket.
|
|
|
+ ElementType bucketOffset_ = 0;
|
|
|
+
|
|
|
+ friend class EnumSet;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Required to allow the use of std::inserter.
|
|
|
+ using value_type = T;
|
|
|
+ using const_iterator = Iterator;
|
|
|
+ using iterator = Iterator;
|
|
|
+
|
|
|
+ public:
|
|
|
+ iterator cbegin() const noexcept {
|
|
|
+ auto it = iterator(this, /* bucketIndex= */ 0, /* bucketOffset= */ 0);
|
|
|
+ if (buckets_.size() == 0) {
|
|
|
+ return it;
|
|
|
+ }
|
|
|
+
|
|
|
+ // The iterator has the logic to find the next valid bit. If the value 0
|
|
|
+ // is not stored, use it to find the next valid bit.
|
|
|
+ if (!HasEnumAt(it.bucketIndex_, it.bucketOffset_)) {
|
|
|
+ ++it;
|
|
|
+ }
|
|
|
+
|
|
|
+ return it;
|
|
|
}
|
|
|
|
|
|
- friend bool operator!=(const EnumSet& a, const EnumSet& b) {
|
|
|
- return !(a == b);
|
|
|
+ iterator begin() const noexcept { return cbegin(); }
|
|
|
+
|
|
|
+ iterator cend() const noexcept {
|
|
|
+ return iterator(this, buckets_.size(), /* bucketOffset= */ 0);
|
|
|
}
|
|
|
|
|
|
- // Adds the given enum value to the set. This has no effect if the
|
|
|
- // enum value is already in the set.
|
|
|
- void Add(EnumType c) { AddWord(ToWord(c)); }
|
|
|
+ iterator end() const noexcept { return cend(); }
|
|
|
|
|
|
- // Removes the given enum value from the set. This has no effect if the
|
|
|
- // enum value is not in the set.
|
|
|
- void Remove(EnumType c) { RemoveWord(ToWord(c)); }
|
|
|
+ // Creates an empty set.
|
|
|
+ EnumSet() : buckets_(0), size_(0) {}
|
|
|
|
|
|
- // Returns true if this enum value is in the set.
|
|
|
- bool Contains(EnumType c) const { return ContainsWord(ToWord(c)); }
|
|
|
+ // Creates a set and store `value` in it.
|
|
|
+ EnumSet(T value) : EnumSet() { insert(value); }
|
|
|
|
|
|
- // Applies f to each enum in the set, in order from smallest enum
|
|
|
- // value to largest.
|
|
|
- void ForEach(std::function<void(EnumType)> f) const {
|
|
|
- for (uint32_t i = 0; i < 64; ++i) {
|
|
|
- if (mask_ & AsMask(i)) f(static_cast<EnumType>(i));
|
|
|
- }
|
|
|
- if (overflow_) {
|
|
|
- for (uint32_t c : *overflow_) f(static_cast<EnumType>(c));
|
|
|
+ // Creates a set and stores each `values` in it.
|
|
|
+ EnumSet(std::initializer_list<T> values) : EnumSet() {
|
|
|
+ for (auto item : values) {
|
|
|
+ insert(item);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Returns true if the set is empty.
|
|
|
- bool IsEmpty() const {
|
|
|
- if (mask_) return false;
|
|
|
- if (overflow_ && !overflow_->empty()) return false;
|
|
|
- return true;
|
|
|
+ // Creates a set, and insert `count` enum values pointed by `array` in it.
|
|
|
+ EnumSet(ElementType count, const T* array) : EnumSet() {
|
|
|
+ for (ElementType i = 0; i < count; i++) {
|
|
|
+ insert(array[i]);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // Returns true if the set contains ANY of the elements of |in_set|,
|
|
|
- // or if |in_set| is empty.
|
|
|
- bool HasAnyOf(const EnumSet<EnumType>& in_set) const {
|
|
|
- if (in_set.IsEmpty()) return true;
|
|
|
+ // Copies the EnumSet `other` into a new EnumSet.
|
|
|
+ EnumSet(const EnumSet& other)
|
|
|
+ : buckets_(other.buckets_), size_(other.size_) {}
|
|
|
|
|
|
- if (mask_ & in_set.mask_) return true;
|
|
|
+ // Moves the EnumSet `other` into a new EnumSet.
|
|
|
+ EnumSet(EnumSet&& other)
|
|
|
+ : buckets_(std::move(other.buckets_)), size_(other.size_) {}
|
|
|
|
|
|
- if (!overflow_ || !in_set.overflow_) return false;
|
|
|
+ // Deep-copies the EnumSet `other` into this EnumSet.
|
|
|
+ EnumSet& operator=(const EnumSet& other) {
|
|
|
+ buckets_ = other.buckets_;
|
|
|
+ size_ = other.size_;
|
|
|
+ return *this;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Matches std::unordered_set::insert behavior.
|
|
|
+ std::pair<iterator, bool> insert(const T& value) {
|
|
|
+ const size_t index = FindBucketForValue(value);
|
|
|
+ const ElementType offset = ComputeBucketOffset(value);
|
|
|
|
|
|
- for (uint32_t item : *in_set.overflow_) {
|
|
|
- if (overflow_->find(item) != overflow_->end()) return true;
|
|
|
+ if (index >= buckets_.size() ||
|
|
|
+ buckets_[index].start != ComputeBucketStart(value)) {
|
|
|
+ size_ += 1;
|
|
|
+ InsertBucketFor(index, value);
|
|
|
+ return std::make_pair(Iterator(this, index, offset), true);
|
|
|
}
|
|
|
|
|
|
- return false;
|
|
|
+ auto& bucket = buckets_[index];
|
|
|
+ const auto mask = ComputeMaskForValue(value);
|
|
|
+ if (bucket.data & mask) {
|
|
|
+ return std::make_pair(Iterator(this, index, offset), false);
|
|
|
+ }
|
|
|
+
|
|
|
+ size_ += 1;
|
|
|
+ bucket.data |= ComputeMaskForValue(value);
|
|
|
+ return std::make_pair(Iterator(this, index, offset), true);
|
|
|
}
|
|
|
|
|
|
- private:
|
|
|
- // Adds the given enum value (as a 32-bit word) to the set. This has no
|
|
|
- // effect if the enum value is already in the set.
|
|
|
- void AddWord(uint32_t word) {
|
|
|
- if (auto new_bits = AsMask(word)) {
|
|
|
- mask_ |= new_bits;
|
|
|
- } else {
|
|
|
- Overflow().insert(word);
|
|
|
+ // Inserts `value` in the set if possible.
|
|
|
+ // Similar to `std::unordered_set::insert`, except the hint is ignored.
|
|
|
+ // Returns an iterator to the inserted element, or the element preventing
|
|
|
+ // insertion.
|
|
|
+ iterator insert(const_iterator, const T& value) {
|
|
|
+ return insert(value).first;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Inserts `value` in the set if possible.
|
|
|
+ // Similar to `std::unordered_set::insert`, except the hint is ignored.
|
|
|
+ // Returns an iterator to the inserted element, or the element preventing
|
|
|
+ // insertion.
|
|
|
+ iterator insert(const_iterator, T&& value) { return insert(value).first; }
|
|
|
+
|
|
|
+ // Removes the value `value` into the set.
|
|
|
+ // Similar to `std::unordered_set::erase`.
|
|
|
+ // Returns the number of erased elements.
|
|
|
+ size_t erase(const T& value) {
|
|
|
+ const size_t index = FindBucketForValue(value);
|
|
|
+ if (index >= buckets_.size() ||
|
|
|
+ buckets_[index].start != ComputeBucketStart(value)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto& bucket = buckets_[index];
|
|
|
+ const auto mask = ComputeMaskForValue(value);
|
|
|
+ if (!(bucket.data & mask)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ size_ -= 1;
|
|
|
+ bucket.data &= ~mask;
|
|
|
+ if (bucket.data == 0) {
|
|
|
+ buckets_.erase(buckets_.cbegin() + index);
|
|
|
}
|
|
|
+ return 1;
|
|
|
}
|
|
|
|
|
|
- // Removes the given enum value (as a 32-bit word) from the set. This has no
|
|
|
- // effect if the enum value is not in the set.
|
|
|
- void RemoveWord(uint32_t word) {
|
|
|
- if (auto new_bits = AsMask(word)) {
|
|
|
- mask_ &= ~new_bits;
|
|
|
- } else {
|
|
|
- auto itr = Overflow().find(word);
|
|
|
- if (itr != Overflow().end()) Overflow().erase(itr);
|
|
|
+ // Returns true if `value` is present in the set.
|
|
|
+ bool contains(T value) const {
|
|
|
+ const size_t index = FindBucketForValue(value);
|
|
|
+ if (index >= buckets_.size() ||
|
|
|
+ buckets_[index].start != ComputeBucketStart(value)) {
|
|
|
+ return false;
|
|
|
}
|
|
|
+ auto& bucket = buckets_[index];
|
|
|
+ return bucket.data & ComputeMaskForValue(value);
|
|
|
}
|
|
|
|
|
|
- // Returns true if the enum represented as a 32-bit word is in the set.
|
|
|
- bool ContainsWord(uint32_t word) const {
|
|
|
- // We shouldn't call Overflow() since this is a const method.
|
|
|
- if (auto bits = AsMask(word)) {
|
|
|
- return (mask_ & bits) != 0;
|
|
|
- } else if (auto overflow = overflow_.get()) {
|
|
|
- return overflow->find(word) != overflow->end();
|
|
|
+ // Returns the 1 if `value` is present in the set, `0` otherwise.
|
|
|
+ inline size_t count(T value) const { return contains(value) ? 1 : 0; }
|
|
|
+
|
|
|
+ // Returns true if the set is holds no values.
|
|
|
+ inline bool empty() const { return size_ == 0; }
|
|
|
+
|
|
|
+ // Returns the number of enums stored in this set.
|
|
|
+ size_t size() const { return size_; }
|
|
|
+
|
|
|
+ // Returns true if this set contains at least one value contained in `in_set`.
|
|
|
+ // Note: If `in_set` is empty, this function returns true.
|
|
|
+ bool HasAnyOf(const EnumSet<T>& in_set) const {
|
|
|
+ if (in_set.empty()) {
|
|
|
+ return true;
|
|
|
}
|
|
|
- // The word is large, but the set doesn't have large members, so
|
|
|
- // it doesn't have an overflow set.
|
|
|
+
|
|
|
+ auto lhs = buckets_.cbegin();
|
|
|
+ auto rhs = in_set.buckets_.cbegin();
|
|
|
+
|
|
|
+ while (lhs != buckets_.cend() && rhs != in_set.buckets_.cend()) {
|
|
|
+ if (lhs->start == rhs->start) {
|
|
|
+ if (lhs->data & rhs->data) {
|
|
|
+ // At least 1 bit is shared. Early return.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ lhs++;
|
|
|
+ rhs++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // LHS bucket is smaller than the current RHS bucket. Catching up on RHS.
|
|
|
+ if (lhs->start < rhs->start) {
|
|
|
+ lhs++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Otherwise, RHS needs to catch up on LHS.
|
|
|
+ rhs++;
|
|
|
+ }
|
|
|
+
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- // Returns the enum value as a uint32_t.
|
|
|
- uint32_t ToWord(EnumType value) const {
|
|
|
- static_assert(sizeof(EnumType) <= sizeof(uint32_t),
|
|
|
- "EnumType must statically castable to uint32_t");
|
|
|
- return static_cast<uint32_t>(value);
|
|
|
+ private:
|
|
|
+ // Returns the index of the last bucket in which `value` could be stored.
|
|
|
+ static constexpr inline size_t ComputeLargestPossibleBucketIndexFor(T value) {
|
|
|
+ return static_cast<size_t>(value) / kBucketSize;
|
|
|
}
|
|
|
|
|
|
- // Determines whether the given enum value can be represented
|
|
|
- // as a bit in a uint64_t mask. If so, then returns that mask bit.
|
|
|
- // Otherwise, returns 0.
|
|
|
- uint64_t AsMask(uint32_t word) const {
|
|
|
- if (word > 63) return 0;
|
|
|
- return uint64_t(1) << word;
|
|
|
+ // Returns the smallest enum value that could be contained in the same bucket
|
|
|
+ // as `value`.
|
|
|
+ static constexpr inline T ComputeBucketStart(T value) {
|
|
|
+ return static_cast<T>(kBucketSize *
|
|
|
+ ComputeLargestPossibleBucketIndexFor(value));
|
|
|
}
|
|
|
|
|
|
- // Ensures that overflow_set_ references a set. A new empty set is
|
|
|
- // allocated if one doesn't exist yet. Returns overflow_set_.
|
|
|
- OverflowSetType& Overflow() {
|
|
|
- if (overflow_.get() == nullptr) {
|
|
|
- overflow_ = MakeUnique<OverflowSetType>();
|
|
|
+ // Returns the index of the bit that corresponds to `value` in the bucket.
|
|
|
+ static constexpr inline ElementType ComputeBucketOffset(T value) {
|
|
|
+ return static_cast<ElementType>(value) % kBucketSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns the bitmask used to represent the enum `value` in its bucket.
|
|
|
+ static constexpr inline BucketType ComputeMaskForValue(T value) {
|
|
|
+ return 1ULL << ComputeBucketOffset(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns the `enum` stored in `bucket` at `offset`.
|
|
|
+ // `offset` is the bit-offset in the bucket storage.
|
|
|
+ static constexpr inline T GetValueFromBucket(const Bucket& bucket,
|
|
|
+ BucketType offset) {
|
|
|
+ return static_cast<T>(static_cast<ElementType>(bucket.start) + offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ // For a given enum `value`, finds the bucket index that could contain this
|
|
|
+ // value. If no such bucket is found, the index at which the new bucket should
|
|
|
+ // be inserted is returned.
|
|
|
+ size_t FindBucketForValue(T value) const {
|
|
|
+ // Set is empty, insert at 0.
|
|
|
+ if (buckets_.size() == 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ const T wanted_start = ComputeBucketStart(value);
|
|
|
+ assert(buckets_.size() > 0 &&
|
|
|
+ "Size must not be 0 here. Has the code above changed?");
|
|
|
+ size_t index = std::min(buckets_.size() - 1,
|
|
|
+ ComputeLargestPossibleBucketIndexFor(value));
|
|
|
+
|
|
|
+ // This loops behaves like std::upper_bound with a reverse iterator.
|
|
|
+ // Buckets are sorted. 3 main cases:
|
|
|
+ // - The bucket matches
|
|
|
+ // => returns the bucket index.
|
|
|
+ // - The found bucket is larger
|
|
|
+ // => scans left until it finds the correct bucket, or insertion point.
|
|
|
+ // - The found bucket is smaller
|
|
|
+ // => We are at the end, so we return past-end index for insertion.
|
|
|
+ for (; buckets_[index].start >= wanted_start; index--) {
|
|
|
+ if (index == 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return index + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Creates a new bucket to store `value` and inserts it at `index`.
|
|
|
+ // If the `index` is past the end, the bucket is inserted at the end of the
|
|
|
+ // vector.
|
|
|
+ void InsertBucketFor(size_t index, T value) {
|
|
|
+ const T bucket_start = ComputeBucketStart(value);
|
|
|
+ Bucket bucket = {1ULL << ComputeBucketOffset(value), bucket_start};
|
|
|
+ auto it = buckets_.emplace(buckets_.begin() + index, std::move(bucket));
|
|
|
+#if defined(NDEBUG)
|
|
|
+ (void)it; // Silencing unused variable warning.
|
|
|
+#else
|
|
|
+ assert(std::next(it) == buckets_.end() ||
|
|
|
+ std::next(it)->start > bucket_start);
|
|
|
+ assert(it == buckets_.begin() || std::prev(it)->start < bucket_start);
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns true if the bucket at `bucketIndex/ stores the enum at
|
|
|
+ // `bucketOffset`, false otherwise.
|
|
|
+ bool HasEnumAt(size_t bucketIndex, BucketType bucketOffset) const {
|
|
|
+ assert(bucketIndex < buckets_.size());
|
|
|
+ assert(bucketOffset < kBucketSize);
|
|
|
+ return buckets_[bucketIndex].data & (1ULL << bucketOffset);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns true if `lhs` and `rhs` hold the exact same values.
|
|
|
+ friend bool operator==(const EnumSet& lhs, const EnumSet& rhs) {
|
|
|
+ if (lhs.size_ != rhs.size_) {
|
|
|
+ return false;
|
|
|
}
|
|
|
- return *overflow_;
|
|
|
+
|
|
|
+ if (lhs.buckets_.size() != rhs.buckets_.size()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return lhs.buckets_ == rhs.buckets_;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns true if `lhs` and `rhs` hold at least 1 different value.
|
|
|
+ friend bool operator!=(const EnumSet& lhs, const EnumSet& rhs) {
|
|
|
+ return !(lhs == rhs);
|
|
|
}
|
|
|
|
|
|
- // Enums with values up to 63 are stored as bits in this mask.
|
|
|
- uint64_t mask_ = 0;
|
|
|
- // Enums with values larger than 63 are stored in this set.
|
|
|
- // This set should normally be empty or very small.
|
|
|
- std::unique_ptr<OverflowSetType> overflow_ = {};
|
|
|
+ // Storage for the buckets.
|
|
|
+ std::vector<Bucket> buckets_;
|
|
|
+ // How many enums is this set storing.
|
|
|
+ size_t size_ = 0;
|
|
|
};
|
|
|
|
|
|
-// A set of spv::Capability, optimized for small capability values.
|
|
|
+// A set of spv::Capability.
|
|
|
using CapabilitySet = EnumSet<spv::Capability>;
|
|
|
|
|
|
} // namespace spvtools
|