| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SPARSE_VECTOR_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SPARSE_VECTOR_H_ |
| |
| #include <limits> |
| #include <type_traits> |
| |
| #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" |
| |
| namespace blink { |
| |
| namespace internal { |
| |
| template <typename VectorType> |
| class NonTraceableSparseVectorFields { |
| DISALLOW_NEW(); |
| |
| protected: |
| VectorType fields_; |
| }; |
| |
| template <typename VectorType> |
| class TraceableSparseVectorFields { |
| DISALLOW_NEW(); |
| |
| public: |
| void Trace(Visitor* visitor) const { visitor->Trace(fields_); } |
| |
| protected: |
| VectorType fields_; |
| }; |
| |
| } // namespace internal |
| |
| // This class is logically like an array of FieldType instances, indexed by the |
| // FieldId enum, but is optimized to save memory when only a small number of |
| // the possible fields are being used. |
| // |
| // This class is implemented using a vector containing the FieldType instances |
| // that are being used, and a bitfield to indicate which fields are being used. |
| // The popcount cpu instruction is critical for the O(1) lookup performance, as |
| // and allows us to determine the vector index of a field using the bitfield. |
| // For example: |
| // enum class FieldId { |
| // kFirst = 0, ... kMiddle = 7, ... kLast = 15, kNumFields = kLast + 1, |
| // } |
| // SparseVector<FieldId, int> sparse_vector; |
| // A sparse vector with kFirst, kMiddle, and kLast populated will have: |
| // fields_bitfield_: 0b00000000000000001000000100000001 |
| // Vector: [int for kFirst, int for kMiddle, int for kLast] |
| // |
| // Template parameters: |
| // FieldId: must be an enum type (enforced below) and have a kNumFields entry |
| // with value equal to 1 + maximum value in the enum. |
| // FieldType: the intention is that it can be an arbitrary type, typically |
| // either an class or a managed pointer to a class. |
| // inline_capacity (optional): size of the inline buffer. |
| // VectorType (optional): The type of the internal vector. |
| // BitfieldType (optional): The type of the internal bitfield. Must be big |
| // enough to contain FieldId::kNumFields bits. |
| // |
| // Time complexity: |
| // Query: O(1) |
| // Modify existing field: O(1) |
| // Add/erase: O(1) (at the end of the vector) to O(N) (N = number of |
| // existing fields) |
| // It's efficient when the number of existing fields is much smaller than |
| // FieldId::kNumFields, add/erase operations mostly happen at the end of the |
| // vector, and the set of existing fields seldom changes. |
| template <typename FieldId, |
| typename FieldType, |
| wtf_size_t inline_capacity = 0, |
| typename VectorType = std::conditional_t< |
| IsMemberType<FieldType>::value || IsTraceableV<FieldType>, |
| HeapVector<FieldType, inline_capacity>, |
| Vector<FieldType, inline_capacity>>, |
| typename BitfieldType = |
| std::conditional_t<static_cast<size_t>(FieldId::kNumFields) <= |
| std::numeric_limits<uint32_t>::digits, |
| uint32_t, |
| uint64_t>> |
| class SparseVector : |
| // The conditional inheritance approach prevents types like |
| // SparseVector<Enum, int> from being treated as traceable by the blinkgc |
| // clang plugin. `requires` clause on the `Trace` method doesn't work. |
| public std::conditional_t< |
| IsTraceableV<VectorType>, |
| internal::TraceableSparseVectorFields<VectorType>, |
| internal::NonTraceableSparseVectorFields<VectorType>> { |
| static_assert(std::is_enum_v<FieldId>); |
| static_assert(std::is_unsigned_v<BitfieldType>); |
| static constexpr wtf_size_t kMaxSize = |
| static_cast<wtf_size_t>(FieldId::kNumFields); |
| static_assert(std::numeric_limits<BitfieldType>::digits >= kMaxSize, |
| "BitfieldType must be big enough to have a bit for each " |
| "field in FieldId."); |
| static_assert(inline_capacity <= kMaxSize); |
| |
| public: |
| wtf_size_t capacity() const { return this->fields_.capacity(); } |
| wtf_size_t size() const { return this->fields_.size(); } |
| bool empty() const { return this->fields_.empty(); } |
| |
| void reserve(wtf_size_t capacity) { |
| CHECK_LE(capacity, kMaxSize); |
| this->fields_.reserve(capacity); |
| } |
| |
| void clear() { |
| this->fields_.clear(); |
| fields_bitfield_ = 0; |
| } |
| |
| // Returns whether the field exists. Time complexity is O(1). |
| bool HasField(FieldId field_id) const { |
| return fields_bitfield_ & FieldIdMask(field_id); |
| } |
| |
| // Returns whether any field between `first` and `last` (both inclusive) |
| // exists. Time complexity is O(1). |
| bool HasFieldInRange(FieldId first, FieldId last) const { |
| return fields_bitfield_ & RangeMask(first, last); |
| } |
| |
| // Returns the value of existing field. Time complexity is O(1). |
| const FieldType& GetField(FieldId field_id) const { |
| DCHECK(HasField(field_id)); |
| return this->fields_[GetFieldIndex(field_id)]; |
| } |
| FieldType& GetField(FieldId field_id) { |
| DCHECK(HasField(field_id)); |
| return this->fields_[GetFieldIndex(field_id)]; |
| } |
| |
| // Adds a field if it's not existing (time complexity is O(1) (if the new |
| // field is at the end) to O(N) (N = number of existing fields)), or modifies |
| // the existing field (time complexity is O(1)). |
| void SetField(FieldId field_id, FieldType&& field) { |
| if (HasField(field_id)) { |
| this->fields_[GetFieldIndex(field_id)] = std::forward<FieldType>(field); |
| } else { |
| fields_bitfield_ = fields_bitfield_ | FieldIdMask(field_id); |
| this->fields_.insert(GetFieldIndex(field_id), |
| std::forward<FieldType>(field)); |
| } |
| } |
| |
| // Erases a field. Returns `true` if the field was previously existing and |
| // is actually erased. Time complexity is O(1) (if the field doesn't exist |
| // or the erased field is at the end) to O(N) (N = number of existing fields). |
| bool EraseField(FieldId field_id) { |
| if (HasField(field_id)) { |
| this->fields_.EraseAt(GetFieldIndex(field_id)); |
| fields_bitfield_ = fields_bitfield_ & ~FieldIdMask(field_id); |
| return true; |
| } |
| return false; |
| } |
| |
| private: |
| static BitfieldType FieldIdMask(FieldId field_id) { |
| CHECK_LT(static_cast<wtf_size_t>(field_id), kMaxSize); |
| return static_cast<BitfieldType>(1) << static_cast<wtf_size_t>(field_id); |
| } |
| |
| // Returns a mask that has entries for all field IDs lower than `upper`. |
| static BitfieldType LowerFieldIdsMask(FieldId upper) { |
| return FieldIdMask(upper) - 1; |
| } |
| |
| static BitfieldType RangeMask(FieldId first, FieldId last) { |
| return (FieldIdMask(last) | LowerFieldIdsMask(last)) & |
| ~LowerFieldIdsMask(first); |
| } |
| |
| // Returns the index in `fields_` that `field_id` is stored in. If `fields_` |
| // isn't storing a field for `field_id`, then this returns the index which |
| // the data for `field_id` should be inserted into. |
| wtf_size_t GetFieldIndex(FieldId field_id) const { |
| // Then count the total population of field IDs lower than that one we |
| // are looking for. The target field ID should be located at the index of |
| // of the total population. |
| return std::popcount(fields_bitfield_ & LowerFieldIdsMask(field_id)); |
| } |
| |
| BitfieldType fields_bitfield_ = 0; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SPARSE_VECTOR_H_ |