blob: a61102008f70612bd9cd37adac3732df922075ef [file] [log] [blame]
// Copyright 2021 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/emulated-virtual-address-subspace.h"
#include "src/base/bits.h"
#include "src/base/platform/platform.h"
namespace v8 {
namespace base {
EmulatedVirtualAddressSubspace::EmulatedVirtualAddressSubspace(
VirtualAddressSpace* parent_space, Address base, size_t mapped_size,
size_t total_size)
: VirtualAddressSpace(parent_space->page_size(),
parent_space->allocation_granularity(), base,
total_size, parent_space->max_page_permissions()),
mapped_size_(mapped_size),
parent_space_(parent_space),
region_allocator_(base, mapped_size, parent_space_->page_size()) {
// For simplicity, we currently require both the mapped and total size to be
// a power of two. This simplifies some things later on, for example, random
// addresses can be generated with a simply bitmask, and will then be inside
// the unmapped space with a probability >= 50% (mapped size == unmapped
// size) or never (mapped size == total size).
DCHECK(base::bits::IsPowerOfTwo(mapped_size));
DCHECK(base::bits::IsPowerOfTwo(total_size));
}
EmulatedVirtualAddressSubspace::~EmulatedVirtualAddressSubspace() {
parent_space_->FreePages(base(), mapped_size_);
}
void EmulatedVirtualAddressSubspace::SetRandomSeed(int64_t seed) {
MutexGuard guard(&mutex_);
rng_.SetSeed(seed);
}
Address EmulatedVirtualAddressSubspace::RandomPageAddress() {
MutexGuard guard(&mutex_);
Address addr = base() + (static_cast<uint64_t>(rng_.NextInt64()) % size());
return RoundDown(addr, allocation_granularity());
}
Address EmulatedVirtualAddressSubspace::AllocatePages(
Address hint, size_t size, size_t alignment, PagePermissions permissions) {
if (hint == kNoHint || MappedRegionContains(hint, size)) {
MutexGuard guard(&mutex_);
// Attempt to find a region in the mapped region.
Address address = region_allocator_.AllocateRegion(hint, size, alignment);
if (address != RegionAllocator::kAllocationFailure) {
// Success. Only need to adjust the page permissions.
if (parent_space_->SetPagePermissions(address, size, permissions)) {
return address;
}
// Probably ran out of memory, but still try to allocate in the unmapped
// space.
CHECK_EQ(size, region_allocator_.FreeRegion(address));
}
}
// No luck or hint is outside of the mapped region. Try to allocate pages in
// the unmapped space using page allocation hints instead.
if (!IsUsableSizeForUnmappedRegion(size)) return kNullAddress;
static constexpr int kMaxAttempts = 10;
for (int i = 0; i < kMaxAttempts; i++) {
// If an unmapped region exists, it must cover at least 50% of the whole
// space (unmapped + mapped region). Since we limit the size of allocation
// to 50% of the unmapped region (see IsUsableSizeForUnmappedRegion), a
// random page address has at least a 25% chance of being a usable base. As
// such, this loop should usually terminate quickly.
DCHECK_GE(unmapped_size(), mapped_size());
while (!UnmappedRegionContains(hint, size)) {
hint = RandomPageAddress();
}
hint = RoundDown(hint, alignment);
const Address result =
parent_space_->AllocatePages(hint, size, alignment, permissions);
if (UnmappedRegionContains(result, size)) {
return result;
} else if (result) {
parent_space_->FreePages(result, size);
}
// Retry at a different address.
hint = RandomPageAddress();
}
return kNullAddress;
}
void EmulatedVirtualAddressSubspace::FreePages(Address address, size_t size) {
if (MappedRegionContains(address, size)) {
MutexGuard guard(&mutex_);
CHECK_EQ(size, region_allocator_.FreeRegion(address));
CHECK(parent_space_->DecommitPages(address, size));
} else {
DCHECK(UnmappedRegionContains(address, size));
parent_space_->FreePages(address, size);
}
}
Address EmulatedVirtualAddressSubspace::AllocateSharedPages(
Address hint, size_t size, PagePermissions permissions,
PlatformSharedMemoryHandle handle, uint64_t offset) {
// Can only allocate shared pages in the unmapped region.
if (!IsUsableSizeForUnmappedRegion(size)) return kNullAddress;
static constexpr int kMaxAttempts = 10;
for (int i = 0; i < kMaxAttempts; i++) {
// See AllocatePages() for why this loop usually terminates quickly.
DCHECK_GE(unmapped_size(), mapped_size());
while (!UnmappedRegionContains(hint, size)) {
hint = RandomPageAddress();
}
Address region = parent_space_->AllocateSharedPages(hint, size, permissions,
handle, offset);
if (UnmappedRegionContains(region, size)) {
return region;
} else if (region) {
parent_space_->FreeSharedPages(region, size);
}
hint = RandomPageAddress();
}
return kNullAddress;
}
void EmulatedVirtualAddressSubspace::FreeSharedPages(Address address,
size_t size) {
DCHECK(UnmappedRegionContains(address, size));
parent_space_->FreeSharedPages(address, size);
}
bool EmulatedVirtualAddressSubspace::SetPagePermissions(
Address address, size_t size, PagePermissions permissions) {
DCHECK(Contains(address, size));
return parent_space_->SetPagePermissions(address, size, permissions);
}
bool EmulatedVirtualAddressSubspace::AllocateGuardRegion(Address address,
size_t size) {
if (MappedRegionContains(address, size)) {
MutexGuard guard(&mutex_);
return region_allocator_.AllocateRegionAt(address, size);
}
if (!UnmappedRegionContains(address, size)) return false;
return parent_space_->AllocateGuardRegion(address, size);
}
void EmulatedVirtualAddressSubspace::FreeGuardRegion(Address address,
size_t size) {
if (MappedRegionContains(address, size)) {
MutexGuard guard(&mutex_);
CHECK_EQ(size, region_allocator_.FreeRegion(address));
} else {
DCHECK(UnmappedRegionContains(address, size));
parent_space_->FreeGuardRegion(address, size);
}
}
bool EmulatedVirtualAddressSubspace::CanAllocateSubspaces() {
// This is not supported, mostly because it's not (yet) needed in practice.
return false;
}
std::unique_ptr<v8::VirtualAddressSpace>
EmulatedVirtualAddressSubspace::AllocateSubspace(
Address hint, size_t size, size_t alignment,
PagePermissions max_page_permissions) {
UNREACHABLE();
}
bool EmulatedVirtualAddressSubspace::RecommitPages(
Address address, size_t size, PagePermissions permissions) {
DCHECK(Contains(address, size));
return parent_space_->RecommitPages(address, size, permissions);
}
bool EmulatedVirtualAddressSubspace::DiscardSystemPages(Address address,
size_t size) {
DCHECK(Contains(address, size));
return parent_space_->DiscardSystemPages(address, size);
}
bool EmulatedVirtualAddressSubspace::DecommitPages(Address address,
size_t size) {
DCHECK(Contains(address, size));
return parent_space_->DecommitPages(address, size);
}
} // namespace base
} // namespace v8