| // 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 |