// 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/virtual-address-space.h" #include "include/v8-platform.h" #include "src/base/bits.h" #include "src/base/platform/platform.h" #include "src/base/platform/wrappers.h" namespace v8 { namespace base { #define STATIC_ASSERT_ENUM(a, b) \ static_assert(static_cast(a) == static_cast(b), \ "mismatching enum: " #a) STATIC_ASSERT_ENUM(PagePermissions::kNoAccess, OS::MemoryPermission::kNoAccess); STATIC_ASSERT_ENUM(PagePermissions::kReadWrite, OS::MemoryPermission::kReadWrite); STATIC_ASSERT_ENUM(PagePermissions::kReadWriteExecute, OS::MemoryPermission::kReadWriteExecute); STATIC_ASSERT_ENUM(PagePermissions::kReadExecute, OS::MemoryPermission::kReadExecute); #undef STATIC_ASSERT_ENUM namespace { uint8_t PagePermissionsToBitset(PagePermissions permissions) { switch (permissions) { case PagePermissions::kNoAccess: return 0b000; case PagePermissions::kRead: return 0b100; case PagePermissions::kReadWrite: return 0b110; case PagePermissions::kReadWriteExecute: return 0b111; case PagePermissions::kReadExecute: return 0b101; } } } // namespace bool IsSubset(PagePermissions lhs, PagePermissions rhs) { uint8_t lhs_bits = PagePermissionsToBitset(lhs); uint8_t rhs_bits = PagePermissionsToBitset(rhs); return (lhs_bits & rhs_bits) == lhs_bits; } VirtualAddressSpace::VirtualAddressSpace() : VirtualAddressSpaceBase(OS::CommitPageSize(), OS::AllocatePageSize(), kNullAddress, std::numeric_limits::max(), PagePermissions::kReadWriteExecute) { #if V8_OS_WIN // On Windows, this additional step is required to lookup the VirtualAlloc2 // and friends functions. OS::EnsureWin32MemoryAPILoaded(); #endif // V8_OS_WIN DCHECK(bits::IsPowerOfTwo(page_size())); DCHECK(bits::IsPowerOfTwo(allocation_granularity())); DCHECK_GE(allocation_granularity(), page_size()); DCHECK(IsAligned(allocation_granularity(), page_size())); } void VirtualAddressSpace::SetRandomSeed(int64_t seed) { OS::SetRandomMmapSeed(seed); } Address VirtualAddressSpace::RandomPageAddress() { return reinterpret_cast
(OS::GetRandomMmapAddr()); } Address VirtualAddressSpace::AllocatePages(Address hint, size_t size, size_t alignment, PagePermissions permissions) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); return reinterpret_cast
( OS::Allocate(reinterpret_cast(hint), size, alignment, static_cast(permissions))); } void VirtualAddressSpace::FreePages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); OS::Free(reinterpret_cast(address), size); } bool VirtualAddressSpace::SetPagePermissions(Address address, size_t size, PagePermissions permissions) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::SetPermissions(reinterpret_cast(address), size, static_cast(permissions)); } bool VirtualAddressSpace::AllocateGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); void* hint = reinterpret_cast(address); void* result = OS::Allocate(hint, size, allocation_granularity(), OS::MemoryPermission::kNoAccess); if (result && result != hint) { OS::Free(result, size); } return result == hint; } void VirtualAddressSpace::FreeGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); OS::Free(reinterpret_cast(address), size); } bool VirtualAddressSpace::CanAllocateSubspaces() { return OS::CanReserveAddressSpace(); } Address VirtualAddressSpace::AllocateSharedPages( Address hint, size_t size, PagePermissions permissions, PlatformSharedMemoryHandle handle, uint64_t offset) { DCHECK(IsAligned(hint, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsAligned(offset, allocation_granularity())); return reinterpret_cast
(OS::AllocateShared( reinterpret_cast(hint), size, static_cast(permissions), handle, offset)); } void VirtualAddressSpace::FreeSharedPages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); OS::FreeShared(reinterpret_cast(address), size); } std::unique_ptr VirtualAddressSpace::AllocateSubspace( Address hint, size_t size, size_t alignment, PagePermissions max_page_permissions) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); base::Optional reservation = OS::CreateAddressSpaceReservation( reinterpret_cast(hint), size, alignment, static_cast(max_page_permissions)); if (!reservation.has_value()) return std::unique_ptr(); return std::unique_ptr( new VirtualAddressSubspace(*reservation, this, max_page_permissions)); } bool VirtualAddressSpace::DiscardSystemPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::DiscardSystemPages(reinterpret_cast(address), size); } bool VirtualAddressSpace::DecommitPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return OS::DecommitPages(reinterpret_cast(address), size); } void VirtualAddressSpace::FreeSubspace(VirtualAddressSubspace* subspace) { OS::FreeAddressSpaceReservation(subspace->reservation_); } VirtualAddressSubspace::VirtualAddressSubspace( AddressSpaceReservation reservation, VirtualAddressSpaceBase* parent_space, PagePermissions max_page_permissions) : VirtualAddressSpaceBase(parent_space->page_size(), parent_space->allocation_granularity(), reinterpret_cast
(reservation.base()), reservation.size(), max_page_permissions), reservation_(reservation), region_allocator_(reinterpret_cast
(reservation.base()), reservation.size(), parent_space->allocation_granularity()), parent_space_(parent_space) { #if V8_OS_WIN // On Windows, the address space reservation needs to be split and merged at // the OS level as well. region_allocator_.set_on_split_callback([this](Address start, size_t size) { DCHECK(IsAligned(start, allocation_granularity())); CHECK(reservation_.SplitPlaceholder(reinterpret_cast(start), size)); }); region_allocator_.set_on_merge_callback([this](Address start, size_t size) { DCHECK(IsAligned(start, allocation_granularity())); CHECK(reservation_.MergePlaceholders(reinterpret_cast(start), size)); }); #endif // V8_OS_WIN } VirtualAddressSubspace::~VirtualAddressSubspace() { parent_space_->FreeSubspace(this); } void VirtualAddressSubspace::SetRandomSeed(int64_t seed) { MutexGuard guard(&mutex_); rng_.SetSeed(seed); } Address VirtualAddressSubspace::RandomPageAddress() { MutexGuard guard(&mutex_); // Note: the random numbers generated here aren't uniformly distributed if the // size isn't a power of two. Address addr = base() + (static_cast(rng_.NextInt64()) % size()); return RoundDown(addr, allocation_granularity()); } Address VirtualAddressSubspace::AllocatePages(Address hint, size_t size, size_t alignment, PagePermissions permissions) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsSubset(permissions, max_page_permissions())); MutexGuard guard(&mutex_); Address address = region_allocator_.AllocateRegion(hint, size, alignment); if (address == RegionAllocator::kAllocationFailure) return kNullAddress; if (!reservation_.Allocate(reinterpret_cast(address), size, static_cast(permissions))) { // This most likely means that we ran out of memory. CHECK_EQ(size, region_allocator_.FreeRegion(address)); return kNullAddress; } return address; } void VirtualAddressSubspace::FreePages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); // The order here is important: on Windows, the allocation first has to be // freed to a placeholder before the placeholder can be merged (during the // merge_callback) with any surrounding placeholder mappings. CHECK(reservation_.Free(reinterpret_cast(address), size)); CHECK_EQ(size, region_allocator_.FreeRegion(address)); } bool VirtualAddressSubspace::SetPagePermissions(Address address, size_t size, PagePermissions permissions) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); DCHECK(IsSubset(permissions, max_page_permissions())); return reservation_.SetPermissions( reinterpret_cast(address), size, static_cast(permissions)); } bool VirtualAddressSubspace::AllocateGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); // It is guaranteed that reserved address space is inaccessible, so we just // need to mark the region as in-use in the region allocator. return region_allocator_.AllocateRegionAt(address, size); } void VirtualAddressSubspace::FreeGuardRegion(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); CHECK_EQ(size, region_allocator_.FreeRegion(address)); } Address VirtualAddressSubspace::AllocateSharedPages( Address hint, size_t size, PagePermissions permissions, PlatformSharedMemoryHandle handle, uint64_t offset) { DCHECK(IsAligned(hint, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsAligned(offset, allocation_granularity())); MutexGuard guard(&mutex_); Address address = region_allocator_.AllocateRegion(hint, size, allocation_granularity()); if (address == RegionAllocator::kAllocationFailure) return kNullAddress; if (!reservation_.AllocateShared( reinterpret_cast(address), size, static_cast(permissions), handle, offset)) { CHECK_EQ(size, region_allocator_.FreeRegion(address)); return kNullAddress; } return address; } void VirtualAddressSubspace::FreeSharedPages(Address address, size_t size) { DCHECK(IsAligned(address, allocation_granularity())); DCHECK(IsAligned(size, allocation_granularity())); MutexGuard guard(&mutex_); // The order here is important: on Windows, the allocation first has to be // freed to a placeholder before the placeholder can be merged (during the // merge_callback) with any surrounding placeholder mappings. CHECK(reservation_.FreeShared(reinterpret_cast(address), size)); CHECK_EQ(size, region_allocator_.FreeRegion(address)); } std::unique_ptr VirtualAddressSubspace::AllocateSubspace(Address hint, size_t size, size_t alignment, PagePermissions max_page_permissions) { DCHECK(IsAligned(alignment, allocation_granularity())); DCHECK(IsAligned(hint, alignment)); DCHECK(IsAligned(size, allocation_granularity())); DCHECK(IsSubset(max_page_permissions, this->max_page_permissions())); MutexGuard guard(&mutex_); Address address = region_allocator_.AllocateRegion(hint, size, alignment); if (address == RegionAllocator::kAllocationFailure) { return std::unique_ptr(); } base::Optional reservation = reservation_.CreateSubReservation( reinterpret_cast(address), size, static_cast(max_page_permissions)); if (!reservation.has_value()) { CHECK_EQ(size, region_allocator_.FreeRegion(address)); return nullptr; } return std::unique_ptr( new VirtualAddressSubspace(*reservation, this, max_page_permissions)); } bool VirtualAddressSubspace::DiscardSystemPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return reservation_.DiscardSystemPages(reinterpret_cast(address), size); } bool VirtualAddressSubspace::DecommitPages(Address address, size_t size) { DCHECK(IsAligned(address, page_size())); DCHECK(IsAligned(size, page_size())); return reservation_.DecommitPages(reinterpret_cast(address), size); } void VirtualAddressSubspace::FreeSubspace(VirtualAddressSubspace* subspace) { MutexGuard guard(&mutex_); AddressSpaceReservation reservation = subspace->reservation_; Address base = reinterpret_cast
(reservation.base()); CHECK_EQ(reservation.size(), region_allocator_.FreeRegion(base)); CHECK(reservation_.FreeSubReservation(reservation)); } } // namespace base } // namespace v8