v8/test/unittests/base/region-allocator-unittest.cc
Samuel Groß 8581adaee6 Introduce v8_enable_virtual_memory_cage
When this is enabled, v8 reserves a large region of virtual address
space during initialization, at the start of which it will place its 4GB
pointer compression cage. The remainder of the cage is used to store
ArrayBuffer backing stores and WASM memory buffers. This will later
allow referencing these buffers from inside V8 through offsets from the
cage base rather than through raw pointers.

Bug: chromium:1218005
Change-Id: I300094b07f64985217104b14c320cc019f8438af
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3010195
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Samuel Groß <saelo@google.com>
Cr-Commit-Position: refs/heads/master@{#76234}
2021-08-11 16:13:42 +00:00

401 lines
14 KiB
C++

// 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, SimpleAllocateAlignedRegion) {
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);
RegionAllocator ra(kBegin, kSize, kPageSize);
// Allocate regions with different alignments and verify that they are
// correctly aligned.
const size_t alignments[] = {kPageSize, kPageSize * 8, kPageSize,
kPageSize * 4, kPageSize * 2, kPageSize * 2,
kPageSize * 4, kPageSize * 2};
for (auto alignment : alignments) {
Address address = ra.AllocateAlignedRegion(kPageSize, alignment);
CHECK_NE(address, RegionAllocator::kAllocationFailure);
CHECK(IsAligned(address, alignment));
}
CHECK_EQ(ra.free_size(), 8 * kPageSize);
}
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