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 #ifndef V8_HEAP_CPPGC_PAGE_MEMORY_H_
6 #define V8_HEAP_CPPGC_PAGE_MEMORY_H_
7
8 #include <array>
9 #include <map>
10 #include <memory>
11 #include <unordered_map>
12 #include <vector>
13
14 #include "include/cppgc/platform.h"
15 #include "src/base/macros.h"
16 #include "src/base/platform/mutex.h"
17 #include "src/heap/cppgc/globals.h"
18
19 namespace cppgc {
20 namespace internal {
21
22 class FatalOutOfMemoryHandler;
23
24 class V8_EXPORT_PRIVATE MemoryRegion final {
25 public:
26 MemoryRegion() = default;
MemoryRegion(Address base, size_t size)27 MemoryRegion(Address base, size_t size) : base_(base), size_(size) {
28 DCHECK(base);
29 DCHECK_LT(0u, size);
30 }
31
base() const32 Address base() const { return base_; }
size() const33 size_t size() const { return size_; }
end() const34 Address end() const { return base_ + size_; }
35
Contains(ConstAddress addr) const36 bool Contains(ConstAddress addr) const {
37 return (reinterpret_cast<uintptr_t>(addr) -
38 reinterpret_cast<uintptr_t>(base_)) < size_;
39 }
40
Contains(const MemoryRegion& other) const41 bool Contains(const MemoryRegion& other) const {
42 return base_ <= other.base() && other.end() <= end();
43 }
44
45 private:
46 Address base_ = nullptr;
47 size_t size_ = 0;
48 };
49
50 // PageMemory provides the backing of a single normal or large page.
51 class V8_EXPORT_PRIVATE PageMemory final {
52 public:
PageMemory(MemoryRegion overall, MemoryRegion writeable)53 PageMemory(MemoryRegion overall, MemoryRegion writeable)
54 : overall_(overall), writable_(writeable) {
55 DCHECK(overall.Contains(writeable));
56 }
57
writeable_region() const58 const MemoryRegion writeable_region() const { return writable_; }
overall_region() const59 const MemoryRegion overall_region() const { return overall_; }
60
61 private:
62 MemoryRegion overall_;
63 MemoryRegion writable_;
64 };
65
66 class V8_EXPORT_PRIVATE PageMemoryRegion {
67 public:
68 virtual ~PageMemoryRegion();
69
reserved_region() const70 const MemoryRegion reserved_region() const { return reserved_region_; }
is_large() const71 bool is_large() const { return is_large_; }
72
73 // Lookup writeable base for an |address| that's contained in
74 // PageMemoryRegion. Filters out addresses that are contained in non-writeable
75 // regions (e.g. guard pages).
76 inline Address Lookup(ConstAddress address) const;
77
78 // Disallow copy/move.
79 PageMemoryRegion(const PageMemoryRegion&) = delete;
80 PageMemoryRegion& operator=(const PageMemoryRegion&) = delete;
81
82 virtual void UnprotectForTesting() = 0;
83
84 protected:
85 PageMemoryRegion(PageAllocator&, FatalOutOfMemoryHandler&, MemoryRegion,
86 bool);
87
88 PageAllocator& allocator_;
89 FatalOutOfMemoryHandler& oom_handler_;
90 const MemoryRegion reserved_region_;
91 const bool is_large_;
92 };
93
94 // NormalPageMemoryRegion serves kNumPageRegions normal-sized PageMemory object.
95 class V8_EXPORT_PRIVATE NormalPageMemoryRegion final : public PageMemoryRegion {
96 public:
97 static constexpr size_t kNumPageRegions = 10;
98
99 NormalPageMemoryRegion(PageAllocator&, FatalOutOfMemoryHandler&);
100 ~NormalPageMemoryRegion() override;
101
GetPageMemory(size_t index) const102 const PageMemory GetPageMemory(size_t index) const {
103 DCHECK_LT(index, kNumPageRegions);
104 return PageMemory(
105 MemoryRegion(reserved_region().base() + kPageSize * index, kPageSize),
106 MemoryRegion(
107 reserved_region().base() + kPageSize * index + kGuardPageSize,
108 kPageSize - 2 * kGuardPageSize));
109 }
110
111 // Allocates a normal page at |writeable_base| address. Changes page
112 // protection.
113 void Allocate(Address writeable_base);
114
115 // Frees a normal page at at |writeable_base| address. Changes page
116 // protection.
117 void Free(Address);
118
119 inline Address Lookup(ConstAddress) const;
120
121 void UnprotectForTesting() final;
122
123 private:
ChangeUsed(size_t index, bool value)124 void ChangeUsed(size_t index, bool value) {
125 DCHECK_LT(index, kNumPageRegions);
126 DCHECK_EQ(value, !page_memories_in_use_[index]);
127 page_memories_in_use_[index] = value;
128 }
129
GetIndex(ConstAddress address) const130 size_t GetIndex(ConstAddress address) const {
131 return static_cast<size_t>(address - reserved_region().base()) >>
132 kPageSizeLog2;
133 }
134
135 std::array<bool, kNumPageRegions> page_memories_in_use_ = {};
136 };
137
138 // LargePageMemoryRegion serves a single large PageMemory object.
139 class V8_EXPORT_PRIVATE LargePageMemoryRegion final : public PageMemoryRegion {
140 public:
141 LargePageMemoryRegion(PageAllocator&, FatalOutOfMemoryHandler&, size_t);
142 ~LargePageMemoryRegion() override;
143
GetPageMemory() const144 const PageMemory GetPageMemory() const {
145 return PageMemory(
146 MemoryRegion(reserved_region().base(), reserved_region().size()),
147 MemoryRegion(reserved_region().base() + kGuardPageSize,
148 reserved_region().size() - 2 * kGuardPageSize));
149 }
150
151 inline Address Lookup(ConstAddress) const;
152
153 void UnprotectForTesting() final;
154 };
155
156 // A PageMemoryRegionTree is a binary search tree of PageMemoryRegions sorted
157 // by reserved base addresses.
158 //
159 // The tree does not keep its elements alive but merely provides indexing
160 // capabilities.
161 class V8_EXPORT_PRIVATE PageMemoryRegionTree final {
162 public:
163 PageMemoryRegionTree();
164 ~PageMemoryRegionTree();
165
166 void Add(PageMemoryRegion*);
167 void Remove(PageMemoryRegion*);
168
169 inline PageMemoryRegion* Lookup(ConstAddress) const;
170
171 private:
172 std::map<ConstAddress, PageMemoryRegion*> set_;
173 };
174
175 // A pool of PageMemory objects represented by the writeable base addresses.
176 //
177 // The pool does not keep its elements alive but merely provides pooling
178 // capabilities.
179 class V8_EXPORT_PRIVATE NormalPageMemoryPool final {
180 public:
181 static constexpr size_t kNumPoolBuckets = 16;
182
183 using Result = std::pair<NormalPageMemoryRegion*, Address>;
184
185 NormalPageMemoryPool();
186 ~NormalPageMemoryPool();
187
188 void Add(size_t, NormalPageMemoryRegion*, Address);
189 Result Take(size_t);
190
191 private:
192 std::vector<Result> pool_[kNumPoolBuckets];
193 };
194
195 // A backend that is used for allocating and freeing normal and large pages.
196 //
197 // Internally maintaints a set of PageMemoryRegions. The backend keeps its used
198 // regions alive.
199 class V8_EXPORT_PRIVATE PageBackend final {
200 public:
201 PageBackend(PageAllocator&, FatalOutOfMemoryHandler&);
202 ~PageBackend();
203
204 // Allocates a normal page from the backend.
205 //
206 // Returns the writeable base of the region.
207 Address AllocateNormalPageMemory(size_t);
208
209 // Returns normal page memory back to the backend. Expects the
210 // |writeable_base| returned by |AllocateNormalMemory()|.
211 void FreeNormalPageMemory(size_t, Address writeable_base);
212
213 // Allocates a large page from the backend.
214 //
215 // Returns the writeable base of the region.
216 Address AllocateLargePageMemory(size_t size);
217
218 // Returns large page memory back to the backend. Expects the |writeable_base|
219 // returned by |AllocateLargePageMemory()|.
220 void FreeLargePageMemory(Address writeable_base);
221
222 // Returns the writeable base if |address| is contained in a valid page
223 // memory.
224 inline Address Lookup(ConstAddress) const;
225
226 // Disallow copy/move.
227 PageBackend(const PageBackend&) = delete;
228 PageBackend& operator=(const PageBackend&) = delete;
229
230 private:
231 // Guards against concurrent uses of `Lookup()`.
232 mutable v8::base::Mutex mutex_;
233 PageAllocator& allocator_;
234 FatalOutOfMemoryHandler& oom_handler_;
235 NormalPageMemoryPool page_pool_;
236 PageMemoryRegionTree page_memory_region_tree_;
237 std::vector<std::unique_ptr<PageMemoryRegion>> normal_page_memory_regions_;
238 std::unordered_map<PageMemoryRegion*, std::unique_ptr<PageMemoryRegion>>
239 large_page_memory_regions_;
240 };
241
242 // Returns true if the provided allocator supports committing at the required
243 // granularity.
SupportsCommittingGuardPages(PageAllocator& allocator)244 inline bool SupportsCommittingGuardPages(PageAllocator& allocator) {
245 return kGuardPageSize != 0 &&
246 kGuardPageSize % allocator.CommitPageSize() == 0;
247 }
248
Lookup(ConstAddress address) const249 Address NormalPageMemoryRegion::Lookup(ConstAddress address) const {
250 size_t index = GetIndex(address);
251 if (!page_memories_in_use_[index]) return nullptr;
252 const MemoryRegion writeable_region = GetPageMemory(index).writeable_region();
253 return writeable_region.Contains(address) ? writeable_region.base() : nullptr;
254 }
255
Lookup(ConstAddress address) const256 Address LargePageMemoryRegion::Lookup(ConstAddress address) const {
257 const MemoryRegion writeable_region = GetPageMemory().writeable_region();
258 return writeable_region.Contains(address) ? writeable_region.base() : nullptr;
259 }
260
Lookup(ConstAddress address) const261 Address PageMemoryRegion::Lookup(ConstAddress address) const {
262 DCHECK(reserved_region().Contains(address));
263 return is_large()
264 ? static_cast<const LargePageMemoryRegion*>(this)->Lookup(address)
265 : static_cast<const NormalPageMemoryRegion*>(this)->Lookup(
266 address);
267 }
268
Lookup(ConstAddress address) const269 PageMemoryRegion* PageMemoryRegionTree::Lookup(ConstAddress address) const {
270 auto it = set_.upper_bound(address);
271 // This check also covers set_.size() > 0, since for empty vectors it is
272 // guaranteed that begin() == end().
273 if (it == set_.begin()) return nullptr;
274 auto* result = std::next(it, -1)->second;
275 if (address < result->reserved_region().end()) return result;
276 return nullptr;
277 }
278
Lookup(ConstAddress address) const279 Address PageBackend::Lookup(ConstAddress address) const {
280 v8::base::MutexGuard guard(&mutex_);
281 PageMemoryRegion* pmr = page_memory_region_tree_.Lookup(address);
282 return pmr ? pmr->Lookup(address) : nullptr;
283 }
284
285 } // namespace internal
286 } // namespace cppgc
287
288 #endif // V8_HEAP_CPPGC_PAGE_MEMORY_H_
289