8345314: Add a red–black tree as a utility data structure

Reviewed-by: aboldtch, jsjolen, stuefe
This commit is contained in:
Casper Norrbin 2025-01-30 12:34:29 +00:00
parent a937f6db30
commit 2efb6aaadb
3 changed files with 1455 additions and 0 deletions

View File

@ -0,0 +1,341 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_UTILITIES_RBTREE_HPP
#define SHARE_UTILITIES_RBTREE_HPP
#include "nmt/memTag.hpp"
#include "runtime/os.hpp"
#include "utilities/globalDefinitions.hpp"
#include <type_traits>
// COMPARATOR must have a static function `cmp(a,b)` which returns:
// - an int < 0 when a < b
// - an int == 0 when a == b
// - an int > 0 when a > b
// ALLOCATOR must check for oom and exit, as RBTree currently does not handle the
// allocation failing.
// Key needs to be of a type that is trivially destructible.
// The tree will call a value's destructor when its node is removed.
// Nodes are address stable and will not change during its lifetime.
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
class RBTree {
friend class RBTreeTest;
private:
ALLOCATOR _allocator;
size_t _num_nodes;
public:
class RBNode {
friend RBTree;
friend class RBTreeTest;
private:
uintptr_t _parent; // LSB encodes color information. 0 = RED, 1 = BLACK
RBNode* _left;
RBNode* _right;
const K _key;
V _value;
DEBUG_ONLY(bool _visited);
public:
const K& key() const { return _key; }
V& val() { return _value; }
const V& val() const { return _value; }
private:
bool is_black() const { return (_parent & 0x1) != 0; }
bool is_red() const { return (_parent & 0x1) == 0; }
void set_black() { _parent |= 0x1; }
void set_red() { _parent &= ~0x1; }
RBNode* parent() const { return (RBNode*)(_parent & ~0x1); }
void set_parent(RBNode* new_parent) { _parent = (_parent & 0x1) | (uintptr_t)new_parent; }
RBNode(const K& key, const V& val DEBUG_ONLY(COMMA bool visited))
: _parent(0), _left(nullptr), _right(nullptr),
_key(key), _value(val) DEBUG_ONLY(COMMA _visited(visited)) {}
bool is_right_child() const {
return parent() != nullptr && parent()->_right == this;
}
bool is_left_child() const {
return parent() != nullptr && parent()->_left == this;
}
void replace_child(RBNode* old_child, RBNode* new_child);
// This node down, right child up
// Returns right child (now parent)
RBNode* rotate_left();
// This node down, left child up
// Returns left child (now parent)
RBNode* rotate_right();
RBNode* prev();
RBNode* next();
#ifdef ASSERT
void verify(size_t& num_nodes, size_t& black_nodes_until_leaf,
size_t& shortest_leaf_path, size_t& longest_leaf_path,
size_t& tree_depth, bool expect_visited);
#endif // ASSERT
};
private:
RBNode* _root;
DEBUG_ONLY(bool _expected_visited);
RBNode* allocate_node(const K& key, const V& val) {
void* node_place = _allocator.allocate(sizeof(RBNode));
assert(node_place != nullptr, "rb-tree allocator must exit on failure");
_num_nodes++;
return new (node_place) RBNode(key, val DEBUG_ONLY(COMMA _expected_visited));
}
void free_node(RBNode* node) {
node->_value.~V();
_allocator.free(node);
_num_nodes--;
}
// True if node is black (nil nodes count as black)
static inline bool is_black(const RBNode* node) {
return node == nullptr || node->is_black();
}
static inline bool is_red(const RBNode* node) {
return node != nullptr && node->is_red();
}
// If the node with key k already exist, the value is updated instead.
RBNode* insert_node(const K& key, const V& val);
void fix_insert_violations(RBNode* node);
void remove_black_leaf(RBNode* node);
// Assumption: node has at most one child. Two children is handled in `remove()`
void remove_from_tree(RBNode* node);
public:
NONCOPYABLE(RBTree);
RBTree() : _allocator(), _num_nodes(0), _root(nullptr) DEBUG_ONLY(COMMA _expected_visited(false)) {
static_assert(std::is_trivially_destructible<K>::value, "key type must be trivially destructable");
}
~RBTree() { this->remove_all(); }
size_t size() { return _num_nodes; }
// Inserts a node with the given k/v into the tree,
// if the key already exist, the value is updated instead.
void upsert(const K& key, const V& val) {
RBNode* node = insert_node(key, val);
fix_insert_violations(node);
}
// Removes the node with the given key from the tree if it exists.
// Returns true if the node was successfully removed, false otherwise.
bool remove(const K& key) {
RBNode* node = find_node(key);
if (node == nullptr){
return false;
}
remove(node);
return true;
}
// Removes the given node from the tree. node must be a valid node
void remove(RBNode* node);
// Removes all existing nodes from the tree.
void remove_all() {
RBNode* to_delete[64];
int stack_idx = 0;
to_delete[stack_idx++] = _root;
while (stack_idx > 0) {
RBNode* head = to_delete[--stack_idx];
if (head == nullptr) continue;
to_delete[stack_idx++] = head->_left;
to_delete[stack_idx++] = head->_right;
free_node(head);
}
_num_nodes = 0;
_root = nullptr;
}
// Finds the node with the closest key <= the given key
const RBNode* closest_leq(const K& key) const {
RBNode* candidate = nullptr;
RBNode* pos = _root;
while (pos != nullptr) {
const int cmp_r = COMPARATOR::cmp(pos->key(), key);
if (cmp_r == 0) { // Exact match
candidate = pos;
break; // Can't become better than that.
}
if (cmp_r < 0) {
// Found a match, try to find a better one.
candidate = pos;
pos = pos->_right;
} else {
pos = pos->_left;
}
}
return candidate;
}
// Finds the node with the closest key > the given key
const RBNode* closest_gt(const K& key) const {
RBNode* candidate = nullptr;
RBNode* pos = _root;
while (pos != nullptr) {
const int cmp_r = COMPARATOR::cmp(pos->key(), key);
if (cmp_r > 0) {
// Found a match, try to find a better one.
candidate = pos;
pos = pos->_left;
} else {
pos = pos->_right;
}
}
return candidate;
}
// Finds the node with the closest key >= the given key
const RBNode* closest_geq(const K& key) const {
RBNode* candidate = nullptr;
RBNode* pos = _root;
while (pos != nullptr) {
const int cmp_r = COMPARATOR::cmp(pos->key(), key);
if (cmp_r == 0) { // Exact match
candidate = pos;
break; // Can't become better than that.
}
if (cmp_r > 0) {
// Found a match, try to find a better one.
candidate = pos;
pos = pos->_left;
} else {
pos = pos->_right;
}
}
return candidate;
}
RBNode* closest_leq(const K& key) {
return const_cast<RBNode*>(
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->closest_leq(key));
}
RBNode* closest_gt(const K& key) {
return const_cast<RBNode*>(
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->closest_gt(key));
}
RBNode* closest_geq(const K& key) {
return const_cast<RBNode*>(
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->closest_geq(key));
}
struct Range {
RBNode* start;
RBNode* end;
Range(RBNode* start, RBNode* end)
: start(start), end(end) {}
};
// Return the range [start, end)
// where start->key() <= addr < end->key().
// Failure to find the range leads to start and/or end being null.
Range find_enclosing_range(K key) {
RBNode* start = closest_leq(key);
RBNode* end = closest_gt(key);
return Range(start, end);
}
// Finds the node associated with the key
const RBNode* find_node(const K& key) const;
RBNode* find_node(const K& key) {
return const_cast<RBNode*>(
static_cast<const RBTree<K, V, COMPARATOR, ALLOCATOR>*>(this)->find_node(key));
}
// Finds the value associated with the key
V* find(const K& key) {
RBNode* node = find_node(key);
return node == nullptr ? nullptr : &node->val();
}
const V* find(const K& key) const {
const RBNode* node = find_node(key);
return node == nullptr ? nullptr : &node->val();
}
// Visit all RBNodes in ascending order, calling f on each node.
template <typename F>
void visit_in_order(F f) const;
// Visit all RBNodes in ascending order whose keys are in range [from, to), calling f on each node.
template <typename F>
void visit_range_in_order(const K& from, const K& to, F f);
#ifdef ASSERT
// Verifies that the tree is correct and holds rb-properties
void verify_self();
#endif // ASSERT
};
template <MemTag mem_tag>
class RBTreeCHeapAllocator {
public:
void* allocate(size_t sz) {
void* allocation = os::malloc(sz, mem_tag);
if (allocation == nullptr) {
vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR,
"red-black tree failed allocation");
}
return allocation;
}
void free(void* ptr) { os::free(ptr); }
};
template <typename K, typename V, typename COMPARATOR, MemTag mem_tag>
using RBTreeCHeap = RBTree<K, V, COMPARATOR, RBTreeCHeapAllocator<mem_tag>>;
#endif // SHARE_UTILITIES_RBTREE_HPP

View File

@ -0,0 +1,541 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_UTILITIES_RBTREE_INLINE_HPP
#define SHARE_UTILITIES_RBTREE_INLINE_HPP
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/powerOfTwo.hpp"
#include "utilities/rbTree.hpp"
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::replace_child(
RBNode* old_child, RBNode* new_child) {
if (_left == old_child) {
_left = new_child;
} else if (_right == old_child) {
_right = new_child;
} else {
ShouldNotReachHere();
}
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::rotate_left() {
// This node down, right child up
RBNode* old_right = _right;
_right = old_right->_left;
if (_right != nullptr) {
_right->set_parent(this);
}
old_right->set_parent(parent());
if (parent() != nullptr) {
parent()->replace_child(this, old_right);
}
old_right->_left = this;
set_parent(old_right);
return old_right;
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::rotate_right() {
// This node down, left child up
RBNode* old_left = _left;
_left = old_left->_right;
if (_left != nullptr) {
_left->set_parent(this);
}
old_left->set_parent(parent());
if (parent() != nullptr) {
parent()->replace_child(this, old_left);
}
old_left->_right = this;
set_parent(old_left);
return old_left;
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::prev() {
RBNode* node = this;
if (_left != nullptr) { // right subtree exists
node = _left;
while (node->_right != nullptr) {
node = node->_right;
}
return node;
}
while (node != nullptr && node->is_left_child()) {
node = node->parent();
}
return node->parent();
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::next() {
RBNode* node = this;
if (_right != nullptr) { // right subtree exists
node = _right;
while (node->_left != nullptr) {
node = node->_left;
}
return node;
}
while (node != nullptr && node->is_right_child()) {
node = node->parent();
}
return node->parent();
}
#ifdef ASSERT
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode::verify(
size_t& num_nodes, size_t& black_nodes_until_leaf, size_t& shortest_leaf_path, size_t& longest_leaf_path,
size_t& tree_depth, bool expect_visited) {
assert(expect_visited != _visited, "node already visited");
_visited = !_visited;
size_t num_black_nodes_left = 0;
size_t shortest_leaf_path_left = 0;
size_t longest_leaf_path_left = 0;
size_t tree_depth_left = 0;
if (_left != nullptr) {
if (_right == nullptr) {
assert(is_black() && _left->is_red(), "if one child it must be red and node black");
}
assert(COMPARATOR::cmp(_left->key(), _key) < 0, "left node must be less than parent");
assert(is_black() || _left->is_black(), "2 red nodes in a row");
assert(_left->parent() == this, "pointer mismatch");
_left->verify(num_nodes, num_black_nodes_left, shortest_leaf_path_left,
longest_leaf_path_left, tree_depth_left, expect_visited);
}
size_t num_black_nodes_right = 0;
size_t shortest_leaf_path_right = 0;
size_t longest_leaf_path_right = 0;
size_t tree_depth_right = 0;
if (_right != nullptr) {
if (_left == nullptr) {
assert(is_black() && _right->is_red(), "if one child it must be red and node black");
}
assert(COMPARATOR::cmp(_right->key(), _key) > 0, "right node must be greater than parent");
assert(is_black() || _left->is_black(), "2 red nodes in a row");
assert(_right->parent() == this, "pointer mismatch");
_right->verify(num_nodes, num_black_nodes_right, shortest_leaf_path_right,
longest_leaf_path_right, tree_depth_right, expect_visited);
}
shortest_leaf_path = MAX2(longest_leaf_path_left, longest_leaf_path_right);
longest_leaf_path = MAX2(longest_leaf_path_left, longest_leaf_path_right);
assert(shortest_leaf_path <= longest_leaf_path && longest_leaf_path <= shortest_leaf_path * 2,
"tree imbalanced, shortest path: %zu longest: %zu", shortest_leaf_path, longest_leaf_path);
assert(num_black_nodes_left == num_black_nodes_right,
"number of black nodes in left/right subtree should match");
num_nodes++;
tree_depth = 1 + MAX2(tree_depth_left, tree_depth_right);
shortest_leaf_path++;
longest_leaf_path++;
black_nodes_until_leaf = num_black_nodes_left;
if (is_black()) {
black_nodes_until_leaf++;
}
}
#endif // ASSERT
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline const typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
RBTree<K, V, COMPARATOR, ALLOCATOR>::find_node(const K& key) const {
RBNode* curr = _root;
while (curr != nullptr) {
const int key_cmp_k = COMPARATOR::cmp(key, curr->key());
if (key_cmp_k == 0) {
return curr;
} else if (key_cmp_k < 0) {
curr = curr->_left;
} else {
curr = curr->_right;
}
}
return nullptr;
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline typename RBTree<K, V, COMPARATOR, ALLOCATOR>::RBNode*
RBTree<K, V, COMPARATOR, ALLOCATOR>::insert_node(const K& key, const V& val) {
RBNode* curr = _root;
if (curr == nullptr) { // Tree is empty
_root = allocate_node(key, val);
return _root;
}
RBNode* parent = nullptr;
while (curr != nullptr) {
const int key_cmp_k = COMPARATOR::cmp(key, curr->key());
if (key_cmp_k == 0) {
curr->_value = val;
return curr;
}
parent = curr;
if (key_cmp_k < 0) {
curr = curr->_left;
} else {
curr = curr->_right;
}
}
// Create and insert new node
RBNode* node = allocate_node(key, val);
node->set_parent(parent);
const int key_cmp_k = COMPARATOR::cmp(key, parent->key());
if (key_cmp_k < 0) {
parent->_left = node;
} else {
parent->_right = node;
}
return node;
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::fix_insert_violations(RBNode* node) {
if (node->is_black()) { // node's value was updated
return; // Tree is already correct
}
RBNode* parent = node->parent();
while (parent != nullptr && parent->is_red()) {
// Node and parent are both red, creating a red-violation
RBNode* grandparent = parent->parent();
if (grandparent == nullptr) { // Parent is the tree root
assert(parent == _root, "parent must be root");
parent->set_black(); // Color parent black to eliminate the red-violation
return;
}
RBNode* uncle = parent->is_left_child() ? grandparent->_right : grandparent->_left;
if (is_black(uncle)) { // Parent is red, uncle is black
// Rotate the parent to the position of the grandparent
if (parent->is_left_child()) {
if (node->is_right_child()) { // Node is an "inner" node
// Rotate and swap node and parent to make it an "outer" node
parent->rotate_left();
parent = node;
}
grandparent->rotate_right(); // Rotate the parent to the position of the grandparent
} else if (parent->is_right_child()) {
if (node->is_left_child()) { // Node is an "inner" node
// Rotate and swap node and parent to make it an "outer" node
parent->rotate_right();
parent = node;
}
grandparent->rotate_left(); // Rotate the parent to the position of the grandparent
}
// Swap parent and grandparent colors to eliminate the red-violation
parent->set_black();
grandparent->set_red();
if (_root == grandparent) {
_root = parent;
}
return;
}
// Parent and uncle are both red
// Paint both black, paint grandparent red to not create a black-violation
parent->set_black();
uncle->set_black();
grandparent->set_red();
// Move up two levels to check for new potential red-violation
node = grandparent;
parent = grandparent->parent();
}
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::remove_black_leaf(RBNode* node) {
// Black node removed, balancing needed
RBNode* parent = node->parent();
while (parent != nullptr) {
// Sibling must exist. If it did not, node would need to be red to not break
// tree properties, and could be trivially removed before reaching here
RBNode* sibling = node->is_left_child() ? parent->_right : parent->_left;
if (is_red(sibling)) { // Sibling red, parent and nephews must be black
assert(is_black(parent), "parent must be black");
assert(is_black(sibling->_left), "nephew must be black");
assert(is_black(sibling->_right), "nephew must be black");
// Swap parent and sibling colors
parent->set_red();
sibling->set_black();
// Rotate parent down and sibling up
if (node->is_left_child()) {
parent->rotate_left();
sibling = parent->_right;
} else {
parent->rotate_right();
sibling = parent->_left;
}
if (_root == parent) {
_root = parent->parent();
}
// Further balancing needed
}
RBNode* close_nephew = node->is_left_child() ? sibling->_left : sibling->_right;
RBNode* distant_nephew = node->is_left_child() ? sibling->_right : sibling->_left;
if (is_red(distant_nephew) || is_red(close_nephew)) {
if (is_black(distant_nephew)) { // close red, distant black
// Rotate sibling down and inner nephew up
if (node->is_left_child()) {
sibling->rotate_right();
} else {
sibling->rotate_left();
}
distant_nephew = sibling;
sibling = close_nephew;
distant_nephew->set_red();
sibling->set_black();
}
// Distant nephew red
// Rotate parent down and sibling up
if (node->is_left_child()) {
parent->rotate_left();
} else {
parent->rotate_right();
}
if (_root == parent) {
_root = sibling;
}
// Swap parent and sibling colors
if (parent->is_black()) {
sibling->set_black();
} else {
sibling->set_red();
}
parent->set_black();
// Color distant nephew black to restore black balance
distant_nephew->set_black();
return;
}
if (is_red(parent)) { // parent red, sibling and nephews black
// Swap parent and sibling colors to restore black balance
sibling->set_red();
parent->set_black();
return;
}
// Parent, sibling, and both nephews black
// Color sibling red and move up one level
sibling->set_red();
node = parent;
parent = node->parent();
}
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::remove_from_tree(RBNode* node) {
RBNode* parent = node->parent();
RBNode* left = node->_left;
RBNode* right = node->_right;
if (left != nullptr) { // node has a left only-child
// node must be black, and child red, otherwise a black-violation would
// exist Remove node and color the child black.
assert(right == nullptr, "right must be nullptr");
assert(is_black(node), "node must be black");
assert(is_red(left), "child must be red");
left->set_black();
left->set_parent(parent);
if (parent == nullptr) {
assert(node == _root, "node must be root");
_root = left;
} else {
parent->replace_child(node, left);
}
} else if (right != nullptr) { // node has a right only-child
// node must be black, and child red, otherwise a black-violation would
// exist Remove node and color the child black.
assert(left == nullptr, "left must be nullptr");
assert(is_black(node), "node must be black");
assert(is_red(right), "child must be red");
right->set_black();
right->set_parent(parent);
if (parent == nullptr) {
assert(node == _root, "node must be root");
_root = right;
} else {
parent->replace_child(node, right);
}
} else { // node has no children
if (node == _root) { // Tree empty
_root = nullptr;
} else {
if (is_black(node)) {
// Removed node is black, creating a black imbalance
remove_black_leaf(node);
}
parent->replace_child(node, nullptr);
}
}
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::remove(RBNode* node) {
assert(node != nullptr, "must be");
if (node->_left != nullptr && node->_right != nullptr) { // node has two children
// Swap place with the in-order successor and delete there instead
RBNode* curr = node->_right;
while (curr->_left != nullptr) {
curr = curr->_left;
}
if (_root == node) _root = curr;
swap(curr->_left, node->_left);
swap(curr->_parent, node->_parent); // Swaps parent and color
// If node is curr's parent, parent and right pointers become invalid
if (node->_right == curr) {
node->_right = curr->_right;
node->set_parent(curr);
curr->_right = node;
} else {
swap(curr->_right, node->_right);
node->parent()->replace_child(curr, node);
curr->_right->set_parent(curr);
}
if (curr->parent() != nullptr) curr->parent()->replace_child(node, curr);
curr->_left->set_parent(curr);
if (node->_left != nullptr) node->_left->set_parent(node);
if (node->_right != nullptr) node->_right->set_parent(node);
}
remove_from_tree(node);
free_node(node);
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
template <typename F>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::visit_in_order(F f) const {
RBNode* to_visit[64];
int stack_idx = 0;
RBNode* head = _root;
while (stack_idx > 0 || head != nullptr) {
while (head != nullptr) {
to_visit[stack_idx++] = head;
head = head->_left;
}
head = to_visit[--stack_idx];
f(head);
head = head->_right;
}
}
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
template <typename F>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::visit_range_in_order(const K& from, const K& to, F f) {
assert(COMPARATOR::cmp(from, to) <= 0, "from must be less or equal to to");
RBNode* curr = closest_geq(from);
if (curr == nullptr) return;
RBNode* end = closest_geq(to);
while (curr != nullptr && curr != end) {
f(curr);
curr = curr->next();
}
}
#ifdef ASSERT
template <typename K, typename V, typename COMPARATOR, typename ALLOCATOR>
inline void RBTree<K, V, COMPARATOR, ALLOCATOR>::verify_self() {
if (_root == nullptr) {
assert(_num_nodes == 0, "rbtree has nodes but no root");
return;
}
assert(_root->parent() == nullptr, "root of rbtree has a parent");
size_t num_nodes = 0;
size_t black_depth = 0;
size_t tree_depth = 0;
size_t shortest_leaf_path = 0;
size_t longest_leaf_path = 0;
_expected_visited = !_expected_visited;
_root->verify(num_nodes, black_depth, shortest_leaf_path, longest_leaf_path, tree_depth, _expected_visited);
const unsigned int maximum_depth = log2i(size() + 1) * 2;
assert(shortest_leaf_path <= longest_leaf_path && longest_leaf_path <= shortest_leaf_path * 2,
"tree imbalanced, shortest path: %zu longest: %zu",
shortest_leaf_path, longest_leaf_path);
assert(tree_depth <= maximum_depth, "rbtree is too deep");
assert(size() == num_nodes,
"unexpected number of nodes in rbtree. expected: %zu"
", actual: %zu", size(), num_nodes);
}
#endif // ASSERT
#endif // SHARE_UTILITIES_RBTREE_INLINE_HPP

View File

@ -0,0 +1,573 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "memory/resourceArea.hpp"
#include "runtime/os.hpp"
#include "testutils.hpp"
#include "unittest.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/rbTree.hpp"
#include "utilities/rbTree.inline.hpp"
class RBTreeTest : public testing::Test {
public:
struct Cmp {
static int cmp(int a, int b) {
return a - b;
}
};
struct CmpInverse {
static int cmp(int a, int b) {
return b - a;
}
};
struct FCmp {
static int cmp(float a, float b) {
if (a < b) return -1;
if (a == b) return 0;
return 1;
}
};
// Bump-pointer style allocator that can't free
template <size_t AreaSize>
struct ArrayAllocator {
uint8_t area[AreaSize];
size_t offset = 0;
void* allocate(size_t sz) {
if (offset + sz > AreaSize) {
vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR,
"red-black tree failed allocation");
}
void* place = &area[offset];
offset += sz;
return place;
}
void free(void* ptr) { }
};
#ifdef ASSERT
template<typename K, typename V, typename CMP, typename ALLOC>
void verify_it(RBTree<K, V, CMP, ALLOC>& t) {
t.verify_self();
}
#endif // ASSERT
using RBTreeInt = RBTreeCHeap<int, int, Cmp, mtOther>;
public:
void inserting_duplicates_results_in_one_value() {
constexpr int up_to = 10;
GrowableArrayCHeap<int, mtTest> nums_seen(up_to, up_to, 0);
RBTreeInt rbtree;
for (int i = 0; i < up_to; i++) {
rbtree.upsert(i, i);
rbtree.upsert(i, i);
rbtree.upsert(i, i);
rbtree.upsert(i, i);
rbtree.upsert(i, i);
}
rbtree.visit_in_order([&](RBTreeInt::RBNode* node) {
nums_seen.at(node->key())++;
});
for (int i = 0; i < up_to; i++) {
EXPECT_EQ(1, nums_seen.at(i));
}
}
void rbtree_ought_not_leak() {
struct LeakCheckedAllocator {
int allocations;
LeakCheckedAllocator()
: allocations(0) {
}
void* allocate(size_t sz) {
void* allocation = os::malloc(sz, mtTest);
if (allocation == nullptr) {
vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR, "rbtree failed allocation");
}
++allocations;
return allocation;
}
void free(void* ptr) {
--allocations;
os::free(ptr);
}
};
constexpr int up_to = 10;
{
RBTree<int, int, Cmp, LeakCheckedAllocator> rbtree;
for (int i = 0; i < up_to; i++) {
rbtree.upsert(i, i);
}
EXPECT_EQ(up_to, rbtree._allocator.allocations);
for (int i = 0; i < up_to; i++) {
rbtree.remove(i);
}
EXPECT_EQ(0, rbtree._allocator.allocations);
EXPECT_EQ(nullptr, rbtree._root);
}
{
RBTree<int, int, Cmp, LeakCheckedAllocator> rbtree;
for (int i = 0; i < up_to; i++) {
rbtree.upsert(i, i);
}
rbtree.remove_all();
EXPECT_EQ(0, rbtree._allocator.allocations);
EXPECT_EQ(nullptr, rbtree._root);
}
}
void test_find() {
struct Empty {};
RBTreeCHeap<float, Empty, FCmp, mtOther> rbtree;
using Node = RBTreeCHeap<float, Empty, FCmp, mtOther>::RBNode;
Node* n = nullptr;
auto test = [&](float f) {
EXPECT_EQ(nullptr, rbtree.find(f));
rbtree.upsert(f, Empty{});
const Node* n = rbtree.find_node(f);
EXPECT_NE(nullptr, n);
EXPECT_EQ(f, n->key());
};
test(1.0f);
test(5.0f);
test(0.0f);
}
void test_visitors() {
{ // Tests with 'default' ordering (ascending)
RBTreeInt rbtree;
using Node = RBTreeInt::RBNode;
rbtree.visit_range_in_order(0, 100, [&](Node* x) {
EXPECT_TRUE(false) << "Empty rbtree has no nodes to visit";
});
// Single-element set
rbtree.upsert(1, 0);
int count = 0;
rbtree.visit_range_in_order(0, 100, [&](Node* x) {
count++;
});
EXPECT_EQ(1, count);
count = 0;
rbtree.visit_in_order([&](Node* x) {
count++;
});
EXPECT_EQ(1, count);
// Add an element outside of the range that should not be visited on the right side and
// one on the left side.
rbtree.upsert(101, 0);
rbtree.upsert(-1, 0);
count = 0;
rbtree.visit_range_in_order(0, 100, [&](Node* x) {
count++;
});
EXPECT_EQ(1, count);
count = 0;
rbtree.visit_in_order([&](Node* x) {
count++;
});
EXPECT_EQ(3, count);
// Visiting empty range [0, 0) == {}
rbtree.upsert(0, 0); // This node should not be visited.
rbtree.visit_range_in_order(0, 0, [&](Node* x) {
EXPECT_TRUE(false) << "Empty visiting range should not visit any node";
});
rbtree.remove_all();
for (int i = 0; i < 11; i++) {
rbtree.upsert(i, 0);
}
ResourceMark rm;
GrowableArray<int> seen;
rbtree.visit_range_in_order(0, 10, [&](Node* x) {
seen.push(x->key());
});
EXPECT_EQ(10, seen.length());
for (int i = 0; i < 10; i++) {
EXPECT_EQ(i, seen.at(i));
}
seen.clear();
rbtree.visit_in_order([&](Node* x) {
seen.push(x->key());
});
EXPECT_EQ(11, seen.length());
for (int i = 0; i < 10; i++) {
EXPECT_EQ(i, seen.at(i));
}
seen.clear();
rbtree.visit_range_in_order(10, 12, [&](Node* x) {
seen.push(x->key());
});
EXPECT_EQ(1, seen.length());
EXPECT_EQ(10, seen.at(0));
}
{ // Test with descending ordering
RBTreeCHeap<int, int, CmpInverse, mtOther> rbtree;
using Node = RBTreeCHeap<int, int, CmpInverse, mtOther>::RBNode;
for (int i = 0; i < 10; i++) {
rbtree.upsert(i, 0);
}
ResourceMark rm;
GrowableArray<int> seen;
rbtree.visit_range_in_order(9, -1, [&](Node* x) {
seen.push(x->key());
});
EXPECT_EQ(10, seen.length());
for (int i = 0; i < 10; i++) {
EXPECT_EQ(10-i-1, seen.at(i));
}
seen.clear();
rbtree.visit_in_order([&](Node* x) {
seen.push(x->key());
});
EXPECT_EQ(10, seen.length());
for (int i = 0; i < 10; i++) {
EXPECT_EQ(10 - i - 1, seen.at(i));
}
}
}
void test_closest_leq() {
using Node = RBTreeInt::RBNode;
{
RBTreeInt rbtree;
Node* n = rbtree.closest_leq(0);
EXPECT_EQ(nullptr, n);
rbtree.upsert(0, 0);
n = rbtree.closest_leq(0);
EXPECT_EQ(0, n->key());
rbtree.upsert(-1, -1);
n = rbtree.closest_leq(0);
EXPECT_EQ(0, n->key());
rbtree.upsert(6, 0);
n = rbtree.closest_leq(6);
EXPECT_EQ(6, n->key());
n = rbtree.closest_leq(-2);
EXPECT_EQ(nullptr, n);
}
}
void test_node_prev() {
RBTreeInt _tree;
using Node = RBTreeInt::RBNode;
constexpr int num_nodes = 100;
for (int i = num_nodes; i > 0; i--) {
_tree.upsert(i, i);
}
Node* node = _tree.find_node(num_nodes);
int count = num_nodes;
while (node != nullptr) {
EXPECT_EQ(count, node->val());
node = node->prev();
count--;
}
EXPECT_EQ(count, 0);
}
void test_node_next() {
RBTreeInt _tree;
using Node = RBTreeInt::RBNode;
constexpr int num_nodes = 100;
for (int i = 0; i < num_nodes; i++) {
_tree.upsert(i, i);
}
Node* node = _tree.find_node(0);
int count = 0;
while (node != nullptr) {
EXPECT_EQ(count, node->val());
node = node->next();
count++;
}
EXPECT_EQ(count, num_nodes);
}
void test_stable_nodes() {
using Node = RBTreeInt::RBNode;
RBTreeInt rbtree;
ResourceMark rm;
GrowableArray<Node*> a(10000);
for (int i = 0; i < 10000; i++) {
rbtree.upsert(i, i);
a.push(rbtree.find_node(i));
}
for (int i = 0; i < 2000; i++) {
int r = os::random() % 10000;
Node* to_delete = rbtree.find_node(r);
if (to_delete != nullptr && to_delete->_left != nullptr &&
to_delete->_right != nullptr) {
rbtree.remove(to_delete);
}
}
// After deleting, nodes should have been moved around but kept their values
for (int i = 0; i < 10000; i++) {
const Node* n = rbtree.find_node(i);
if (n != nullptr) {
EXPECT_EQ(a.at(i), n);
}
}
}
void test_stable_nodes_addresses() {
using Tree = RBTreeCHeap<int, void*, Cmp, mtOther>;
using Node = Tree::RBNode;
Tree rbtree;
for (int i = 0; i < 10000; i++) {
rbtree.upsert(i, nullptr);
Node* inserted_node = rbtree.find_node(i);
inserted_node->val() = inserted_node;
}
for (int i = 0; i < 2000; i++) {
int r = os::random() % 10000;
Node* to_delete = rbtree.find_node(r);
if (to_delete != nullptr && to_delete->_left != nullptr &&
to_delete->_right != nullptr) {
rbtree.remove(to_delete);
}
}
// After deleting, values should have remained consistant
rbtree.visit_in_order([&](Node* node) {
EXPECT_EQ(node, node->val());
});
}
#ifdef ASSERT
void test_fill_verify() {
RBTreeInt rbtree;
ResourceMark rm;
GrowableArray<int> allocations;
int size = 10000;
// Create random values
for (int i = 0; i < size; i++) {
int r = os::random() % size;
allocations.append(r);
}
// Insert ~half of the values
for (int i = 0; i < size; i++) {
int r = os::random();
if (r % 2 == 0) {
rbtree.upsert(allocations.at(i), allocations.at(i));
}
if (i % 100 == 0) {
verify_it(rbtree);
}
}
// Insert and remove randomly
for (int i = 0; i < size; i++) {
int r = os::random();
if (r % 2 == 0) {
rbtree.upsert(allocations.at(i), allocations.at(i));
} else {
rbtree.remove(allocations.at(i));
}
if (i % 100 == 0) {
verify_it(rbtree);
}
}
// Remove all elements
for (int i = 0; i < size; i++) {
rbtree.remove(allocations.at(i));
}
verify_it(rbtree);
EXPECT_EQ(rbtree.size(), 0UL);
}
void test_nodes_visited_once() {
constexpr size_t memory_size = 65536;
using Tree = RBTree<int, int, Cmp, ArrayAllocator<memory_size>>;
using Node = Tree::RBNode;
Tree tree;
int num_nodes = memory_size / sizeof(Node);
for (int i = 0; i < num_nodes; i++) {
tree.upsert(i, i);
}
Node* start = tree.find_node(0);
Node* node = start;
for (int i = 0; i < num_nodes; i++) {
EXPECT_EQ(tree._expected_visited, node->_visited);
node += 1;
}
verify_it(tree);
node = start;
for (int i = 0; i < num_nodes; i++) {
EXPECT_EQ(tree._expected_visited, node->_visited);
node += 1;
}
}
#endif // ASSERT
};
TEST_VM_F(RBTreeTest, InsertingDuplicatesResultsInOneValue) {
this->inserting_duplicates_results_in_one_value();
}
TEST_VM_F(RBTreeTest, RBTreeOughtNotLeak) {
this->rbtree_ought_not_leak();
}
TEST_VM_F(RBTreeTest, TestFind) {
this->test_find();
}
TEST_VM_F(RBTreeTest, TestVisitors) {
this->test_visitors();
}
TEST_VM_F(RBTreeTest, TestClosestLeq) {
this->test_closest_leq();
}
TEST_VM_F(RBTreeTest, NodePrev) {
this->test_node_prev();
}
TEST_VM_F(RBTreeTest, NodeNext) {
this->test_node_next();
}
TEST_VM_F(RBTreeTest, NodeStableTest) {
this->test_stable_nodes();
}
TEST_VM_F(RBTreeTest, NodeStableAddressTest) {
this->test_stable_nodes_addresses();
}
#ifdef ASSERT
TEST_VM_F(RBTreeTest, FillAndVerify) {
this->test_fill_verify();
}
TEST_VM_F(RBTreeTest, NodesVisitedOnce) {
this->test_nodes_visited_once();
}
TEST_VM_F(RBTreeTest, InsertRemoveVerify) {
constexpr int num_nodes = 100;
for (int n_t1 = 0; n_t1 < num_nodes; n_t1++) {
for (int n_t2 = 0; n_t2 < n_t1; n_t2++) {
RBTreeInt tree;
for (int i = 0; i < n_t1; i++) {
tree.upsert(i, i);
}
for (int i = 0; i < n_t2; i++) {
tree.remove(i);
}
verify_it(tree);
}
}
}
TEST_VM_F(RBTreeTest, VerifyItThroughStressTest) {
{ // Repeatedly verify a tree of moderate size
RBTreeInt rbtree;
constexpr int ten_thousand = 10000;
for (int i = 0; i < ten_thousand; i++) {
int r = os::random();
if (r % 2 == 0) {
rbtree.upsert(i, i);
} else {
rbtree.remove(i);
}
if (i % 100 == 0) {
verify_it(rbtree);
}
}
for (int i = 0; i < ten_thousand; i++) {
int r = os::random();
if (r % 2 == 0) {
rbtree.upsert(i, i);
} else {
rbtree.remove(i);
}
if (i % 100 == 0) {
verify_it(rbtree);
}
}
}
{ // Make a very large tree and verify at the end
struct Nothing {};
RBTreeCHeap<int, Nothing, Cmp, mtOther> rbtree;
constexpr int one_hundred_thousand = 100000;
for (int i = 0; i < one_hundred_thousand; i++) {
rbtree.upsert(i, Nothing());
}
verify_it(rbtree);
}
}
#endif // ASSERT