From 7d52f1e64d17d4a77dacc6074ead11e975eed9eb Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Sat, 8 Feb 2025 06:35:27 +0000 Subject: [PATCH] 8349525: RBTree: provide leftmost, rightmost, and a simple way to print trees Reviewed-by: jsjolen, cnorrbin --- src/hotspot/share/utilities/rbTree.hpp | 68 +++++-- src/hotspot/share/utilities/rbTree.inline.hpp | 74 +++++-- test/hotspot/gtest/utilities/test_rbtree.cpp | 186 +++++++++++++----- 3 files changed, 253 insertions(+), 75 deletions(-) diff --git a/src/hotspot/share/utilities/rbTree.hpp b/src/hotspot/share/utilities/rbTree.hpp index 9192c7b6011..576ee2f5bd3 100644 --- a/src/hotspot/share/utilities/rbTree.hpp +++ b/src/hotspot/share/utilities/rbTree.hpp @@ -30,6 +30,8 @@ #include "utilities/globalDefinitions.hpp" #include +class outputStream; + // COMPARATOR must have a static function `cmp(a,b)` which returns: // - an int < 0 when a < b // - an int == 0 when a == b @@ -43,7 +45,7 @@ template class RBTree { friend class RBTreeTest; - + typedef RBTree TreeType; private: ALLOCATOR _allocator; size_t _num_nodes; @@ -61,12 +63,13 @@ public: const K _key; V _value; - DEBUG_ONLY(bool _visited); + DEBUG_ONLY(mutable bool _visited); public: const K& key() const { return _key; } V& val() { return _value; } const V& val() const { return _value; } + void set_val(const V& v) { _value = v; } private: bool is_black() const { return (_parent & 0x1) != 0; } @@ -100,20 +103,22 @@ public: // Returns left child (now parent) RBNode* rotate_right(); - RBNode* prev(); + const RBNode* prev() const; - RBNode* next(); + const RBNode* next() const; #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 - }; + size_t& tree_depth, bool expect_visited) const; + #endif // ASSERT + }; // End: RBNode + + typedef TreeType::RBNode NodeType; private: RBNode* _root; - DEBUG_ONLY(bool _expected_visited); + DEBUG_ONLY(mutable bool _expected_visited); RBNode* allocate_node(const K& key, const V& val) { void* node_place = _allocator.allocate(sizeof(RBNode)); @@ -148,6 +153,8 @@ private: // Assumption: node has at most one child. Two children is handled in `remove()` void remove_from_tree(RBNode* node); + void print_node_on(outputStream* st, int depth, const NodeType* n) const; + public: NONCOPYABLE(RBTree); @@ -156,7 +163,7 @@ public: } ~RBTree() { this->remove_all(); } - size_t size() { return _num_nodes; } + size_t size() const { return _num_nodes; } // Inserts a node with the given k/v into the tree, // if the key already exist, the value is updated instead. @@ -257,19 +264,44 @@ public: RBNode* closest_leq(const K& key) { return const_cast( - static_cast*>(this)->closest_leq(key)); + static_cast(this)->closest_leq(key)); } RBNode* closest_gt(const K& key) { return const_cast( - static_cast*>(this)->closest_gt(key)); + static_cast(this)->closest_gt(key)); } RBNode* closest_geq(const K& key) { return const_cast( - static_cast*>(this)->closest_geq(key)); + static_cast(this)->closest_geq(key)); } + // Returns leftmost node, nullptr if tree is empty. + // If COMPARATOR::cmp(a, b) behaves canonically (positive value for a > b), this will the smallest key value. + const RBNode* leftmost() const { + RBNode* n = _root, *n2 = nullptr; + while (n != nullptr) { + n2 = n; + n = n->_left; + } + return n2; + } + + // Returns rightmost node, nullptr if tree is empty. + // If COMPARATOR::cmp(a, b) behaves canonically (positive value for a > b), this will the largest key value. + const RBNode* rightmost() const { + RBNode* n = _root, *n2 = nullptr; + while (n != nullptr) { + n2 = n; + n = n->_right; + } + return n2; + } + + RBNode* leftmost() { return const_cast(static_cast(this)->leftmost()); } + RBNode* rightmost() { return const_cast(static_cast(this)->rightmost()); } + struct Range { RBNode* start; RBNode* end; @@ -280,7 +312,7 @@ public: // 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) { + Range find_enclosing_range(K key) const { RBNode* start = closest_leq(key); RBNode* end = closest_gt(key); return Range(start, end); @@ -291,7 +323,7 @@ public: RBNode* find_node(const K& key) { return const_cast( - static_cast*>(this)->find_node(key)); + static_cast(this)->find_node(key)); } // Finds the value associated with the key @@ -311,12 +343,12 @@ public: // 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); + void visit_range_in_order(const K& from, const K& to, F f) const; -#ifdef ASSERT // Verifies that the tree is correct and holds rb-properties - void verify_self(); -#endif // ASSERT + void verify_self() const NOT_DEBUG({}); + + void print_on(outputStream* st) const; }; diff --git a/src/hotspot/share/utilities/rbTree.inline.hpp b/src/hotspot/share/utilities/rbTree.inline.hpp index 95aeb34913f..22a828ee126 100644 --- a/src/hotspot/share/utilities/rbTree.inline.hpp +++ b/src/hotspot/share/utilities/rbTree.inline.hpp @@ -25,8 +25,10 @@ #ifndef SHARE_UTILITIES_RBTREE_INLINE_HPP #define SHARE_UTILITIES_RBTREE_INLINE_HPP +#include "metaprogramming/enableIf.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/rbTree.hpp" @@ -87,9 +89,9 @@ RBTree::RBNode::rotate_right() { } template -inline typename RBTree::RBNode* -RBTree::RBNode::prev() { - RBNode* node = this; +inline const typename RBTree::RBNode* +RBTree::RBNode::prev() const { + const RBNode* node = this; if (_left != nullptr) { // right subtree exists node = _left; while (node->_right != nullptr) { @@ -105,9 +107,9 @@ RBTree::RBNode::prev() { } template -inline typename RBTree::RBNode* -RBTree::RBNode::next() { - RBNode* node = this; +inline const typename RBTree::RBNode* +RBTree::RBNode::next() const { + const RBNode* node = this; if (_right != nullptr) { // right subtree exists node = _right; while (node->_left != nullptr) { @@ -126,7 +128,7 @@ RBTree::RBNode::next() { 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) { + size_t& tree_depth, bool expect_visited) const { assert(expect_visited != _visited, "node already visited"); _visited = !_visited; @@ -479,12 +481,13 @@ inline void RBTree::remove(RBNode* node) { template template inline void RBTree::visit_in_order(F f) const { - RBNode* to_visit[64]; + const RBNode* to_visit[64]; int stack_idx = 0; - RBNode* head = _root; + const RBNode* head = _root; while (stack_idx > 0 || head != nullptr) { while (head != nullptr) { to_visit[stack_idx++] = head; + assert(stack_idx <= (int)(sizeof(to_visit)/sizeof(to_visit[0])), "stack too deep"); head = head->_left; } head = to_visit[--stack_idx]; @@ -495,11 +498,11 @@ inline void RBTree::visit_in_order(F f) const { template template -inline void RBTree::visit_range_in_order(const K& from, const K& to, F f) { +inline void RBTree::visit_range_in_order(const K& from, const K& to, F f) const { assert(COMPARATOR::cmp(from, to) <= 0, "from must be less or equal to to"); - RBNode* curr = closest_geq(from); + const RBNode* curr = closest_geq(from); if (curr == nullptr) return; - RBNode* end = closest_geq(to); + const RBNode* const end = closest_geq(to); while (curr != nullptr && curr != end) { f(curr); @@ -509,7 +512,7 @@ inline void RBTree::visit_range_in_order(const K& f #ifdef ASSERT template -inline void RBTree::verify_self() { +inline void RBTree::verify_self() const { if (_root == nullptr) { assert(_num_nodes == 0, "rbtree has nodes but no root"); return; @@ -538,4 +541,49 @@ inline void RBTree::verify_self() { } #endif // ASSERT +template ::value), + ENABLE_IF(std::is_signed::value)> +void print_T(outputStream* st, T x) { + st->print(INT64_FORMAT, (int64_t)x); +} + +template ::value), + ENABLE_IF(std::is_unsigned::value)> +void print_T(outputStream* st, T x) { + st->print(UINT64_FORMAT, (uint64_t)x); +} + +template ::value)> +void print_T(outputStream* st, T x) { + st->print(PTR_FORMAT, p2i(x)); +} + +template +void RBTree::print_node_on(outputStream* st, int depth, const NodeType* n) const { + st->print("(%d)", depth); + st->sp(1 + depth * 2); + st->print("@" PTR_FORMAT ": [", p2i(n)); + print_T(st, n->key()); + st->print("] = "); + print_T(st, n->val()); + st->cr(); + depth++; + if (n->_right != nullptr) { + print_node_on(st, depth, n->_right); + } + if (n->_left != nullptr) { + print_node_on(st, depth, n->_left); + } +} + +template +void RBTree::print_on(outputStream* st) const { + if (_root != nullptr) { + print_node_on(st, 0, _root); + } +} + #endif // SHARE_UTILITIES_RBTREE_INLINE_HPP diff --git a/test/hotspot/gtest/utilities/test_rbtree.cpp b/test/hotspot/gtest/utilities/test_rbtree.cpp index c1be34b08d6..41b06e0af5f 100644 --- a/test/hotspot/gtest/utilities/test_rbtree.cpp +++ b/test/hotspot/gtest/utilities/test_rbtree.cpp @@ -72,13 +72,6 @@ struct ArrayAllocator { void free(void* ptr) { } }; -#ifdef ASSERT - template - void verify_it(RBTree& t) { - t.verify_self(); - } -#endif // ASSERT - using RBTreeInt = RBTreeCHeap; public: @@ -86,6 +79,7 @@ public: constexpr int up_to = 10; GrowableArrayCHeap nums_seen(up_to, up_to, 0); RBTreeInt rbtree; + const RBTreeInt& rbtree_const = rbtree; for (int i = 0; i < up_to; i++) { rbtree.upsert(i, i); @@ -95,7 +89,7 @@ public: rbtree.upsert(i, i); } - rbtree.visit_in_order([&](RBTreeInt::RBNode* node) { + rbtree_const.visit_in_order([&](const RBTreeInt::RBNode* node) { nums_seen.at(node->key())++; }); for (int i = 0; i < up_to; i++) { @@ -173,22 +167,23 @@ public: void test_visitors() { { // Tests with 'default' ordering (ascending) RBTreeInt rbtree; + const RBTreeInt& rbtree_const = rbtree; using Node = RBTreeInt::RBNode; - rbtree.visit_range_in_order(0, 100, [&](Node* x) { + rbtree_const.visit_range_in_order(0, 100, [&](const 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) { + rbtree_const.visit_range_in_order(0, 100, [&](const Node* x) { count++; }); EXPECT_EQ(1, count); count = 0; - rbtree.visit_in_order([&](Node* x) { + rbtree_const.visit_in_order([&](const Node* x) { count++; }); EXPECT_EQ(1, count); @@ -198,20 +193,20 @@ public: rbtree.upsert(101, 0); rbtree.upsert(-1, 0); count = 0; - rbtree.visit_range_in_order(0, 100, [&](Node* x) { + rbtree_const.visit_range_in_order(0, 100, [&](const Node* x) { count++; }); EXPECT_EQ(1, count); count = 0; - rbtree.visit_in_order([&](Node* x) { + rbtree_const.visit_in_order([&](const 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) { + rbtree_const.visit_range_in_order(0, 0, [&](const Node* x) { EXPECT_TRUE(false) << "Empty visiting range should not visit any node"; }); @@ -222,7 +217,7 @@ public: ResourceMark rm; GrowableArray seen; - rbtree.visit_range_in_order(0, 10, [&](Node* x) { + rbtree_const.visit_range_in_order(0, 10, [&](const Node* x) { seen.push(x->key()); }); EXPECT_EQ(10, seen.length()); @@ -231,7 +226,7 @@ public: } seen.clear(); - rbtree.visit_in_order([&](Node* x) { + rbtree_const.visit_in_order([&](const Node* x) { seen.push(x->key()); }); EXPECT_EQ(11, seen.length()); @@ -240,7 +235,7 @@ public: } seen.clear(); - rbtree.visit_range_in_order(10, 12, [&](Node* x) { + rbtree_const.visit_range_in_order(10, 12, [&](const Node* x) { seen.push(x->key()); }); EXPECT_EQ(1, seen.length()); @@ -248,6 +243,7 @@ public: } { // Test with descending ordering RBTreeCHeap rbtree; + const RBTreeCHeap& rbtree_const = rbtree; using Node = RBTreeCHeap::RBNode; for (int i = 0; i < 10; i++) { @@ -255,7 +251,7 @@ public: } ResourceMark rm; GrowableArray seen; - rbtree.visit_range_in_order(9, -1, [&](Node* x) { + rbtree_const.visit_range_in_order(9, -1, [&](const Node* x) { seen.push(x->key()); }); EXPECT_EQ(10, seen.length()); @@ -264,7 +260,7 @@ public: } seen.clear(); - rbtree.visit_in_order([&](Node* x) { + rbtree_const.visit_in_order([&](const Node* x) { seen.push(x->key()); }); EXPECT_EQ(10, seen.length()); @@ -278,36 +274,38 @@ public: using Node = RBTreeInt::RBNode; { RBTreeInt rbtree; - Node* n = rbtree.closest_leq(0); + const RBTreeInt& rbtree_const = rbtree; + const Node* n = rbtree_const.closest_leq(0); EXPECT_EQ(nullptr, n); rbtree.upsert(0, 0); - n = rbtree.closest_leq(0); + n = rbtree_const.closest_leq(0); EXPECT_EQ(0, n->key()); rbtree.upsert(-1, -1); - n = rbtree.closest_leq(0); + n = rbtree_const.closest_leq(0); EXPECT_EQ(0, n->key()); rbtree.upsert(6, 0); - n = rbtree.closest_leq(6); + n = rbtree_const.closest_leq(6); EXPECT_EQ(6, n->key()); - n = rbtree.closest_leq(-2); + n = rbtree_const.closest_leq(-2); EXPECT_EQ(nullptr, n); } } void test_node_prev() { - RBTreeInt _tree; + RBTreeInt rbtree; + const RBTreeInt& rbtree_const = rbtree; using Node = RBTreeInt::RBNode; constexpr int num_nodes = 100; for (int i = num_nodes; i > 0; i--) { - _tree.upsert(i, i); + rbtree.upsert(i, i); } - Node* node = _tree.find_node(num_nodes); + const Node* node = rbtree_const.find_node(num_nodes); int count = num_nodes; while (node != nullptr) { EXPECT_EQ(count, node->val()); @@ -318,16 +316,17 @@ public: EXPECT_EQ(count, 0); } - void test_node_next() { - RBTreeInt _tree; + void test_node_next() { + RBTreeInt rbtree; + const RBTreeInt& rbtree_const = rbtree; using Node = RBTreeInt::RBNode; constexpr int num_nodes = 100; for (int i = 0; i < num_nodes; i++) { - _tree.upsert(i, i); + rbtree.upsert(i, i); } - Node* node = _tree.find_node(0); + const Node* node = rbtree_const.find_node(0); int count = 0; while (node != nullptr) { EXPECT_EQ(count, node->val()); @@ -339,8 +338,9 @@ public: } void test_stable_nodes() { - using Node = RBTreeInt::RBNode; RBTreeInt rbtree; + const RBTreeInt& rbtree_const = rbtree; + using Node = RBTreeInt::RBNode; ResourceMark rm; GrowableArray a(10000); for (int i = 0; i < 10000; i++) { @@ -359,7 +359,7 @@ public: // 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); + const Node* n = rbtree_const.find_node(i); if (n != nullptr) { EXPECT_EQ(a.at(i), n); } @@ -386,15 +386,48 @@ public: } // After deleting, values should have remained consistant - rbtree.visit_in_order([&](Node* node) { + rbtree.visit_in_order([&](const Node* node) { EXPECT_EQ(node, node->val()); }); } + void test_leftmost_rightmost() { + using Node = RBTreeInt::RBNode; + for (int i = 0; i < 10; i++) { + RBTreeInt rbtree; + const RBTreeInt& rbtree_const = rbtree; + int max = 0, min = INT_MAX; + for (int j = 0; j < 10; j++) { + if (j == 0) { + ASSERT_EQ(rbtree_const.leftmost(), (const Node*)nullptr); + ASSERT_EQ(rbtree_const.rightmost(), (const Node*)nullptr); + } else { + ASSERT_EQ(rbtree_const.rightmost()->key(), max); + ASSERT_EQ(rbtree_const.rightmost()->val(), max); + ASSERT_EQ(rbtree_const.leftmost()->key(), min); + ASSERT_EQ(rbtree_const.leftmost()->val(), min); + ASSERT_EQ(rbtree_const.rightmost(), rbtree.rightmost()); + ASSERT_EQ(rbtree_const.leftmost(), rbtree.leftmost()); + } + const int r = os::random(); + rbtree.upsert(r, r); + min = MIN2(min, r); + max = MAX2(max, r); + } + // Explicitly test non-const variants + Node* n = rbtree.rightmost(); + ASSERT_EQ(n->key(), max); + n->set_val(1); + n = rbtree.leftmost(); + ASSERT_EQ(n->key(), min); + n->set_val(1); + } + } + #ifdef ASSERT void test_fill_verify() { RBTreeInt rbtree; - + const RBTreeInt& rbtree_const = rbtree; ResourceMark rm; GrowableArray allocations; @@ -412,7 +445,7 @@ public: rbtree.upsert(allocations.at(i), allocations.at(i)); } if (i % 100 == 0) { - verify_it(rbtree); + rbtree_const.verify_self(); } } @@ -425,7 +458,7 @@ public: rbtree.remove(allocations.at(i)); } if (i % 100 == 0) { - verify_it(rbtree); + rbtree_const.verify_self(); } } @@ -434,8 +467,8 @@ public: rbtree.remove(allocations.at(i)); } - verify_it(rbtree); - EXPECT_EQ(rbtree.size(), 0UL); + rbtree.verify_self(); + EXPECT_EQ(rbtree_const.size(), 0UL); } void test_nodes_visited_once() { @@ -458,7 +491,7 @@ public: node += 1; } - verify_it(tree); + tree.verify_self(); node = start; for (int i = 0; i < num_nodes; i++) { @@ -507,6 +540,71 @@ TEST_VM_F(RBTreeTest, NodeStableAddressTest) { this->test_stable_nodes_addresses(); } +TEST_VM_F(RBTreeTest, LeftMostRightMost) { + this->test_leftmost_rightmost(); +} + +struct PtrCmp { + static int cmp(const void* a, const void* b) { + const uintptr_t ai = p2u(a); + const uintptr_t bi = p2u(b); + return ai == bi ? 0 : (ai > bi ? 1 : -1); + } +}; + +TEST_VM(RBTreeTestNonFixture, TestPrintPointerTree) { + typedef RBTreeCHeap TreeType; + TreeType tree; +#ifdef _LP64 + const void* const p1 = (const void*) 0x800000000ULL; + const char* const s1 = "[0x0000000800000000] = 1"; + const void* const p2 = (const void*) 0xDEADBEEF0ULL; + const char* const s2 = "[0x0000000deadbeef0] = 2"; + const void* const p3 = (const void*) 0x7f223fba0ULL; + const char* const s3 = "[0x00000007f223fba0] = 3"; +#else + const void* const p1 = (const void*) 0x80000000ULL; + const char* const s1 = "[0x80000000] = 1"; + const void* const p2 = (const void*) 0xDEADBEEFLL; + const char* const s2 = "[0xdeadbeef] = 2"; + const void* const p3 = (const void*) 0x7f223fbaULL; + const char* const s3 = "[0x7f223fba] = 3"; +#endif + tree.upsert(p1, 1); + tree.upsert(p2, 2); + tree.upsert(p3, 3); + stringStream ss; + tree.print_on(&ss); + const char* const N = nullptr; + ASSERT_NE(strstr(ss.base(), s1), N); + ASSERT_NE(strstr(ss.base(), s2), N); + ASSERT_NE(strstr(ss.base(), s3), N); +} + +struct IntCmp { + static int cmp(int a, int b) { return a == b ? 0 : (a > b ? 1 : -1); } +}; + +TEST_VM(RBTreeTestNonFixture, TestPrintIntegerTree) { + typedef RBTree > TreeType; + TreeType tree; + const int i1 = 82924; + const char* const s1 = "[82924] = 1"; + const int i2 = -13591; + const char* const s2 = "[-13591] = 2"; + const int i3 = 0; + const char* const s3 = "[0] = 3"; + tree.upsert(i1, 1); + tree.upsert(i2, 2); + tree.upsert(i3, 3); + stringStream ss; + tree.print_on(&ss); + const char* const N = nullptr; + ASSERT_NE(strstr(ss.base(), s1), N); + ASSERT_NE(strstr(ss.base(), s2), N); + ASSERT_NE(strstr(ss.base(), s3), N); +} + #ifdef ASSERT TEST_VM_F(RBTreeTest, FillAndVerify) { this->test_fill_verify(); @@ -527,7 +625,7 @@ TEST_VM_F(RBTreeTest, InsertRemoveVerify) { for (int i = 0; i < n_t2; i++) { tree.remove(i); } - verify_it(tree); + tree.verify_self(); } } } @@ -544,7 +642,7 @@ TEST_VM_F(RBTreeTest, VerifyItThroughStressTest) { rbtree.remove(i); } if (i % 100 == 0) { - verify_it(rbtree); + rbtree.verify_self(); } } for (int i = 0; i < ten_thousand; i++) { @@ -555,7 +653,7 @@ TEST_VM_F(RBTreeTest, VerifyItThroughStressTest) { rbtree.remove(i); } if (i % 100 == 0) { - verify_it(rbtree); + rbtree.verify_self(); } } } @@ -566,7 +664,7 @@ TEST_VM_F(RBTreeTest, VerifyItThroughStressTest) { for (int i = 0; i < one_hundred_thousand; i++) { rbtree.upsert(i, Nothing()); } - verify_it(rbtree); + rbtree.verify_self(); } }