1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/heap/cppgc/page-memory.h"
6
7#include "src/base/macros.h"
8#include "src/base/sanitizer/asan.h"
9#include "src/heap/cppgc/platform.h"
10
11namespace cppgc {
12namespace internal {
13
14namespace {
15
16void Unprotect(PageAllocator& allocator, FatalOutOfMemoryHandler& oom_handler,
17               const PageMemory& page_memory) {
18  if (SupportsCommittingGuardPages(allocator)) {
19    if (!allocator.SetPermissions(page_memory.writeable_region().base(),
20                                  page_memory.writeable_region().size(),
21                                  PageAllocator::Permission::kReadWrite)) {
22      oom_handler("Oilpan: Unprotecting memory.");
23    }
24  } else {
25    // No protection in case the allocator cannot commit at the required
26    // granularity. Only protect if the allocator supports committing at that
27    // granularity.
28    //
29    // The allocator needs to support committing the overall range.
30    CHECK_EQ(0u,
31             page_memory.overall_region().size() % allocator.CommitPageSize());
32    if (!allocator.SetPermissions(page_memory.overall_region().base(),
33                                  page_memory.overall_region().size(),
34                                  PageAllocator::Permission::kReadWrite)) {
35      oom_handler("Oilpan: Unprotecting memory.");
36    }
37  }
38}
39
40void Protect(PageAllocator& allocator, FatalOutOfMemoryHandler& oom_handler,
41             const PageMemory& page_memory) {
42  if (SupportsCommittingGuardPages(allocator)) {
43    // Swap the same region, providing the OS with a chance for fast lookup and
44    // change.
45    if (!allocator.SetPermissions(page_memory.writeable_region().base(),
46                                  page_memory.writeable_region().size(),
47                                  PageAllocator::Permission::kNoAccess)) {
48      oom_handler("Oilpan: Protecting memory.");
49    }
50  } else {
51    // See Unprotect().
52    CHECK_EQ(0u,
53             page_memory.overall_region().size() % allocator.CommitPageSize());
54    if (!allocator.SetPermissions(page_memory.overall_region().base(),
55                                  page_memory.overall_region().size(),
56                                  PageAllocator::Permission::kNoAccess)) {
57      oom_handler("Oilpan: Protecting memory.");
58    }
59  }
60}
61
62MemoryRegion ReserveMemoryRegion(PageAllocator& allocator,
63                                 FatalOutOfMemoryHandler& oom_handler,
64                                 size_t allocation_size) {
65  void* region_memory =
66      allocator.AllocatePages(nullptr, allocation_size, kPageSize,
67                              PageAllocator::Permission::kNoAccess);
68  if (!region_memory) {
69    oom_handler("Oilpan: Reserving memory.");
70  }
71  const MemoryRegion reserved_region(static_cast<Address>(region_memory),
72                                     allocation_size);
73  DCHECK_EQ(reserved_region.base() + allocation_size, reserved_region.end());
74  return reserved_region;
75}
76
77void FreeMemoryRegion(PageAllocator& allocator,
78                      const MemoryRegion& reserved_region) {
79  // Make sure pages returned to OS are unpoisoned.
80  ASAN_UNPOISON_MEMORY_REGION(reserved_region.base(), reserved_region.size());
81  allocator.FreePages(reserved_region.base(), reserved_region.size());
82}
83
84}  // namespace
85
86PageMemoryRegion::PageMemoryRegion(PageAllocator& allocator,
87                                   FatalOutOfMemoryHandler& oom_handler,
88                                   MemoryRegion reserved_region, bool is_large)
89    : allocator_(allocator),
90      oom_handler_(oom_handler),
91      reserved_region_(reserved_region),
92      is_large_(is_large) {}
93
94PageMemoryRegion::~PageMemoryRegion() {
95  FreeMemoryRegion(allocator_, reserved_region());
96}
97
98// static
99constexpr size_t NormalPageMemoryRegion::kNumPageRegions;
100
101NormalPageMemoryRegion::NormalPageMemoryRegion(
102    PageAllocator& allocator, FatalOutOfMemoryHandler& oom_handler)
103    : PageMemoryRegion(
104          allocator, oom_handler,
105          ReserveMemoryRegion(allocator, oom_handler,
106                              RoundUp(kPageSize * kNumPageRegions,
107                                      allocator.AllocatePageSize())),
108          false) {
109#ifdef DEBUG
110  for (size_t i = 0; i < kNumPageRegions; ++i) {
111    DCHECK_EQ(false, page_memories_in_use_[i]);
112  }
113#endif  // DEBUG
114}
115
116NormalPageMemoryRegion::~NormalPageMemoryRegion() = default;
117
118void NormalPageMemoryRegion::Allocate(Address writeable_base) {
119  const size_t index = GetIndex(writeable_base);
120  ChangeUsed(index, true);
121  Unprotect(allocator_, oom_handler_, GetPageMemory(index));
122}
123
124void NormalPageMemoryRegion::Free(Address writeable_base) {
125  const size_t index = GetIndex(writeable_base);
126  ChangeUsed(index, false);
127  Protect(allocator_, oom_handler_, GetPageMemory(index));
128}
129
130void NormalPageMemoryRegion::UnprotectForTesting() {
131  for (size_t i = 0; i < kNumPageRegions; ++i) {
132    Unprotect(allocator_, oom_handler_, GetPageMemory(i));
133  }
134}
135
136LargePageMemoryRegion::LargePageMemoryRegion(
137    PageAllocator& allocator, FatalOutOfMemoryHandler& oom_handler,
138    size_t length)
139    : PageMemoryRegion(
140          allocator, oom_handler,
141          ReserveMemoryRegion(allocator, oom_handler,
142                              RoundUp(length + 2 * kGuardPageSize,
143                                      allocator.AllocatePageSize())),
144          true) {}
145
146LargePageMemoryRegion::~LargePageMemoryRegion() = default;
147
148void LargePageMemoryRegion::UnprotectForTesting() {
149  Unprotect(allocator_, oom_handler_, GetPageMemory());
150}
151
152PageMemoryRegionTree::PageMemoryRegionTree() = default;
153
154PageMemoryRegionTree::~PageMemoryRegionTree() = default;
155
156void PageMemoryRegionTree::Add(PageMemoryRegion* region) {
157  DCHECK(region);
158  auto result = set_.emplace(region->reserved_region().base(), region);
159  USE(result);
160  DCHECK(result.second);
161}
162
163void PageMemoryRegionTree::Remove(PageMemoryRegion* region) {
164  DCHECK(region);
165  auto size = set_.erase(region->reserved_region().base());
166  USE(size);
167  DCHECK_EQ(1u, size);
168}
169
170NormalPageMemoryPool::NormalPageMemoryPool() = default;
171
172NormalPageMemoryPool::~NormalPageMemoryPool() = default;
173
174void NormalPageMemoryPool::Add(size_t bucket, NormalPageMemoryRegion* pmr,
175                               Address writeable_base) {
176  DCHECK_LT(bucket, kNumPoolBuckets);
177  pool_[bucket].push_back(std::make_pair(pmr, writeable_base));
178}
179
180std::pair<NormalPageMemoryRegion*, Address> NormalPageMemoryPool::Take(
181    size_t bucket) {
182  DCHECK_LT(bucket, kNumPoolBuckets);
183  if (pool_[bucket].empty()) return {nullptr, nullptr};
184  std::pair<NormalPageMemoryRegion*, Address> pair = pool_[bucket].back();
185  pool_[bucket].pop_back();
186  return pair;
187}
188
189PageBackend::PageBackend(PageAllocator& allocator,
190                         FatalOutOfMemoryHandler& oom_handler)
191    : allocator_(allocator), oom_handler_(oom_handler) {}
192
193PageBackend::~PageBackend() = default;
194
195Address PageBackend::AllocateNormalPageMemory(size_t bucket) {
196  v8::base::MutexGuard guard(&mutex_);
197  std::pair<NormalPageMemoryRegion*, Address> result = page_pool_.Take(bucket);
198  if (!result.first) {
199    auto pmr =
200        std::make_unique<NormalPageMemoryRegion>(allocator_, oom_handler_);
201    for (size_t i = 0; i < NormalPageMemoryRegion::kNumPageRegions; ++i) {
202      page_pool_.Add(bucket, pmr.get(),
203                     pmr->GetPageMemory(i).writeable_region().base());
204    }
205    page_memory_region_tree_.Add(pmr.get());
206    normal_page_memory_regions_.push_back(std::move(pmr));
207    result = page_pool_.Take(bucket);
208    DCHECK(result.first);
209  }
210  result.first->Allocate(result.second);
211  return result.second;
212}
213
214void PageBackend::FreeNormalPageMemory(size_t bucket, Address writeable_base) {
215  v8::base::MutexGuard guard(&mutex_);
216  auto* pmr = static_cast<NormalPageMemoryRegion*>(
217      page_memory_region_tree_.Lookup(writeable_base));
218  pmr->Free(writeable_base);
219  page_pool_.Add(bucket, pmr, writeable_base);
220}
221
222Address PageBackend::AllocateLargePageMemory(size_t size) {
223  v8::base::MutexGuard guard(&mutex_);
224  auto pmr =
225      std::make_unique<LargePageMemoryRegion>(allocator_, oom_handler_, size);
226  const PageMemory pm = pmr->GetPageMemory();
227  Unprotect(allocator_, oom_handler_, pm);
228  page_memory_region_tree_.Add(pmr.get());
229  large_page_memory_regions_.insert(std::make_pair(pmr.get(), std::move(pmr)));
230  return pm.writeable_region().base();
231}
232
233void PageBackend::FreeLargePageMemory(Address writeable_base) {
234  v8::base::MutexGuard guard(&mutex_);
235  PageMemoryRegion* pmr = page_memory_region_tree_.Lookup(writeable_base);
236  page_memory_region_tree_.Remove(pmr);
237  auto size = large_page_memory_regions_.erase(pmr);
238  USE(size);
239  DCHECK_EQ(1u, size);
240}
241
242}  // namespace internal
243}  // namespace cppgc
244