diff --git a/src/hotspot/share/utilities/rbTree.hpp b/src/hotspot/share/utilities/rbTree.hpp new file mode 100644 index 00000000000..9192c7b6011 --- /dev/null +++ b/src/hotspot/share/utilities/rbTree.hpp @@ -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 + +// 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 +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::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( + static_cast*>(this)->closest_leq(key)); + } + + RBNode* closest_gt(const K& key) { + return const_cast( + static_cast*>(this)->closest_gt(key)); + } + + RBNode* closest_geq(const K& key) { + return const_cast( + static_cast*>(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( + static_cast*>(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 + 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 + 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 +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 +using RBTreeCHeap = RBTree>; + +#endif // SHARE_UTILITIES_RBTREE_HPP diff --git a/src/hotspot/share/utilities/rbTree.inline.hpp b/src/hotspot/share/utilities/rbTree.inline.hpp new file mode 100644 index 00000000000..95aeb34913f --- /dev/null +++ b/src/hotspot/share/utilities/rbTree.inline.hpp @@ -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 +inline void RBTree::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 +inline typename RBTree::RBNode* +RBTree::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 +inline typename RBTree::RBNode* +RBTree::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 +inline typename RBTree::RBNode* +RBTree::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 +inline typename RBTree::RBNode* +RBTree::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 +inline void RBTree::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 +inline const typename RBTree::RBNode* +RBTree::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 +inline typename RBTree::RBNode* +RBTree::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 +inline void RBTree::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 +inline void RBTree::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 +inline void RBTree::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 +inline void RBTree::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 +template +inline void RBTree::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 +template +inline void RBTree::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 +inline void RBTree::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 diff --git a/test/hotspot/gtest/utilities/test_rbtree.cpp b/test/hotspot/gtest/utilities/test_rbtree.cpp new file mode 100644 index 00000000000..c1be34b08d6 --- /dev/null +++ b/test/hotspot/gtest/utilities/test_rbtree.cpp @@ -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 +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 + void verify_it(RBTree& t) { + t.verify_self(); + } +#endif // ASSERT + +using RBTreeInt = RBTreeCHeap; + +public: + void inserting_duplicates_results_in_one_value() { + constexpr int up_to = 10; + GrowableArrayCHeap 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 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 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 rbtree; + using Node = RBTreeCHeap::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 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 rbtree; + using Node = RBTreeCHeap::RBNode; + + for (int i = 0; i < 10; i++) { + rbtree.upsert(i, 0); + } + ResourceMark rm; + GrowableArray 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 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; + 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 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>; + 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 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