| // Copyright 2020 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/heap/cppgc/page-memory.h" |
| |
| #include "src/base/macros.h" |
| #include "src/heap/cppgc/sanitizers.h" |
| |
| namespace cppgc { |
| namespace internal { |
| |
| namespace { |
| |
| void Unprotect(PageAllocator* allocator, const PageMemory& page_memory) { |
| if (SupportsCommittingGuardPages(allocator)) { |
| CHECK(allocator->SetPermissions(page_memory.writeable_region().base(), |
| page_memory.writeable_region().size(), |
| PageAllocator::Permission::kReadWrite)); |
| } else { |
| // No protection in case the allocator cannot commit at the required |
| // granularity. Only protect if the allocator supports committing at that |
| // granularity. |
| // |
| // The allocator needs to support committing the overall range. |
| CHECK_EQ(0u, |
| page_memory.overall_region().size() % allocator->CommitPageSize()); |
| CHECK(allocator->SetPermissions(page_memory.overall_region().base(), |
| page_memory.overall_region().size(), |
| PageAllocator::Permission::kReadWrite)); |
| } |
| } |
| |
| void Protect(PageAllocator* allocator, const PageMemory& page_memory) { |
| if (SupportsCommittingGuardPages(allocator)) { |
| // Swap the same region, providing the OS with a chance for fast lookup and |
| // change. |
| CHECK(allocator->SetPermissions(page_memory.writeable_region().base(), |
| page_memory.writeable_region().size(), |
| PageAllocator::Permission::kNoAccess)); |
| } else { |
| // See Unprotect(). |
| CHECK_EQ(0u, |
| page_memory.overall_region().size() % allocator->CommitPageSize()); |
| CHECK(allocator->SetPermissions(page_memory.overall_region().base(), |
| page_memory.overall_region().size(), |
| PageAllocator::Permission::kNoAccess)); |
| } |
| } |
| |
| MemoryRegion ReserveMemoryRegion(PageAllocator* allocator, |
| size_t allocation_size) { |
| void* region_memory = |
| allocator->AllocatePages(nullptr, allocation_size, kPageSize, |
| PageAllocator::Permission::kNoAccess); |
| const MemoryRegion reserved_region(static_cast<Address>(region_memory), |
| allocation_size); |
| DCHECK_EQ(reserved_region.base() + allocation_size, reserved_region.end()); |
| return reserved_region; |
| } |
| |
| void FreeMemoryRegion(PageAllocator* allocator, |
| const MemoryRegion& reserved_region) { |
| // Make sure pages returned to OS are unpoisoned. |
| ASAN_UNPOISON_MEMORY_REGION(reserved_region.base(), reserved_region.size()); |
| allocator->FreePages(reserved_region.base(), reserved_region.size()); |
| } |
| |
| } // namespace |
| |
| PageMemoryRegion::PageMemoryRegion(PageAllocator* allocator, |
| MemoryRegion reserved_region, bool is_large) |
| : allocator_(allocator), |
| reserved_region_(reserved_region), |
| is_large_(is_large) {} |
| |
| PageMemoryRegion::~PageMemoryRegion() { |
| FreeMemoryRegion(allocator_, reserved_region()); |
| } |
| |
| // static |
| constexpr size_t NormalPageMemoryRegion::kNumPageRegions; |
| |
| NormalPageMemoryRegion::NormalPageMemoryRegion(PageAllocator* allocator) |
| : PageMemoryRegion(allocator, |
| ReserveMemoryRegion( |
| allocator, RoundUp(kPageSize * kNumPageRegions, |
| allocator->AllocatePageSize())), |
| false) { |
| #ifdef DEBUG |
| for (size_t i = 0; i < kNumPageRegions; ++i) { |
| DCHECK_EQ(false, page_memories_in_use_[i]); |
| } |
| #endif // DEBUG |
| } |
| |
| NormalPageMemoryRegion::~NormalPageMemoryRegion() = default; |
| |
| void NormalPageMemoryRegion::Allocate(Address writeable_base) { |
| const size_t index = GetIndex(writeable_base); |
| ChangeUsed(index, true); |
| Unprotect(allocator_, GetPageMemory(index)); |
| } |
| |
| void NormalPageMemoryRegion::Free(Address writeable_base) { |
| const size_t index = GetIndex(writeable_base); |
| ChangeUsed(index, false); |
| Protect(allocator_, GetPageMemory(index)); |
| } |
| |
| void NormalPageMemoryRegion::UnprotectForTesting() { |
| for (size_t i = 0; i < kNumPageRegions; ++i) { |
| Unprotect(allocator_, GetPageMemory(i)); |
| } |
| } |
| |
| LargePageMemoryRegion::LargePageMemoryRegion(PageAllocator* allocator, |
| size_t length) |
| : PageMemoryRegion(allocator, |
| ReserveMemoryRegion( |
| allocator, RoundUp(length + 2 * kGuardPageSize, |
| allocator->AllocatePageSize())), |
| true) {} |
| |
| LargePageMemoryRegion::~LargePageMemoryRegion() = default; |
| |
| void LargePageMemoryRegion::UnprotectForTesting() { |
| Unprotect(allocator_, GetPageMemory()); |
| } |
| |
| PageMemoryRegionTree::PageMemoryRegionTree() = default; |
| |
| PageMemoryRegionTree::~PageMemoryRegionTree() = default; |
| |
| void PageMemoryRegionTree::Add(PageMemoryRegion* region) { |
| DCHECK(region); |
| auto result = set_.emplace(region->reserved_region().base(), region); |
| USE(result); |
| DCHECK(result.second); |
| } |
| |
| void PageMemoryRegionTree::Remove(PageMemoryRegion* region) { |
| DCHECK(region); |
| auto size = set_.erase(region->reserved_region().base()); |
| USE(size); |
| DCHECK_EQ(1u, size); |
| } |
| |
| NormalPageMemoryPool::NormalPageMemoryPool() = default; |
| |
| NormalPageMemoryPool::~NormalPageMemoryPool() = default; |
| |
| void NormalPageMemoryPool::Add(size_t bucket, NormalPageMemoryRegion* pmr, |
| Address writeable_base) { |
| DCHECK_LT(bucket, kNumPoolBuckets); |
| pool_[bucket].push_back(std::make_pair(pmr, writeable_base)); |
| } |
| |
| std::pair<NormalPageMemoryRegion*, Address> NormalPageMemoryPool::Take( |
| size_t bucket) { |
| DCHECK_LT(bucket, kNumPoolBuckets); |
| if (pool_[bucket].empty()) return {nullptr, nullptr}; |
| std::pair<NormalPageMemoryRegion*, Address> pair = pool_[bucket].back(); |
| pool_[bucket].pop_back(); |
| return pair; |
| } |
| |
| PageBackend::PageBackend(PageAllocator* allocator) : allocator_(allocator) {} |
| |
| PageBackend::~PageBackend() = default; |
| |
| Address PageBackend::AllocateNormalPageMemory(size_t bucket) { |
| std::pair<NormalPageMemoryRegion*, Address> result = page_pool_.Take(bucket); |
| if (!result.first) { |
| auto pmr = std::make_unique<NormalPageMemoryRegion>(allocator_); |
| for (size_t i = 0; i < NormalPageMemoryRegion::kNumPageRegions; ++i) { |
| page_pool_.Add(bucket, pmr.get(), |
| pmr->GetPageMemory(i).writeable_region().base()); |
| } |
| page_memory_region_tree_.Add(pmr.get()); |
| normal_page_memory_regions_.push_back(std::move(pmr)); |
| return AllocateNormalPageMemory(bucket); |
| } |
| result.first->Allocate(result.second); |
| return result.second; |
| } |
| |
| void PageBackend::FreeNormalPageMemory(size_t bucket, Address writeable_base) { |
| auto* pmr = static_cast<NormalPageMemoryRegion*>( |
| page_memory_region_tree_.Lookup(writeable_base)); |
| pmr->Free(writeable_base); |
| page_pool_.Add(bucket, pmr, writeable_base); |
| } |
| |
| Address PageBackend::AllocateLargePageMemory(size_t size) { |
| auto pmr = std::make_unique<LargePageMemoryRegion>(allocator_, size); |
| const PageMemory pm = pmr->GetPageMemory(); |
| Unprotect(allocator_, pm); |
| page_memory_region_tree_.Add(pmr.get()); |
| large_page_memory_regions_.insert(std::make_pair(pmr.get(), std::move(pmr))); |
| return pm.writeable_region().base(); |
| } |
| |
| void PageBackend::FreeLargePageMemory(Address writeable_base) { |
| PageMemoryRegion* pmr = page_memory_region_tree_.Lookup(writeable_base); |
| page_memory_region_tree_.Remove(pmr); |
| auto size = large_page_memory_regions_.erase(pmr); |
| USE(size); |
| DCHECK_EQ(1u, size); |
| } |
| |
| } // namespace internal |
| } // namespace cppgc |