gecko-dev/toolkit/recordreplay/SplayTree.h

357 строки
9.2 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_recordreplay_SplayTree_h
#define mozilla_recordreplay_SplayTree_h
#include "mozilla/Types.h"
#include "ProcessRecordReplay.h"
//#define ENABLE_COHERENCY_CHECKS
namespace mozilla {
namespace recordreplay {
/*
* Class which represents a splay tree with nodes allocated from an alloc
* policy.
*
* Splay trees are balanced binary search trees for which search, insert and
* remove are all amortized O(log n).
*
* T indicates the type of tree elements, L has a Lookup type and a static
* 'ssize_t compare(const L::Lookup&, const T&)' method ordering the elements.
*/
template <class T, class L, class AllocPolicy, size_t ChunkPages>
class SplayTree {
struct Node {
T mItem;
Node* mLeft;
Node* mRight;
Node* mParent;
explicit Node(const T& aItem)
: mItem(aItem), mLeft(nullptr), mRight(nullptr), mParent(nullptr) {}
};
AllocPolicy mAlloc;
Node* mRoot;
Node* mFreeList;
SplayTree(const SplayTree&) = delete;
SplayTree& operator=(const SplayTree&) = delete;
public:
explicit SplayTree(const AllocPolicy& aAlloc = AllocPolicy())
: mAlloc(aAlloc), mRoot(nullptr), mFreeList(nullptr) {}
bool empty() const { return !mRoot; }
void clear() {
while (mRoot) {
remove(mRoot);
}
}
Maybe<T> maybeLookup(const typename L::Lookup& aLookup,
bool aRemove = false) {
if (!mRoot) {
return Nothing();
}
Node* last = lookup(aLookup);
splay(last);
checkCoherency(mRoot, nullptr);
Maybe<T> res;
if (L::compare(aLookup, last->mItem) == 0) {
res = Some(last->mItem);
if (aRemove) {
remove(last);
}
}
return res;
}
// Lookup an item which matches aLookup, or the closest item less than it.
Maybe<T> lookupClosestLessOrEqual(const typename L::Lookup& aLookup,
bool aRemove = false) {
if (!mRoot) {
return Nothing();
}
Node* last = lookup(aLookup);
Node* search = last;
while (search && L::compare(aLookup, search->mItem) < 0) {
search = search->mParent;
}
Maybe<T> res = search ? Some(search->mItem) : Nothing();
if (aRemove && search) {
remove(search);
} else {
splay(last);
}
checkCoherency(mRoot, nullptr);
return res;
}
void insert(const typename L::Lookup& aLookup, const T& aValue) {
MOZ_RELEASE_ASSERT(L::compare(aLookup, aValue) == 0);
Node* element = allocateNode(aValue);
if (!mRoot) {
mRoot = element;
return;
}
Node* last = lookup(aLookup);
ssize_t cmp = L::compare(aLookup, last->mItem);
Node** parentPointer;
if (cmp < 0) {
parentPointer = &last->mLeft;
} else if (cmp > 0) {
parentPointer = &last->mRight;
} else {
// The lookup matches an existing entry in the tree. Place it to the left
// of the element just looked up.
if (!last->mLeft) {
parentPointer = &last->mLeft;
} else {
last = last->mLeft;
while (last->mRight) {
last = last->mRight;
}
parentPointer = &last->mRight;
}
}
MOZ_RELEASE_ASSERT(!*parentPointer);
*parentPointer = element;
element->mParent = last;
splay(element);
checkCoherency(mRoot, nullptr);
}
class Iter {
friend class SplayTree;
SplayTree* mTree;
Node* mNode;
bool mRemoved;
Iter(SplayTree* aTree, Node* aNode)
: mTree(aTree), mNode(aNode), mRemoved(false) {}
public:
const T& ref() { return mNode->mItem; }
bool done() { return !mNode; }
Iter& operator++() {
MOZ_RELEASE_ASSERT(!mRemoved);
if (mNode->mRight) {
mNode = mNode->mRight;
while (mNode->mLeft) {
mNode = mNode->mLeft;
}
} else {
while (true) {
Node* cur = mNode;
mNode = mNode->mParent;
if (!mNode || mNode->mLeft == cur) {
break;
}
}
}
return *this;
}
void removeEntry() {
mTree->remove(mNode);
mRemoved = true;
}
};
Iter begin() {
Node* node = mRoot;
while (node && node->mLeft) {
node = node->mLeft;
}
return Iter(this, node);
}
private:
// Lookup an item matching aLookup, or the closest node to it.
Node* lookup(const typename L::Lookup& aLookup) const {
MOZ_RELEASE_ASSERT(mRoot);
Node* node = mRoot;
Node* parent;
do {
parent = node;
ssize_t c = L::compare(aLookup, node->mItem);
if (c == 0) {
return node;
}
node = (c < 0) ? node->mLeft : node->mRight;
} while (node);
return parent;
}
void remove(Node* aNode) {
splay(aNode);
MOZ_RELEASE_ASSERT(aNode && aNode == mRoot);
// Find another node which can be swapped in for the root: either the
// rightmost child of the root's left, or the leftmost child of the
// root's right.
Node* swap;
Node* swapChild;
if (mRoot->mLeft) {
swap = mRoot->mLeft;
while (swap->mRight) {
swap = swap->mRight;
}
swapChild = swap->mLeft;
} else if (mRoot->mRight) {
swap = mRoot->mRight;
while (swap->mLeft) {
swap = swap->mLeft;
}
swapChild = swap->mRight;
} else {
freeNode(mRoot);
mRoot = nullptr;
return;
}
// The selected node has at most one child, in swapChild. Detach it
// from the subtree by replacing it with that child.
if (swap == swap->mParent->mLeft) {
swap->mParent->mLeft = swapChild;
} else {
swap->mParent->mRight = swapChild;
}
if (swapChild) {
swapChild->mParent = swap->mParent;
}
mRoot->mItem = swap->mItem;
freeNode(swap);
checkCoherency(mRoot, nullptr);
}
size_t NodesPerChunk() const { return ChunkPages * PageSize / sizeof(Node); }
Node* allocateNode(const T& aValue) {
if (!mFreeList) {
Node* nodeArray = mAlloc.template pod_malloc<Node>(NodesPerChunk());
for (size_t i = 0; i < NodesPerChunk() - 1; i++) {
nodeArray[i].mLeft = &nodeArray[i + 1];
}
mFreeList = nodeArray;
}
Node* node = mFreeList;
mFreeList = node->mLeft;
new (node) Node(aValue);
return node;
}
void freeNode(Node* aNode) {
aNode->mLeft = mFreeList;
mFreeList = aNode;
}
void splay(Node* aNode) {
// Rotate the element until it is at the root of the tree. Performing
// the rotations in this fashion preserves the amortized balancing of
// the tree.
MOZ_RELEASE_ASSERT(aNode);
while (aNode != mRoot) {
Node* parent = aNode->mParent;
if (parent == mRoot) {
// Zig rotation.
rotate(aNode);
MOZ_RELEASE_ASSERT(aNode == mRoot);
return;
}
Node* grandparent = parent->mParent;
if ((parent->mLeft == aNode) == (grandparent->mLeft == parent)) {
// Zig-zig rotation.
rotate(parent);
rotate(aNode);
} else {
// Zig-zag rotation.
rotate(aNode);
rotate(aNode);
}
}
}
void rotate(Node* aNode) {
// Rearrange nodes so that node becomes the parent of its current
// parent, while preserving the sortedness of the tree.
Node* parent = aNode->mParent;
if (parent->mLeft == aNode) {
// x y
// y c ==> a x
// a b b c
parent->mLeft = aNode->mRight;
if (aNode->mRight) {
aNode->mRight->mParent = parent;
}
aNode->mRight = parent;
} else {
MOZ_RELEASE_ASSERT(parent->mRight == aNode);
// x y
// a y ==> x c
// b c a b
parent->mRight = aNode->mLeft;
if (aNode->mLeft) {
aNode->mLeft->mParent = parent;
}
aNode->mLeft = parent;
}
aNode->mParent = parent->mParent;
parent->mParent = aNode;
if (Node* grandparent = aNode->mParent) {
if (grandparent->mLeft == parent) {
grandparent->mLeft = aNode;
} else {
grandparent->mRight = aNode;
}
} else {
mRoot = aNode;
}
}
#ifdef ENABLE_COHERENCY_CHECKS
Node* checkCoherency(Node* aNode, Node* aMinimum) {
if (!aNode) {
MOZ_RELEASE_ASSERT(!mRoot);
return nullptr;
}
MOZ_RELEASE_ASSERT(aNode->mParent || aNode == mRoot);
MOZ_RELEASE_ASSERT(!aMinimum || L::compare(L::getLookup(aMinimum->mItem),
aNode->mItem) <= 0);
if (aNode->mLeft) {
MOZ_RELEASE_ASSERT(aNode->mLeft->mParent == aNode);
Node* leftMaximum = checkCoherency(aNode->mLeft, aMinimum);
MOZ_RELEASE_ASSERT(
L::compare(L::getLookup(leftMaximum->mItem), aNode->mItem) <= 0);
}
if (aNode->mRight) {
MOZ_RELEASE_ASSERT(aNode->mRight->mParent == aNode);
return checkCoherency(aNode->mRight, aNode);
}
return aNode;
}
#else
inline void checkCoherency(Node* aNode, Node* aMinimum) {}
#endif
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_SplayTree_h