blob: ab1550ecfb5a4bea68fb6df88d93758e9bbe7c3d [file] [log] [blame]
// 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_