1 /* 2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 #include "precompiled.hpp" 26 #include "memory/allocation.hpp" 27 #include "nmt/memTag.hpp" 28 #include "nmt/nmtNativeCallStackStorage.hpp" 29 #include "nmt/vmatree.hpp" 30 #include "runtime/os.hpp" 31 #include "unittest.hpp" 32 33 using Tree = VMATree; 34 using Node = Tree::TreapNode; 35 using NCS = NativeCallStackStorage; 36 37 class NMTVMATreeTest : public testing::Test { 38 public: 39 NCS ncs; 40 constexpr static const int si_len = 2; 41 NCS::StackIndex si[si_len]; 42 NativeCallStack stacks[si_len]; 43 44 NMTVMATreeTest() : ncs(true) { 45 stacks[0] = make_stack(0xA); 46 stacks[1] = make_stack(0xB); 47 si[0] = ncs.push(stacks[0]); 48 si[1] = ncs.push(stacks[0]); 49 } 50 51 // Utilities 52 53 VMATree::TreapNode* treap_root(VMATree& tree) { 54 return tree._tree._root; 55 } 56 57 VMATree::VMATreap& treap(VMATree& tree) { 58 return tree._tree; 59 } 60 61 VMATree::TreapNode* find(VMATree::VMATreap& treap, const VMATree::position key) { 62 return treap.find(treap._root, key); 63 } 64 65 NativeCallStack make_stack(size_t a) { 66 NativeCallStack stack((address*)&a, 1); 67 return stack; 68 } 69 70 VMATree::StateType in_type_of(VMATree::TreapNode* x) { 71 return x->val().in.type(); 72 } 73 74 VMATree::StateType out_type_of(VMATree::TreapNode* x) { 75 return x->val().out.type(); 76 } 77 78 int count_nodes(Tree& tree) { 79 int count = 0; 80 treap(tree).visit_in_order([&](Node* x) { 81 ++count; 82 }); 83 return count; 84 } 85 86 // Tests 87 // Adjacent reservations are merged if the properties match. 88 void adjacent_2_nodes(const VMATree::RegionData& rd) { 89 Tree tree; 90 for (int i = 0; i < 10; i++) { 91 tree.reserve_mapping(i * 100, 100, rd); 92 } 93 EXPECT_EQ(2, count_nodes(tree)); 94 95 // Reserving the exact same space again should result in still having only 2 nodes 96 for (int i = 0; i < 10; i++) { 97 tree.reserve_mapping(i * 100, 100, rd); 98 } 99 EXPECT_EQ(2, count_nodes(tree)); 100 101 // Do it backwards instead. 102 Tree tree2; 103 for (int i = 9; i >= 0; i--) { 104 tree2.reserve_mapping(i * 100, 100, rd); 105 } 106 EXPECT_EQ(2, count_nodes(tree2)); 107 } 108 109 // After removing all ranges we should be left with an entirely empty tree 110 void remove_all_leaves_empty_tree(const VMATree::RegionData& rd) { 111 Tree tree; 112 tree.reserve_mapping(0, 100 * 10, rd); 113 for (int i = 0; i < 10; i++) { 114 tree.release_mapping(i * 100, 100); 115 } 116 EXPECT_EQ(nullptr, treap_root(tree)); 117 118 // Other way around 119 tree.reserve_mapping(0, 100 * 10, rd); 120 for (int i = 9; i >= 0; i--) { 121 tree.release_mapping(i * 100, 100); 122 } 123 EXPECT_EQ(nullptr, treap_root(tree)); 124 } 125 126 // Committing in a whole reserved range results in 2 nodes 127 void commit_whole(const VMATree::RegionData& rd) { 128 Tree tree; 129 tree.reserve_mapping(0, 100 * 10, rd); 130 for (int i = 0; i < 10; i++) { 131 tree.commit_mapping(i * 100, 100, rd); 132 } 133 treap(tree).visit_in_order([&](Node* x) { 134 VMATree::StateType in = in_type_of(x); 135 VMATree::StateType out = out_type_of(x); 136 EXPECT_TRUE((in == VMATree::StateType::Released && out == VMATree::StateType::Committed) || 137 (in == VMATree::StateType::Committed && out == VMATree::StateType::Released)); 138 }); 139 EXPECT_EQ(2, count_nodes(tree)); 140 } 141 142 // Committing in middle of reservation ends with a sequence of 4 nodes 143 void commit_middle(const VMATree::RegionData& rd) { 144 Tree tree; 145 tree.reserve_mapping(0, 100, rd); 146 tree.commit_mapping(50, 25, rd); 147 148 size_t found[16]; 149 size_t wanted[4] = {0, 50, 75, 100}; 150 auto exists = [&](size_t x) { 151 for (int i = 0; i < 4; i++) { 152 if (wanted[i] == x) return true; 153 } 154 return false; 155 }; 156 157 int i = 0; 158 treap(tree).visit_in_order([&](Node* x) { 159 if (i < 16) { 160 found[i] = x->key(); 161 } 162 i++; 163 }); 164 165 ASSERT_EQ(4, i) << "0 - 50 - 75 - 100 nodes expected"; 166 EXPECT_TRUE(exists(found[0])); 167 EXPECT_TRUE(exists(found[1])); 168 EXPECT_TRUE(exists(found[2])); 169 EXPECT_TRUE(exists(found[3])); 170 }; 171 }; 172 173 174 175 TEST_VM_F(NMTVMATreeTest, OverlappingReservationsResultInTwoNodes) { 176 VMATree::RegionData rd{si[0], mtTest}; 177 Tree tree; 178 for (int i = 99; i >= 0; i--) { 179 tree.reserve_mapping(i * 100, 101, rd); 180 } 181 EXPECT_EQ(2, count_nodes(tree)); 182 } 183 184 // Low-level tests inspecting the state of the tree. 185 TEST_VM_F(NMTVMATreeTest, LowLevel) { 186 adjacent_2_nodes(VMATree::empty_regiondata); 187 remove_all_leaves_empty_tree(VMATree::empty_regiondata); 188 commit_middle(VMATree::empty_regiondata); 189 commit_whole(VMATree::empty_regiondata); 190 191 VMATree::RegionData rd{si[0], mtTest }; 192 adjacent_2_nodes(rd); 193 remove_all_leaves_empty_tree(rd); 194 commit_middle(rd); 195 commit_whole(rd); 196 197 { // Identical operation but different metadata should not merge 198 Tree tree; 199 VMATree::RegionData rd{si[0], mtTest }; 200 VMATree::RegionData rd2{si[1], mtNMT }; 201 tree.reserve_mapping(0, 100, rd); 202 tree.reserve_mapping(100, 100, rd2); 203 204 EXPECT_EQ(3, count_nodes(tree)); 205 int found_nodes = 0; 206 } 207 208 { // Reserving after commit should overwrite commit 209 Tree tree; 210 VMATree::RegionData rd{si[0], mtTest }; 211 VMATree::RegionData rd2{si[1], mtNMT }; 212 tree.commit_mapping(50, 50, rd2); 213 tree.reserve_mapping(0, 100, rd); 214 treap(tree).visit_in_order([&](Node* x) { 215 EXPECT_TRUE(x->key() == 0 || x->key() == 100); 216 if (x->key() == 0) { 217 EXPECT_EQ(x->val().out.regiondata().mem_tag, mtTest); 218 } 219 }); 220 221 EXPECT_EQ(2, count_nodes(tree)); 222 } 223 224 { // Split a reserved region into two different reserved regions 225 Tree tree; 226 VMATree::RegionData rd{si[0], mtTest }; 227 VMATree::RegionData rd2{si[1], mtNMT }; 228 VMATree::RegionData rd3{si[0], mtNone }; 229 tree.reserve_mapping(0, 100, rd); 230 tree.reserve_mapping(0, 50, rd2); 231 tree.reserve_mapping(50, 50, rd3); 232 233 EXPECT_EQ(3, count_nodes(tree)); 234 } 235 { // One big reserve + release leaves an empty tree 236 Tree::RegionData rd{si[0], mtNMT}; 237 Tree tree; 238 tree.reserve_mapping(0, 500000, rd); 239 tree.release_mapping(0, 500000); 240 241 EXPECT_EQ(nullptr, treap_root(tree)); 242 } 243 244 { // A committed region inside of/replacing a reserved region 245 // should replace the reserved region's metadata. 246 Tree::RegionData rd{si[0], mtNMT}; 247 VMATree::RegionData rd2{si[1], mtTest}; 248 Tree tree; 249 tree.reserve_mapping(0, 100, rd); 250 tree.commit_mapping(0, 100, rd2); 251 treap(tree).visit_range_in_order(0, 99999, [&](Node* x) { 252 if (x->key() == 0) { 253 EXPECT_EQ(mtTest, x->val().out.regiondata().mem_tag); 254 } 255 if (x->key() == 100) { 256 EXPECT_EQ(mtTest, x->val().in.regiondata().mem_tag); 257 } 258 }); 259 } 260 261 { // Attempting to reserve or commit an empty region should not change the tree. 262 Tree tree; 263 Tree::RegionData rd{si[0], mtNMT}; 264 tree.reserve_mapping(0, 0, rd); 265 EXPECT_EQ(nullptr, treap_root(tree)); 266 tree.commit_mapping(0, 0, rd); 267 EXPECT_EQ(nullptr, treap_root(tree)); 268 } 269 } 270 271 // Tests for summary accounting 272 TEST_VM_F(NMTVMATreeTest, SummaryAccounting) { 273 { // Fully enclosed re-reserving works correctly. 274 Tree::RegionData rd(NCS::StackIndex(), mtTest); 275 Tree::RegionData rd2(NCS::StackIndex(), mtNMT); 276 Tree tree; 277 VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); 278 VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 279 EXPECT_EQ(100, diff.reserve); 280 all_diff = tree.reserve_mapping(50, 25, rd2); 281 diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 282 VMATree::SingleDiff diff2 = all_diff.tag[NMTUtil::tag_to_index(mtNMT)]; 283 EXPECT_EQ(-25, diff.reserve); 284 EXPECT_EQ(25, diff2.reserve); 285 } 286 { // Fully release reserved mapping 287 Tree::RegionData rd(NCS::StackIndex(), mtTest); 288 Tree tree; 289 VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); 290 VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 291 EXPECT_EQ(100, diff.reserve); 292 all_diff = tree.release_mapping(0, 100); 293 diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 294 EXPECT_EQ(-100, diff.reserve); 295 } 296 { // Convert some of a released mapping to a committed one 297 Tree::RegionData rd(NCS::StackIndex(), mtTest); 298 Tree tree; 299 VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); 300 VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 301 EXPECT_EQ(diff.reserve, 100); 302 all_diff = tree.commit_mapping(0, 100, rd); 303 diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 304 EXPECT_EQ(0, diff.reserve); 305 EXPECT_EQ(100, diff.commit); 306 } 307 { // Adjacent reserved mappings with same type 308 Tree::RegionData rd(NCS::StackIndex(), mtTest); 309 Tree tree; 310 VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); 311 VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 312 EXPECT_EQ(diff.reserve, 100); 313 all_diff = tree.reserve_mapping(100, 100, rd); 314 diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 315 EXPECT_EQ(100, diff.reserve); 316 } 317 { // Adjacent reserved mappings with different flags 318 Tree::RegionData rd(NCS::StackIndex(), mtTest); 319 Tree::RegionData rd2(NCS::StackIndex(), mtNMT); 320 Tree tree; 321 VMATree::SummaryDiff all_diff = tree.reserve_mapping(0, 100, rd); 322 VMATree::SingleDiff diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 323 EXPECT_EQ(diff.reserve, 100); 324 all_diff = tree.reserve_mapping(100, 100, rd2); 325 diff = all_diff.tag[NMTUtil::tag_to_index(mtTest)]; 326 EXPECT_EQ(0, diff.reserve); 327 diff = all_diff.tag[NMTUtil::tag_to_index(mtNMT)]; 328 EXPECT_EQ(100, diff.reserve); 329 } 330 331 { // A commit with two previous commits inside of it should only register 332 // the new memory in the commit diff. 333 Tree tree; 334 Tree::RegionData rd(NCS::StackIndex(), mtTest); 335 tree.commit_mapping(128, 128, rd); 336 tree.commit_mapping(512, 128, rd); 337 VMATree::SummaryDiff diff = tree.commit_mapping(0, 1024, rd); 338 EXPECT_EQ(768, diff.tag[NMTUtil::tag_to_index(mtTest)].commit); 339 EXPECT_EQ(768, diff.tag[NMTUtil::tag_to_index(mtTest)].reserve); 340 } 341 } 342 343 // Exceedingly simple tracker for page-granular allocations 344 // Use it for testing consistency with VMATree. 345 struct SimpleVMATracker : public CHeapObj<mtTest> { 346 const size_t page_size = 4096; 347 enum Kind { Reserved, Committed, Free }; 348 struct Info { 349 Kind kind; 350 MemTag mem_tag; 351 NativeCallStack stack; 352 Info() : kind(Free), mem_tag(mtNone), stack() {} 353 354 Info(Kind kind, NativeCallStack stack, MemTag mem_tag) 355 : kind(kind), mem_tag(mem_tag), stack(stack) {} 356 357 bool eq(Info other) { 358 return kind == other.kind && stack.equals(other.stack); 359 } 360 }; 361 // Page (4KiB) granular array 362 static constexpr const size_t num_pages = 1024 * 4; 363 Info pages[num_pages]; 364 365 SimpleVMATracker() 366 : pages() { 367 for (size_t i = 0; i < num_pages; i++) { 368 pages[i] = Info(); 369 } 370 } 371 372 VMATree::SummaryDiff do_it(Kind kind, size_t start, size_t size, NativeCallStack stack, MemTag mem_tag) { 373 assert(is_aligned(size, page_size) && is_aligned(start, page_size), "page alignment"); 374 375 VMATree::SummaryDiff diff; 376 const size_t page_count = size / page_size; 377 const size_t start_idx = start / page_size; 378 const size_t end_idx = start_idx + page_count; 379 assert(end_idx < SimpleVMATracker::num_pages, ""); 380 381 Info new_info(kind, stack, mem_tag); 382 for (size_t i = start_idx; i < end_idx; i++) { 383 Info& old_info = pages[i]; 384 385 // Register diff 386 if (old_info.kind == Reserved) { 387 diff.tag[(int)old_info.mem_tag].reserve -= page_size; 388 } else if (old_info.kind == Committed) { 389 diff.tag[(int)old_info.mem_tag].reserve -= page_size; 390 diff.tag[(int)old_info.mem_tag].commit -= page_size; 391 } 392 393 if (kind == Reserved) { 394 diff.tag[(int)new_info.mem_tag].reserve += page_size; 395 } else if (kind == Committed) { 396 diff.tag[(int)new_info.mem_tag].reserve += page_size; 397 diff.tag[(int)new_info.mem_tag].commit += page_size; 398 } 399 // Overwrite old one with new 400 pages[i] = new_info; 401 } 402 return diff; 403 } 404 405 VMATree::SummaryDiff reserve(size_t start, size_t size, NativeCallStack stack, MemTag mem_tag) { 406 return do_it(Reserved, start, size, stack, mem_tag); 407 } 408 409 VMATree::SummaryDiff commit(size_t start, size_t size, NativeCallStack stack, MemTag mem_tag) { 410 return do_it(Committed, start, size, stack, mem_tag); 411 } 412 413 VMATree::SummaryDiff release(size_t start, size_t size) { 414 return do_it(Free, start, size, NativeCallStack(), mtNone); 415 } 416 }; 417 418 constexpr const size_t SimpleVMATracker::num_pages; 419 420 TEST_VM_F(NMTVMATreeTest, TestConsistencyWithSimpleTracker) { 421 // In this test we use ASSERT macros from gtest instead of EXPECT 422 // as any error will propagate and become larger as the test progresses. 423 SimpleVMATracker* tr = new SimpleVMATracker(); 424 const size_t page_size = tr->page_size; 425 VMATree tree; 426 NCS ncss(true); 427 constexpr const int candidates_len_tags = 4; 428 constexpr const int candidates_len_stacks = 2; 429 430 NativeCallStack candidate_stacks[candidates_len_stacks] = { 431 make_stack(0xA), 432 make_stack(0xB), 433 }; 434 435 const MemTag candidate_tags[candidates_len_tags] = { 436 mtNMT, 437 mtTest, 438 }; 439 440 const int operation_count = 100000; // One hundred thousand 441 for (int i = 0; i < operation_count; i++) { 442 size_t page_start = (size_t)(os::random() % SimpleVMATracker::num_pages); 443 size_t page_end = (size_t)(os::random() % (SimpleVMATracker::num_pages)); 444 445 if (page_end < page_start) { 446 const size_t temp = page_start; 447 page_start = page_end; 448 page_end = page_start; 449 } 450 const size_t num_pages = page_end - page_start; 451 452 if (num_pages == 0) { 453 i--; continue; 454 } 455 456 const size_t start = page_start * page_size; 457 const size_t size = num_pages * page_size; 458 459 const MemTag mem_tag = candidate_tags[os::random() % candidates_len_tags]; 460 const NativeCallStack stack = candidate_stacks[os::random() % candidates_len_stacks]; 461 462 const NCS::StackIndex si = ncss.push(stack); 463 VMATree::RegionData data(si, mem_tag); 464 465 const SimpleVMATracker::Kind kind = (SimpleVMATracker::Kind)(os::random() % 3); 466 467 VMATree::SummaryDiff tree_diff; 468 VMATree::SummaryDiff simple_diff; 469 if (kind == SimpleVMATracker::Reserved) { 470 simple_diff = tr->reserve(start, size, stack, mem_tag); 471 tree_diff = tree.reserve_mapping(start, size, data); 472 } else if (kind == SimpleVMATracker::Committed) { 473 simple_diff = tr->commit(start, size, stack, mem_tag); 474 tree_diff = tree.commit_mapping(start, size, data); 475 } else { 476 simple_diff = tr->release(start, size); 477 tree_diff = tree.release_mapping(start, size); 478 } 479 480 for (int j = 0; j < mt_number_of_tags; j++) { 481 VMATree::SingleDiff td = tree_diff.tag[j]; 482 VMATree::SingleDiff sd = simple_diff.tag[j]; 483 ASSERT_EQ(td.reserve, sd.reserve); 484 ASSERT_EQ(td.commit, sd.commit); 485 } 486 487 488 // Do an in-depth check every 25 000 iterations. 489 if (i % 25000 == 0) { 490 size_t j = 0; 491 while (j < SimpleVMATracker::num_pages) { 492 while (j < SimpleVMATracker::num_pages && 493 tr->pages[j].kind == SimpleVMATracker::Free) { 494 j++; 495 } 496 497 if (j == SimpleVMATracker::num_pages) { 498 break; 499 } 500 501 size_t start = j; 502 SimpleVMATracker::Info starti = tr->pages[start]; 503 504 while (j < SimpleVMATracker::num_pages && 505 tr->pages[j].eq(starti)) { 506 j++; 507 } 508 509 size_t end = j-1; 510 ASSERT_LE(end, SimpleVMATracker::num_pages); 511 SimpleVMATracker::Info endi = tr->pages[end]; 512 513 VMATree::VMATreap& treap = this->treap(tree); 514 VMATree::TreapNode* startn = find(treap, start * page_size); 515 ASSERT_NE(nullptr, startn); 516 VMATree::TreapNode* endn = find(treap, (end * page_size) + page_size); 517 ASSERT_NE(nullptr, endn); 518 519 const NativeCallStack& start_stack = ncss.get(startn->val().out.stack()); 520 const NativeCallStack& end_stack = ncss.get(endn->val().in.stack()); 521 ASSERT_TRUE(starti.stack.equals(start_stack)); 522 ASSERT_TRUE(endi.stack.equals(end_stack)); 523 524 ASSERT_EQ(starti.mem_tag, startn->val().out.mem_tag()); 525 ASSERT_EQ(endi.mem_tag, endn->val().in.mem_tag()); 526 } 527 } 528 } 529 }