blob: e693b30fe6f5a540c8f13b052d199edc0101a1da [file] [edit]
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <numeric>
#include <wtf/CommaPrinter.h>
#include <wtf/DataLog.h>
#include <wtf/FastMalloc.h>
#include <wtf/MathExtras.h>
#include <wtf/Range.h>
#include <wtf/Vector.h>
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
namespace WTF {
// IntervalSet: Stores a set of Range<T> to Value. Optimized with the following assumptions:
// - hasOverlap() is the most frequent operation.
// - find() is the next most frequent operation.
// - insert() is much less frequent.
// - erase() is the least frequent operation.
//
// Implemented as a cache-line-aware B+ tree specialized for storing Range<T> keys.
template<typename T, typename Value, size_t cacheLinesPerNode = 1>
requires std::is_trivially_destructible_v<T> && std::is_trivially_destructible_v<Value>
class IntervalSet {
WTF_MAKE_NONCOPYABLE(IntervalSet);
public:
using Interval = Range<T>;
static constexpr size_t cpuCacheLineSize = 64;
static constexpr size_t targetNodeSize = cacheLinesPerNode * cpuCacheLineSize;
// Calculate optimal order for each node type based on target cache line usage
static constexpr size_t calculateLeafOrder()
{
constexpr size_t sizePerOrder = sizeof(Interval) + sizeof(Value);
return targetNodeSize / sizePerOrder;
}
static constexpr size_t calculateInnerOrder()
{
constexpr size_t sizePerOrder = sizeof(Interval) + sizeof(uintptr_t);
return targetNodeSize / sizePerOrder;
}
static constexpr size_t leafOrder = calculateLeafOrder();
static constexpr size_t innerOrder = calculateInnerOrder();
// Ensure cacheLinesPerNode parameter is large enough for valid B+ tree orders
static_assert(leafOrder >= 2, "cacheLinesPerNode parameter too small: LeafNode order must be at least 2 for a valid B+ tree");
static_assert(innerOrder >= 2, "cacheLinesPerNode parameter too small: InnerNode order must be at least 2 for a valid B+ tree");
IntervalSet() = default;
IntervalSet(IntervalSet&& other)
: m_root(other.m_root)
, m_rootInterval(other.m_rootInterval)
, m_height(other.m_height)
#if ASSERT_ENABLED
, assertOnlyNumNodes(other.assertOnlyNumNodes)
#endif
{
other.m_root = { };
other.m_rootInterval = { };
other.m_height = 0;
#if ASSERT_ENABLED
other.assertOnlyNumNodes = 0;
#endif
}
IntervalSet& operator=(IntervalSet&& other)
{
if (this != &other) {
freeAllNodes();
m_root = other.m_root;
m_rootInterval = other.m_rootInterval;
m_height = other.m_height;
#if ASSERT_ENABLED
assertOnlyNumNodes = other.assertOnlyNumNodes;
#endif
other.m_root = { };
other.m_rootInterval = { };
other.m_height = 0;
#if ASSERT_ENABLED
other.assertOnlyNumNodes = 0;
#endif
}
return *this;
}
~IntervalSet()
{
freeAllNodes();
ASSERT(!assertOnlyNumNodes);
}
bool isEmpty() const { return !m_rootInterval; }
// Insert an interval-value pair. The interval must not overlap with an existing interval.
// Invalidates all iterators.
void insert(const Interval& interval, const Value& value)
{
if (!m_root) [[unlikely]] {
LeafNode* leaf = allocNode<LeafNode>();
m_root = NodeRef(leaf, 0);
m_height = 0;
}
Path path;
NodeRef* nodeRef = &m_root;
// Descend down the tree, recording the path taken.
for (unsigned depth = 0; depth < m_height; depth++) {
InnerNode* inner = nodeRef->asInner();
size_t index = inner->subtreeForInsert(nodeRef->size(), interval.end());
path.append({ nodeRef, index });
nodeRef = &inner->child(index);
}
// Found the correct leaf for the insert, now determine the index within that leaf.
size_t insertionIndex = nodeRef->asLeaf()->firstIntervalEndAfter(nodeRef->size(), interval.end());
path.append({ nodeRef, insertionIndex });
ASSERT(path.size() == m_height + 1);
auto [newNode, newNodeCoverage] = insertInNodeSplitIfNeeded<LeafNode>(path, m_height, interval, value);
// Ascend back up along the same path, inserting any new children and splitting inner nodes as needed.
for (int depth = m_height - 1; depth >= 0; depth--) {
if (!newNode) [[likely]]
return;
PathEntry& entry = path[depth];
ASSERT(entry.nodeRef->asInner()->child(entry.index).size() + newNode.size() == (static_cast<unsigned>(depth + 1) == m_height ? leafOrder : innerOrder) + 1);
ASSERT(newNodeCoverage);
entry.index++; // Insert new parent immediately after the existing parent
std::tie(newNode, newNodeCoverage) = insertInNodeSplitIfNeeded<InnerNode>(path, depth, newNodeCoverage, newNode);
}
// If there's a new node at depth 0 then a new level is required.
if (newNode) [[unlikely]] {
ASSERT(m_root.size() + newNode.size() == (m_height ? innerOrder : leafOrder) + 1);
// Need to add another level to the tree.
InnerNode* newRoot = allocNode<InnerNode>();
newRoot->interval(0) = m_rootInterval;
newRoot->child(0) = m_root;
newRoot->interval(1) = newNodeCoverage;
newRoot->child(1) = newNode;
m_height++;
m_root = NodeRef(newRoot, 2);
m_rootInterval = newRoot->coverage(2);
}
}
// Remove the given interval from the IntervalSet. The interval must be present.
// Invalidates all iterators.
void erase(const Interval& interval)
{
Path path;
ASSERT(interval.overlaps(m_rootInterval));
ASSERT(m_root);
NodeRef* nodeRef = &m_root;
for (unsigned depth = 0; depth < m_height; ++depth) {
InnerNode* inner = nodeRef->asInner();
size_t index = inner->firstIntervalEndAfter(nodeRef->size(), interval.begin());
ASSERT(index < nodeRef->size());
ASSERT(inner->interval(index).begin() < interval.end());
path.append({ nodeRef, index });
nodeRef = &inner->child(index);
}
LeafNode* leaf = nodeRef->asLeaf();
size_t eraseIndex = leaf->firstIntervalEndAfter(nodeRef->size(), interval.begin());
ASSERT(leaf->interval(eraseIndex).begin() == interval.begin() && leaf->interval(eraseIndex).end() == interval.end());
path.append({ nodeRef, eraseIndex });
bool removedNode = eraseFromNode<LeafNode>(path, m_height);
// Ascend removing references to any child that was removed, which may in turn cause the parent to become empty.
for (int depth = m_height - 1; depth >= 0; depth--) {
if (!removedNode) [[likely]]
return;
removedNode = eraseFromNode<InnerNode>(path, depth);
}
// If removedNode was true at every depth, the tree is now empty.
if (removedNode) [[unlikely]] {
ASSERT(!assertOnlyNumNodes);
ASSERT(!m_root);
m_rootInterval = Interval();
m_height = 0;
}
}
// Returns the Interval and Value for the first interval that overlaps with the query interval,
// if an overlapping interval exists. Otherwise, returns std::nullopt.
std::optional<std::pair<Interval, Value>> find(const Interval& query) const
{
if (!query.overlaps(m_rootInterval))
return std::nullopt;
ASSERT(m_root);
NodeRef nodeRef = m_root;
for (unsigned depth = 0; depth < m_height; ++depth) {
InnerNode* inner = nodeRef.asInner();
size_t index = inner->firstIntervalEndAfter(nodeRef.size(), query.begin());
if (index == nodeRef.size())
return std::nullopt; // query is entirely after this subtree
if (query.end() <= inner->interval(index).begin())
return std::nullopt; // query is entirely before this subtree
nodeRef = inner->child(index);
}
LeafNode* leaf = nodeRef.asLeaf();
size_t index = leaf->firstIntervalEndAfter(nodeRef.size(), query.begin());
ASSERT(index < nodeRef.size()); // coverage check at parent level ensures this
ASSERT(query.begin() < leaf->interval(index).end());
if (query.end() <= leaf->interval(index).begin())
return std::nullopt;
return std::make_pair(leaf->interval(index), leaf->value(index));
}
// Returns true iff an interval in the set overlaps with the query interval. Similar to find() but
// can sometimes terminate before descending the full depth since the Interval-Value result is not needed.
bool hasOverlap(const Interval& query) const
{
if (!query.overlaps(m_rootInterval))
return false;
ASSERT(m_root);
NodeRef nodeRef = m_root;
for (unsigned depth = 0; depth < m_height; ++depth) {
InnerNode* inner = nodeRef.asInner();
size_t index = inner->firstIntervalEndAfter(nodeRef.size(), query.begin());
if (index == nodeRef.size())
return false; // query starts after all intervals
// query start lands either within the subtree or the gap immediately preceding that subtree
ASSERT(query.begin() < inner->interval(index).end());
if (query.end() <= inner->interval(index).begin())
return false; // query is entirely in the gap before this subtree
if (inner->interval(index).end() <= query.end())
return true; // query spans subtree end point so it must overlap the last interval
if (query.begin() <= inner->interval(index).begin())
return true; // query spans subtree start point so it must overlap the first interval
// Otherwise, subtree encompasses query so need to search subtree
ASSERT(inner->interval(index).begin() < query.begin() && query.end() < inner->interval(index).end());
nodeRef = inner->child(index);
}
LeafNode* leaf = nodeRef.asLeaf();
size_t index = leaf->firstIntervalEndAfter(nodeRef.size(), query.begin());
ASSERT(query.begin() < leaf->interval(index).end());
return leaf->interval(index).begin() < query.end();
}
void dump(PrintStream& out) const
{
out.print("IntervalSet(height=", m_height, ", leafOrder=", leafOrder, ", innerOrder=", innerOrder, ")");
if (!m_root) {
out.print(" <empty>");
return;
}
out.println(" coverage=", m_rootInterval);
dumpSubtree(out, m_root, m_height, 0);
}
// Height indicates the number of edges to reach the leaf level in a non-empty tree.
unsigned height() const { return m_height; }
private:
struct LeafNode;
struct InnerNode;
// Common base class for all nodes - provides type identity for NodeRef
struct Node { };
template<typename Payload, size_t order>
struct NodeImpl : public Node {
using PayloadType = Payload;
static constexpr size_t capacity = order;
Interval& interval(size_t index)
{
ASSERT(index < capacity);
return intervals[index];
}
const Interval coverage(size_t size) const
{
RELEASE_ASSERT(size);
return { intervals[0].begin(), intervals[size - 1].end() };
}
// Transfer count intervals and values from the rightNode to this node, where the rightNode
// is the immediate right cousin of this.
void shiftLeftFrom(size_t& size, NodeImpl* rightNode, size_t& rightSize, size_t count)
{
ASSERT(size + count <= capacity);
ASSERT(count <= rightSize);
for (size_t i = 0; i < count; i++) {
intervals[i + size] = rightNode->intervals[i];
payloads[i + size] = rightNode->payloads[i];
}
for (size_t i = 0; i < rightSize - count; i++) {
rightNode->intervals[i] = rightNode->intervals[i + count];
rightNode->payloads[i] = rightNode->payloads[i + count];
}
size += count;
rightSize -= count;
}
// Transfer count intervals and values from this node to the rightNode, where the rightNode
// is the immediate right cousin of this.
void shiftRightTo(size_t& size, NodeImpl* rightNode, size_t& rightSize, size_t count)
{
ASSERT(rightSize + count <= capacity);
ASSERT(count <= size);
for (size_t i = rightSize + count - 1; i >= count; i--) {
rightNode->intervals[i] = rightNode->intervals[i - count];
rightNode->payloads[i] = rightNode->payloads[i - count];
}
for (size_t i = 0; i < count; i++) {
rightNode->intervals[i] = intervals[size - count + i];
rightNode->payloads[i] = payloads[size - count + i];
}
size -= count;
rightSize += count;
}
void insertAt(size_t& size, size_t index, const Interval& interval, const Payload& value)
{
ASSERT(size < capacity);
ASSERT(index <= size);
for (size_t i = size; i > index; --i) {
intervals[i] = intervals[i - 1];
payloads[i] = payloads[i - 1];
}
intervals[index] = interval;
payloads[index] = value;
size++;
}
void removeAt(size_t& size, size_t index)
{
ASSERT(size <= capacity);
ASSERT(index < size);
for (size_t i = index; i < size - 1; ++i) {
intervals[i] = intervals[i + 1];
payloads[i] = payloads[i + 1];
}
size--;
}
// Find the least interval with end greater than the given point, and return the index, if exists.
// Otherwise, returns size if no such interval exists.
size_t firstIntervalEndAfter(size_t size, T point) const
{
ASSERT(size <= capacity);
for (size_t i = 0; i < size; i++) {
if (point < intervals[i].end())
return i;
}
return size;
}
// Intervals and payloads are stored separately for better cache access patterns in the case
// that cacheLinesPerNode > 1.
std::array<Interval, order> intervals;
std::array<Payload, order> payloads; // Either the NodeRefs to children (InnerNode) or the values (LeafNode)
};
// NodeRef is used to hold links from parent to children. The NodeRef contains both the pointer to the
// child node (which may be either another InnerNode or a LeafNode) and the number of elements stored
// in that pointed to node. This is more space and cache efficient than storing the size in each node
// because it uses less storage and the size of a child node can be determined without accessing the
// child node's cacheline.
class NodeRef {
public:
static_assert(isPowerOfTwo(cpuCacheLineSize));
static constexpr uintptr_t sizeMask = cpuCacheLineSize - 1;
static_assert(leafOrder <= sizeMask && innerOrder <= sizeMask);
NodeRef()
: m_bits(0) { }
NodeRef(Node* ptr, size_t size)
: m_bits(reinterpret_cast<uintptr_t>(ptr) | size)
{
ASSERT(!(reinterpret_cast<uintptr_t>(ptr) & sizeMask));
ASSERT(size <= sizeMask);
}
Node* node() const
{
return reinterpret_cast<Node*>(m_bits & ~sizeMask);
}
size_t size() const
{
return m_bits & sizeMask;
}
void setSize(size_t newSize)
{
ASSERT(newSize <= sizeMask);
m_bits = (m_bits & ~sizeMask) | newSize;
}
explicit operator bool() const { return m_bits; }
template<typename NodeType> requires std::is_base_of_v<Node, NodeType>
NodeType* as() const
{
return static_cast<NodeType*>(node());
}
LeafNode* asLeaf() const
{
return as<LeafNode>();
}
InnerNode* asInner() const
{
return as<InnerNode>();
}
private:
uintptr_t m_bits;
};
// LeafNodes are always at depth of m_height.
struct LeafNode : public NodeImpl<Value, leafOrder> {
Value& value(size_t index)
{
ASSERT(index < leafOrder);
return this->payloads[index];
}
};
// InnerNode are at all depths != m_height.
struct InnerNode : public NodeImpl<NodeRef, innerOrder> {
NodeRef& child(size_t index)
{
ASSERT(index < innerOrder);
return this->payloads[index];
}
size_t subtreeForInsert(size_t size, T endPoint) const
{
ASSERT(size);
ASSERT(size <= innerOrder);
for (size_t i = 0; i < size - 1; i++) {
if (endPoint <= this->intervals[i + 1].begin())
return i;
}
return size - 1;
}
};
private:
struct PathEntry {
NodeRef* nodeRef; // Indirection allows insert/erase to perform tree modifications
size_t index;
bool operator==(const PathEntry& other) const
{
return nodeRef->node() == other.nodeRef->node() && index == other.index;
}
};
// Path specifies which NodeRef and index were traversed at each depth to reach a particular payload within the tree.
class Path : public Vector<PathEntry, 8> {
using Base = Vector<PathEntry, 8>;
public:
Path() = default;
Path(const Path& from, unsigned depth)
: Base(from)
{
ASSERT(this->size() > depth);
this->shrink(depth + 1);
}
// Advances to the next index of the leaf node, if exists. If the current leaf node is exhausted,
// advances to the leaf node to the immediate right and set index to 0.
void nextIndexInLeaf()
{
ASSERT(this->size());
PathEntry& leafEntry = this->last();
if (++leafEntry.index < leafEntry.nodeRef->size()) [[likely]]
return;
// Move on to the next leaf node, if exists.
toRightCousin();
ASSERT(!this->size() || !this->last().index);
}
// Cousin means node at the same depth (includes siblings, aka 0th cousin). The immediate
// left and right cousins may be in different subtrees, i.e. not necessarily siblings.
void toLeftCousin() { toCousin<TraverseLeft>(); }
void toRightCousin() { toCousin<TraverseRight>(); }
private:
struct TraverseLeft {
static bool hasMoreChildren(const PathEntry& entry)
{
// If index != 0, then we can traverse left at this level.
return !!entry.index;
}
static size_t nextSubtreeIndex(const PathEntry& entry)
{
ASSERT(entry.index);
// Left sibling is in the previous subtree.
return entry.index - 1;
}
static size_t descendIndex(const NodeRef nodeRef)
{
ASSERT(nodeRef.size());
// Descend down the right-most branches.
return nodeRef.size() - 1;
}
};
struct TraverseRight {
static bool hasMoreChildren(const PathEntry& entry)
{
// If index != size() - 1, then we can traverse right at this level.
return entry.index < entry.nodeRef->size() - 1;
}
static size_t nextSubtreeIndex(const PathEntry& entry)
{
ASSERT(entry.index < entry.nodeRef->size() - 1);
// Right sibling is in the next subtree.
return entry.index + 1;
}
static size_t descendIndex(const NodeRef nodeRef)
{
ASSERT_UNUSED(nodeRef, nodeRef.size());
// Descend down the left-most branches.
return 0;
}
};
// Modifies the path so that it becomes the path to the immediate left or right cousin.
template<typename Traverser>
void toCousin()
{
int initialDepth = this->size() - 1;
if (!initialDepth) {
this->clear(); // Root has no cousins
return;
}
// Ascend up until we find a node with indicies to the left.
int depth = initialDepth - 1;
for (; depth >= 0; depth--) {
PathEntry& innerEntry = this->at(depth);
if (Traverser::hasMoreChildren(innerEntry))
break;
}
if (depth < 0) {
// Exhausted all indicies of the root node.
this->clear();
return;
}
// Descend down the right-most edge of the left subtree.
PathEntry& innerEntry = this->at(depth);
innerEntry.index = Traverser::nextSubtreeIndex(innerEntry);
depth++;
NodeRef* childRef = &innerEntry.nodeRef->asInner()->child(innerEntry.index);
for (; depth < initialDepth; depth++) {
ASSERT(childRef->size());
size_t childIndex = Traverser::descendIndex(*childRef);
this->at(depth).nodeRef = childRef;
this->at(depth).index = childIndex;
childRef = &childRef->asInner()->child(childIndex);
}
ASSERT(childRef->size());
this->at(depth).nodeRef = childRef;
this->at(depth).index = Traverser::descendIndex(*childRef);
}
};
public:
class iterator {
public:
iterator() = default;
iterator(Path&& path)
: m_path(WTF::move(path)) { }
const Interval& interval() const
{
auto [leaf, index] = leafAndIndex();
return leaf->interval(index);
}
const Value& value() const
{
auto [leaf, index] = leafAndIndex();
return leaf->value(index);
}
const std::pair<Interval, Value> operator*() const
{
return { interval(), value() };
}
iterator& operator++()
{
m_path.nextIndexInLeaf();
return *this;
}
bool operator==(const iterator& other) const
{
return m_path == other.m_path;
}
bool operator!=(const iterator& other) const
{
return !(*this == other);
}
private:
const std::pair<LeafNode*, unsigned> leafAndIndex() const
{
const PathEntry& entry = m_path.last();
return { entry.nodeRef->asLeaf(), entry.index };
}
Path m_path;
};
// Returns an iterator with the path to the left-most leaf node and index 0
iterator begin() const
{
if (isEmpty())
return end();
Path path;
NodeRef* nodeRef = const_cast<NodeRef*>(&m_root);
// Generate path to the left-most leaf node.
for (unsigned depth = 0; depth < m_height; depth++) {
ASSERT(nodeRef->size());
path.append({ nodeRef, 0 });
nodeRef = &nodeRef->asInner()->child(0);
}
// Leaf node
ASSERT(nodeRef->size());
path.append({ nodeRef, 0 });
ASSERT(path.size() == m_height + 1);
return iterator(WTF::move(path));
}
iterator end() const
{
return iterator();
}
private:
bool isFirstOrLastIndex(NodeRef nodeRef, size_t index)
{
ASSERT(index < nodeRef.size());
return !index || index == nodeRef.size() - 1;
}
// After an interval within a node, give by path and depth, is modified, propagate the new interval
// information upwards, as necessary, in order to keep inner nodes' "coverage" intervals consistent.
void updateCoverage(const Path& path, int depth, Interval coverage)
{
ASSERT(depth >= 0);
depth--; // So that depth is at the parent of the node with 'coverage'.
while (depth >= 0) {
const PathEntry& entry = path[depth];
InnerNode* inner = entry.nodeRef->asInner();
inner->interval(entry.index) = coverage;
if (!isFirstOrLastIndex(*entry.nodeRef, entry.index)) {
// Since first/last of this node was not modified, its coverage hasn't changed - no need to continue upward.
verifyCoverageConsistency(path, depth, inner->coverage(entry.nodeRef->size()));
return;
}
coverage = inner->coverage(entry.nodeRef->size());
depth--;
}
m_rootInterval = coverage;
}
void verifyCoverageConsistency(const Path& path, int depth, Interval coverage)
{
#ifdef ASSERT_ENABLED
ASSERT(depth >= 0);
depth--;
while (depth >= 0) {
const PathEntry& entry = path[depth];
InnerNode* inner = entry.nodeRef->asInner();
ASSERT(inner->interval(entry.index) == coverage);
coverage = inner->coverage(entry.nodeRef->size());
depth--;
}
if (m_rootInterval != coverage)
dataLogLn("FAIL: m_rootInterval=", m_rootInterval, " coverage=", coverage, " Tree=", *this);
ASSERT(m_rootInterval == coverage);
#endif
}
// Inserts interval and payload into the node referred to by path at the given depth. Updates affected NodePtr
// sizes and coverages for the affected subtree. If the node needed to be split then returns the NodePtr and
// coverage interval for the new node so that the caller can insert the new node into the parent.
template<typename NodeType>
std::pair<NodeRef, Interval> insertInNodeSplitIfNeeded(const Path& path, int depth, const Interval& interval, const typename NodeType::PayloadType& payload)
{
NodeRef* nodeRef = path[depth].nodeRef;
size_t nodeSize = nodeRef->size();
ASSERT(nodeSize <= NodeType::capacity);
if (nodeSize < NodeType::capacity) [[likely]] {
auto insertionIndex = path[depth].index;
auto node = nodeRef->template as<NodeType>();
node->insertAt(nodeSize, insertionIndex, interval, payload);
nodeRef->setSize(nodeSize);
if (isFirstOrLastIndex(*nodeRef, insertionIndex))
updateCoverage(path, depth, node->coverage(nodeSize));
return { NodeRef(), Interval() };
}
if (tryRedistributeLeftAndInsert<NodeType>(path, depth, interval, payload))
return { NodeRef(), Interval() };
if (tryRedistributeRightAndInsert<NodeType>(path, depth, interval, payload))
return { NodeRef(), Interval() };
return splitNodeAndInsert<NodeType>(path, depth, interval, payload);
}
template<typename NodeType>
bool tryRedistributeLeftAndInsert(const Path& path, int depth, const Interval& interval, const typename NodeType::PayloadType& payload)
{
NodeRef* nodeRef = path[depth].nodeRef;
auto insertionIndex = path[depth].index;
auto node = nodeRef->template as<NodeType>();
size_t nodeSize = nodeRef->size();
Path leftPath(path, depth);
leftPath.toLeftCousin();
if (leftPath.isEmpty())
return false;
// Note that since interval begin is used as the boundary between nodes and intervals are not allowed to
// overlap, insertionIndex will never be 0 if there is a left node -- the left node would have been chosen instead.
// Therefore if there is only one empty slot, the empty slot can be put into the right node without danger of
// shifting the insertionIndex into the left node.
ASSERT(0 < insertionIndex && insertionIndex <= nodeSize);
NodeRef* leftNodeRef = leftPath[depth].nodeRef;
size_t leftNodeSize = leftNodeRef->size();
if (leftNodeSize == NodeType::capacity)
return false;
auto leftNode = leftNodeRef->template as<NodeType>();
size_t newSize = std::midpoint(leftNodeSize, nodeSize);
ASSERT(newSize < NodeType::capacity);
size_t numToMove = nodeSize - newSize;
leftNode->shiftLeftFrom(leftNodeSize, node, nodeSize, numToMove);
ASSERT(nodeSize == newSize);
if (insertionIndex < numToMove)
leftNode->insertAt(leftNodeSize, leftNodeSize + insertionIndex - numToMove, interval, payload);
else
node->insertAt(nodeSize, insertionIndex - numToMove, interval, payload);
leftNodeRef->setSize(leftNodeSize);
updateCoverage(leftPath, depth, leftNode->coverage(leftNodeSize));
nodeRef->setSize(nodeSize);
updateCoverage(path, depth, node->coverage(nodeSize));
return true;
}
template<typename NodeType>
bool tryRedistributeRightAndInsert(const Path& path, int depth, const Interval& interval, const typename NodeType::PayloadType& payload)
{
NodeRef* nodeRef = path[depth].nodeRef;
auto insertionIndex = path[depth].index;
auto node = nodeRef->template as<NodeType>();
size_t nodeSize = nodeRef->size();
Path rightPath(path, depth);
rightPath.toRightCousin();
if (rightPath.isEmpty())
return false;
NodeRef* rightNodeRef = rightPath[depth].nodeRef;
size_t rightNodeSize = rightNodeRef->size();
if (rightNodeSize == NodeType::capacity)
return false;
auto rightNode = rightNodeRef->template as<NodeType>();
// If the insertion index is after all items of the left node and we only have one empty slot
// we need to insert into the head of the right node.
if (insertionIndex == NodeType::capacity) {
rightNode->insertAt(rightNodeSize, 0, interval, payload);
rightNodeRef->setSize(rightNodeSize);
updateCoverage(rightPath, depth, rightNode->coverage(rightNodeSize));
return true;
}
// Now, we know that insertionINdex < capacity, so if there's only one empty slot between both nodes,
// we should put it in the left node and the insertion point will still always be in the left node.
size_t newSize = std::midpoint(rightNodeSize, nodeSize);
ASSERT(newSize < NodeType::capacity);
size_t numToMove = nodeSize - newSize;
node->shiftRightTo(nodeSize, rightNode, rightNodeSize, numToMove);
ASSERT(nodeSize == newSize);
if (insertionIndex <= nodeSize)
node->insertAt(nodeSize, insertionIndex, interval, payload);
else
rightNode->insertAt(rightNodeSize, insertionIndex - nodeSize, interval, payload);
nodeRef->setSize(nodeSize);
updateCoverage(path, depth, node->coverage(nodeSize));
rightNodeRef->setSize(rightNodeSize);
updateCoverage(rightPath, depth, rightNode->coverage(rightNodeSize));
return true;
}
template<typename NodeType>
std::pair<NodeRef, Interval> splitNodeAndInsert(const Path& path, int depth, const Interval& interval, const typename NodeType::PayloadType& payload)
{
NodeRef* nodeRef = path[depth].nodeRef;
auto insertionIndex = path[depth].index;
auto node = nodeRef->template as<NodeType>();
size_t nodeSize = nodeRef->size();
constexpr size_t splitPoint = (NodeType::capacity + 1) / 2;
auto newNode = allocNode<NodeType>();
ASSERT(nodeSize == NodeType::capacity);
for (size_t i = splitPoint; i < nodeSize; ++i) {
newNode->intervals[i - splitPoint] = node->intervals[i];
newNode->payloads[i - splitPoint] = node->payloads[i];
}
size_t newNodeSize = nodeSize - splitPoint;
nodeSize = splitPoint;
if (insertionIndex <= nodeSize)
node->insertAt(nodeSize, insertionIndex, interval, payload);
else
newNode->insertAt(newNodeSize, insertionIndex - nodeSize, interval, payload);
nodeRef->setSize(nodeSize);
updateCoverage(path, depth, node->coverage(nodeSize));
return { NodeRef(newNode, newNodeSize), newNode->coverage(newNodeSize) };
}
template<typename NodeType>
bool eraseFromNode(const Path& path, int depth)
{
NodeRef* nodeRef = path[depth].nodeRef;
auto eraseIndex = path[depth].index;
auto node = nodeRef->template as<NodeType>();
size_t nodeSize = nodeRef->size();
ASSERT(nodeSize <= NodeType::capacity);
if (nodeSize == 1) [[unlikely]] {
ASSERT(!eraseIndex);
freeNode(node);
*nodeRef = NodeRef();
return true;
}
node->removeAt(nodeSize, eraseIndex);
if (isFirstOrLastIndex(*nodeRef, eraseIndex))
updateCoverage(path, depth, node->coverage(nodeSize));
nodeRef->setSize(nodeSize);
return false;
}
template<typename NodeType>
NodeType* allocNode()
{
ASSERT(++assertOnlyNumNodes);
static_assert(sizeof(NodeType) <= targetNodeSize);
return static_cast<NodeType*>(fastAlignedMalloc(cpuCacheLineSize, targetNodeSize));
}
template<typename NodeType>
void freeNode(NodeType* node)
{
ASSERT(assertOnlyNumNodes--);
fastFree(node);
}
void freeAllNodes()
{
if (!m_root)
return;
Vector<std::pair<NodeRef, unsigned>, 16> stack;
stack.append({ m_root, m_height });
while (!stack.isEmpty()) {
auto [node, distanceToLeaf] = stack.takeLast();
if (!distanceToLeaf) {
freeNode(node.asLeaf());
continue;
}
InnerNode* inner = node.asInner();
for (size_t i = 0; i < node.size(); ++i)
stack.append({ inner->child(i), distanceToLeaf - 1 });
freeNode(inner);
}
}
void dumpSubtree(PrintStream& out, NodeRef nodeRef, unsigned distanceToLeaf, unsigned indent) const
{
auto printIndent = [&] {
for (unsigned i = 0; i < indent; ++i)
out.print(" ");
};
if (distanceToLeaf) {
InnerNode* inner = nodeRef.asInner();
printIndent();
out.println("Inner(size=", nodeRef.size(), ", coverage=", inner->coverage(nodeRef.size()), "):");
for (size_t i = 0; i < nodeRef.size(); ++i) {
printIndent();
out.println(" [", i, "] ", inner->interval(i));
dumpSubtree(out, inner->child(i), distanceToLeaf - 1, indent + 2);
}
} else {
CommaPrinter comma;
LeafNode* leaf = nodeRef.asLeaf();
printIndent();
out.print("Leaf(size=", nodeRef.size(), "): ");
for (size_t i = 0; i < nodeRef.size(); ++i)
out.print(comma, leaf->interval(i), "=", leaf->value(i));
out.println();
}
}
NodeRef m_root { };
Interval m_rootInterval { };
unsigned m_height { 0 };
#if ASSERT_ENABLED
unsigned assertOnlyNumNodes { 0 };
#endif
};
} // namespace WTF
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
using WTF::IntervalSet;