| // Copyright 2018 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/base/region-allocator.h" |
| #include "test/unittests/test-utils.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace v8 { |
| namespace base { |
| |
| using Address = RegionAllocator::Address; |
| using RegionState = RegionAllocator::RegionState; |
| using v8::internal::KB; |
| using v8::internal::MB; |
| |
| TEST(RegionAllocatorTest, SimpleAllocateRegionAt) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCount = 16; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| const Address kEnd = kBegin + kSize; |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region. |
| for (Address address = kBegin; address < kEnd; address += kPageSize) { |
| CHECK_EQ(ra.free_size(), kEnd - address); |
| CHECK(ra.AllocateRegionAt(address, kPageSize)); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // Free one region and then the allocation should succeed. |
| CHECK_EQ(ra.FreeRegion(kBegin), kPageSize); |
| CHECK_EQ(ra.free_size(), kPageSize); |
| CHECK(ra.AllocateRegionAt(kBegin, kPageSize)); |
| |
| // Free all the pages. |
| for (Address address = kBegin; address < kEnd; address += kPageSize) { |
| CHECK_EQ(ra.FreeRegion(address), kPageSize); |
| } |
| |
| // Check that the whole region is free and can be fully allocated. |
| CHECK_EQ(ra.free_size(), kSize); |
| CHECK_EQ(ra.AllocateRegion(kSize), kBegin); |
| } |
| |
| TEST(RegionAllocatorTest, SimpleAllocateRegion) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCount = 16; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| const Address kEnd = kBegin + kSize; |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region. |
| for (size_t i = 0; i < kPageCount; i++) { |
| CHECK_EQ(ra.free_size(), kSize - kPageSize * i); |
| Address address = ra.AllocateRegion(kPageSize); |
| CHECK_NE(address, RegionAllocator::kAllocationFailure); |
| CHECK_EQ(address, kBegin + kPageSize * i); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // Try to free one page and ensure that we are able to allocate it again. |
| for (Address address = kBegin; address < kEnd; address += kPageSize) { |
| CHECK_EQ(ra.FreeRegion(address), kPageSize); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), address); |
| } |
| CHECK_EQ(ra.free_size(), 0); |
| } |
| |
| TEST(RegionAllocatorTest, AllocateRegionRandom) { |
| const size_t kPageSize = 8 * KB; |
| const size_t kPageCountLog = 16; |
| const size_t kPageCount = (size_t{1} << kPageCountLog); |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(153 * MB); |
| const Address kEnd = kBegin + kSize; |
| |
| base::RandomNumberGenerator rng(::testing::FLAGS_gtest_random_seed); |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| std::set<Address> allocated_pages; |
| // The page addresses must be randomized this number of allocated pages. |
| const size_t kRandomizationLimit = ra.max_load_for_randomization_ / kPageSize; |
| CHECK_LT(kRandomizationLimit, kPageCount); |
| |
| Address last_address = kBegin; |
| bool saw_randomized_pages = false; |
| |
| for (size_t i = 0; i < kPageCount; i++) { |
| Address address = ra.AllocateRegion(&rng, kPageSize); |
| CHECK_NE(address, RegionAllocator::kAllocationFailure); |
| CHECK(IsAligned(address, kPageSize)); |
| CHECK_LE(kBegin, address); |
| CHECK_LT(address, kEnd); |
| CHECK_EQ(allocated_pages.find(address), allocated_pages.end()); |
| allocated_pages.insert(address); |
| |
| saw_randomized_pages |= (address < last_address); |
| last_address = address; |
| |
| if (i == kRandomizationLimit) { |
| // We must evidence allocation randomization till this point. |
| // The rest of the allocations may still be randomized depending on |
| // the free ranges distribution, however it is not guaranteed. |
| CHECK(saw_randomized_pages); |
| } |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| } |
| |
| TEST(RegionAllocatorTest, AllocateBigRegions) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCountLog = 10; |
| const size_t kPageCount = (size_t{1} << kPageCountLog) - 1; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region. |
| for (size_t i = 0; i < kPageCountLog; i++) { |
| Address address = ra.AllocateRegion(kPageSize * (size_t{1} << i)); |
| CHECK_NE(address, RegionAllocator::kAllocationFailure); |
| CHECK_EQ(address, kBegin + kPageSize * ((size_t{1} << i) - 1)); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // Try to free one page and ensure that we are able to allocate it again. |
| for (size_t i = 0; i < kPageCountLog; i++) { |
| const size_t size = kPageSize * (size_t{1} << i); |
| Address address = kBegin + kPageSize * ((size_t{1} << i) - 1); |
| CHECK_EQ(ra.FreeRegion(address), size); |
| CHECK_EQ(ra.AllocateRegion(size), address); |
| } |
| CHECK_EQ(ra.free_size(), 0); |
| } |
| |
| TEST(RegionAllocatorTest, MergeLeftToRightCoalecsingRegions) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCountLog = 10; |
| const size_t kPageCount = (size_t{1} << kPageCountLog); |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region using the following page size pattern: |
| // |0|1|22|3333|... |
| CHECK_EQ(ra.AllocateRegion(kPageSize), kBegin); |
| for (size_t i = 0; i < kPageCountLog; i++) { |
| Address address = ra.AllocateRegion(kPageSize * (size_t{1} << i)); |
| CHECK_NE(address, RegionAllocator::kAllocationFailure); |
| CHECK_EQ(address, kBegin + kPageSize * (size_t{1} << i)); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // Try to free two coalescing regions and ensure the new page of bigger size |
| // can be allocated. |
| size_t current_size = kPageSize; |
| for (size_t i = 0; i < kPageCountLog; i++) { |
| CHECK_EQ(ra.FreeRegion(kBegin), current_size); |
| CHECK_EQ(ra.FreeRegion(kBegin + current_size), current_size); |
| current_size += current_size; |
| CHECK_EQ(ra.AllocateRegion(current_size), kBegin); |
| } |
| CHECK_EQ(ra.free_size(), 0); |
| } |
| |
| TEST(RegionAllocatorTest, MergeRightToLeftCoalecsingRegions) { |
| base::RandomNumberGenerator rng(::testing::FLAGS_gtest_random_seed); |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCountLog = 10; |
| const size_t kPageCount = (size_t{1} << kPageCountLog); |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region. |
| for (size_t i = 0; i < kPageCount; i++) { |
| Address address = ra.AllocateRegion(kPageSize); |
| CHECK_NE(address, RegionAllocator::kAllocationFailure); |
| CHECK_EQ(address, kBegin + kPageSize * i); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // Free pages with even indices left-to-right. |
| for (size_t i = 0; i < kPageCount; i += 2) { |
| Address address = kBegin + kPageSize * i; |
| CHECK_EQ(ra.FreeRegion(address), kPageSize); |
| } |
| |
| // Free pages with odd indices right-to-left. |
| for (size_t i = 1; i < kPageCount; i += 2) { |
| Address address = kBegin + kPageSize * (kPageCount - i); |
| CHECK_EQ(ra.FreeRegion(address), kPageSize); |
| // Now we should be able to allocate a double-sized page. |
| CHECK_EQ(ra.AllocateRegion(kPageSize * 2), address - kPageSize); |
| // .. but there's a window for only one such page. |
| CHECK_EQ(ra.AllocateRegion(kPageSize * 2), |
| RegionAllocator::kAllocationFailure); |
| } |
| |
| // Free all the double-sized pages. |
| for (size_t i = 0; i < kPageCount; i += 2) { |
| Address address = kBegin + kPageSize * i; |
| CHECK_EQ(ra.FreeRegion(address), kPageSize * 2); |
| } |
| |
| // Check that the whole region is free and can be fully allocated. |
| CHECK_EQ(ra.free_size(), kSize); |
| CHECK_EQ(ra.AllocateRegion(kSize), kBegin); |
| } |
| |
| TEST(RegionAllocatorTest, Fragmentation) { |
| const size_t kPageSize = 64 * KB; |
| const size_t kPageCount = 9; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region. |
| for (size_t i = 0; i < kPageCount; i++) { |
| Address address = ra.AllocateRegion(kPageSize); |
| CHECK_NE(address, RegionAllocator::kAllocationFailure); |
| CHECK_EQ(address, kBegin + kPageSize * i); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // Free pages in the following order and check the freed size. |
| struct { |
| size_t page_index_to_free; |
| size_t expected_page_count; |
| } testcase[] = { // ......... |
| {0, 9}, // x........ |
| {2, 9}, // x.x...... |
| {4, 9}, // x.x.x.... |
| {6, 9}, // x.x.x.x.. |
| {8, 9}, // x.x.x.x.x |
| {1, 7}, // xxx.x.x.x |
| {7, 5}, // xxx.x.xxx |
| {3, 3}, // xxxxx.xxx |
| {5, 1}}; // xxxxxxxxx |
| CHECK_EQ(kPageCount, arraysize(testcase)); |
| |
| CHECK_EQ(ra.all_regions_.size(), kPageCount); |
| for (size_t i = 0; i < kPageCount; i++) { |
| Address address = kBegin + kPageSize * testcase[i].page_index_to_free; |
| CHECK_EQ(ra.FreeRegion(address), kPageSize); |
| CHECK_EQ(ra.all_regions_.size(), testcase[i].expected_page_count); |
| } |
| |
| // Check that the whole region is free and can be fully allocated. |
| CHECK_EQ(ra.free_size(), kSize); |
| CHECK_EQ(ra.AllocateRegion(kSize), kBegin); |
| } |
| |
| TEST(RegionAllocatorTest, FindRegion) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCount = 16; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| const Address kEnd = kBegin + kSize; |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| // Allocate the whole region. |
| for (Address address = kBegin; address < kEnd; address += kPageSize) { |
| CHECK_EQ(ra.free_size(), kEnd - address); |
| CHECK(ra.AllocateRegionAt(address, kPageSize)); |
| } |
| |
| // No free regions left, the allocation should fail. |
| CHECK_EQ(ra.free_size(), 0); |
| CHECK_EQ(ra.AllocateRegion(kPageSize), RegionAllocator::kAllocationFailure); |
| |
| // The out-of region requests must return end iterator. |
| CHECK_EQ(ra.FindRegion(kBegin - 1), ra.all_regions_.end()); |
| CHECK_EQ(ra.FindRegion(kBegin - kPageSize), ra.all_regions_.end()); |
| CHECK_EQ(ra.FindRegion(kBegin / 2), ra.all_regions_.end()); |
| CHECK_EQ(ra.FindRegion(kEnd), ra.all_regions_.end()); |
| CHECK_EQ(ra.FindRegion(kEnd + kPageSize), ra.all_regions_.end()); |
| CHECK_EQ(ra.FindRegion(kEnd * 2), ra.all_regions_.end()); |
| |
| for (Address address = kBegin; address < kEnd; address += kPageSize / 4) { |
| RegionAllocator::AllRegionsSet::iterator region_iter = |
| ra.FindRegion(address); |
| CHECK_NE(region_iter, ra.all_regions_.end()); |
| RegionAllocator::Region* region = *region_iter; |
| Address region_start = RoundDown(address, kPageSize); |
| CHECK_EQ(region->begin(), region_start); |
| CHECK_LE(region->begin(), address); |
| CHECK_LT(address, region->end()); |
| } |
| } |
| |
| TEST(RegionAllocatorTest, TrimRegion) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCount = 64; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| Address address = kBegin + 13 * kPageSize; |
| size_t size = 37 * kPageSize; |
| size_t free_size = kSize - size; |
| CHECK(ra.AllocateRegionAt(address, size)); |
| |
| size_t trim_size = kPageSize; |
| do { |
| CHECK_EQ(ra.CheckRegion(address), size); |
| CHECK_EQ(ra.free_size(), free_size); |
| |
| trim_size = std::min(size, trim_size); |
| size -= trim_size; |
| free_size += trim_size; |
| CHECK_EQ(ra.TrimRegion(address, size), trim_size); |
| trim_size *= 2; |
| } while (size != 0); |
| |
| // Check that the whole region is free and can be fully allocated. |
| CHECK_EQ(ra.free_size(), kSize); |
| CHECK_EQ(ra.AllocateRegion(kSize), kBegin); |
| } |
| |
| TEST(RegionAllocatorTest, AllocateExcluded) { |
| const size_t kPageSize = 4 * KB; |
| const size_t kPageCount = 64; |
| const size_t kSize = kPageSize * kPageCount; |
| const Address kBegin = static_cast<Address>(kPageSize * 153); |
| |
| RegionAllocator ra(kBegin, kSize, kPageSize); |
| |
| Address address = kBegin + 13 * kPageSize; |
| size_t size = 37 * kPageSize; |
| CHECK(ra.AllocateRegionAt(address, size, RegionState::kExcluded)); |
| |
| // The region is not free and cannot be allocated at again. |
| CHECK(!ra.IsFree(address, size)); |
| CHECK(!ra.AllocateRegionAt(address, size)); |
| auto region_iter = ra.FindRegion(address); |
| |
| CHECK((*region_iter)->is_excluded()); |
| |
| // It's not possible to free or trim an excluded region. |
| CHECK_EQ(ra.FreeRegion(address), 0); |
| CHECK_EQ(ra.TrimRegion(address, kPageSize), 0); |
| } |
| |
| } // namespace base |
| } // namespace v8 |