1 // Copyright 2021 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/base/virtual-address-space.h"
6
7 #include "include/v8-platform.h"
8 #include "src/base/bits.h"
9 #include "src/base/platform/platform.h"
10 #include "src/base/platform/wrappers.h"
11
12 namespace v8 {
13 namespace base {
14
15 #define STATIC_ASSERT_ENUM(a, b) \
16 static_assert(static_cast<int>(a) == static_cast<int>(b), \
17 "mismatching enum: " #a)
18
19 STATIC_ASSERT_ENUM(PagePermissions::kNoAccess, OS::MemoryPermission::kNoAccess);
20 STATIC_ASSERT_ENUM(PagePermissions::kReadWrite,
21 OS::MemoryPermission::kReadWrite);
22 STATIC_ASSERT_ENUM(PagePermissions::kReadWriteExecute,
23 OS::MemoryPermission::kReadWriteExecute);
24 STATIC_ASSERT_ENUM(PagePermissions::kReadExecute,
25 OS::MemoryPermission::kReadExecute);
26
27 #undef STATIC_ASSERT_ENUM
28
29 namespace {
PagePermissionsToBitset(PagePermissions permissions)30 uint8_t PagePermissionsToBitset(PagePermissions permissions) {
31 switch (permissions) {
32 case PagePermissions::kNoAccess:
33 return 0b000;
34 case PagePermissions::kRead:
35 return 0b100;
36 case PagePermissions::kReadWrite:
37 return 0b110;
38 case PagePermissions::kReadWriteExecute:
39 return 0b111;
40 case PagePermissions::kReadExecute:
41 return 0b101;
42 }
43 }
44 } // namespace
45
IsSubset(PagePermissions lhs,PagePermissions rhs)46 bool IsSubset(PagePermissions lhs, PagePermissions rhs) {
47 uint8_t lhs_bits = PagePermissionsToBitset(lhs);
48 uint8_t rhs_bits = PagePermissionsToBitset(rhs);
49 return (lhs_bits & rhs_bits) == lhs_bits;
50 }
51
VirtualAddressSpace()52 VirtualAddressSpace::VirtualAddressSpace()
53 : VirtualAddressSpaceBase(OS::CommitPageSize(), OS::AllocatePageSize(),
54 kNullAddress,
55 std::numeric_limits<uintptr_t>::max(),
56 PagePermissions::kReadWriteExecute) {
57 #if V8_OS_WIN
58 // On Windows, this additional step is required to lookup the VirtualAlloc2
59 // and friends functions.
60 OS::EnsureWin32MemoryAPILoaded();
61 #endif // V8_OS_WIN
62 DCHECK(bits::IsPowerOfTwo(page_size()));
63 DCHECK(bits::IsPowerOfTwo(allocation_granularity()));
64 DCHECK_GE(allocation_granularity(), page_size());
65 DCHECK(IsAligned(allocation_granularity(), page_size()));
66 }
67
SetRandomSeed(int64_t seed)68 void VirtualAddressSpace::SetRandomSeed(int64_t seed) {
69 OS::SetRandomMmapSeed(seed);
70 }
71
RandomPageAddress()72 Address VirtualAddressSpace::RandomPageAddress() {
73 return reinterpret_cast<Address>(OS::GetRandomMmapAddr());
74 }
75
AllocatePages(Address hint,size_t size,size_t alignment,PagePermissions permissions)76 Address VirtualAddressSpace::AllocatePages(Address hint, size_t size,
77 size_t alignment,
78 PagePermissions permissions) {
79 DCHECK(IsAligned(alignment, allocation_granularity()));
80 DCHECK(IsAligned(hint, alignment));
81 DCHECK(IsAligned(size, allocation_granularity()));
82
83 return reinterpret_cast<Address>(
84 OS::Allocate(reinterpret_cast<void*>(hint), size, alignment,
85 static_cast<OS::MemoryPermission>(permissions)));
86 }
87
FreePages(Address address,size_t size)88 void VirtualAddressSpace::FreePages(Address address, size_t size) {
89 DCHECK(IsAligned(address, allocation_granularity()));
90 DCHECK(IsAligned(size, allocation_granularity()));
91
92 OS::Free(reinterpret_cast<void*>(address), size);
93 }
94
SetPagePermissions(Address address,size_t size,PagePermissions permissions)95 bool VirtualAddressSpace::SetPagePermissions(Address address, size_t size,
96 PagePermissions permissions) {
97 DCHECK(IsAligned(address, page_size()));
98 DCHECK(IsAligned(size, page_size()));
99
100 return OS::SetPermissions(reinterpret_cast<void*>(address), size,
101 static_cast<OS::MemoryPermission>(permissions));
102 }
103
AllocateGuardRegion(Address address,size_t size)104 bool VirtualAddressSpace::AllocateGuardRegion(Address address, size_t size) {
105 DCHECK(IsAligned(address, allocation_granularity()));
106 DCHECK(IsAligned(size, allocation_granularity()));
107
108 void* hint = reinterpret_cast<void*>(address);
109 void* result = OS::Allocate(hint, size, allocation_granularity(),
110 OS::MemoryPermission::kNoAccess);
111 if (result && result != hint) {
112 OS::Free(result, size);
113 }
114 return result == hint;
115 }
116
FreeGuardRegion(Address address,size_t size)117 void VirtualAddressSpace::FreeGuardRegion(Address address, size_t size) {
118 DCHECK(IsAligned(address, allocation_granularity()));
119 DCHECK(IsAligned(size, allocation_granularity()));
120
121 OS::Free(reinterpret_cast<void*>(address), size);
122 }
123
CanAllocateSubspaces()124 bool VirtualAddressSpace::CanAllocateSubspaces() {
125 return OS::CanReserveAddressSpace();
126 }
127
AllocateSharedPages(Address hint,size_t size,PagePermissions permissions,PlatformSharedMemoryHandle handle,uint64_t offset)128 Address VirtualAddressSpace::AllocateSharedPages(
129 Address hint, size_t size, PagePermissions permissions,
130 PlatformSharedMemoryHandle handle, uint64_t offset) {
131 DCHECK(IsAligned(hint, allocation_granularity()));
132 DCHECK(IsAligned(size, allocation_granularity()));
133 DCHECK(IsAligned(offset, allocation_granularity()));
134
135 return reinterpret_cast<Address>(OS::AllocateShared(
136 reinterpret_cast<void*>(hint), size,
137 static_cast<OS::MemoryPermission>(permissions), handle, offset));
138 }
139
FreeSharedPages(Address address,size_t size)140 void VirtualAddressSpace::FreeSharedPages(Address address, size_t size) {
141 DCHECK(IsAligned(address, allocation_granularity()));
142 DCHECK(IsAligned(size, allocation_granularity()));
143
144 OS::FreeShared(reinterpret_cast<void*>(address), size);
145 }
146
AllocateSubspace(Address hint,size_t size,size_t alignment,PagePermissions max_page_permissions)147 std::unique_ptr<v8::VirtualAddressSpace> VirtualAddressSpace::AllocateSubspace(
148 Address hint, size_t size, size_t alignment,
149 PagePermissions max_page_permissions) {
150 DCHECK(IsAligned(alignment, allocation_granularity()));
151 DCHECK(IsAligned(hint, alignment));
152 DCHECK(IsAligned(size, allocation_granularity()));
153
154 base::Optional<AddressSpaceReservation> reservation =
155 OS::CreateAddressSpaceReservation(
156 reinterpret_cast<void*>(hint), size, alignment,
157 static_cast<OS::MemoryPermission>(max_page_permissions));
158 if (!reservation.has_value())
159 return std::unique_ptr<v8::VirtualAddressSpace>();
160 return std::unique_ptr<v8::VirtualAddressSpace>(
161 new VirtualAddressSubspace(*reservation, this, max_page_permissions));
162 }
163
DiscardSystemPages(Address address,size_t size)164 bool VirtualAddressSpace::DiscardSystemPages(Address address, size_t size) {
165 DCHECK(IsAligned(address, page_size()));
166 DCHECK(IsAligned(size, page_size()));
167
168 return OS::DiscardSystemPages(reinterpret_cast<void*>(address), size);
169 }
170
DecommitPages(Address address,size_t size)171 bool VirtualAddressSpace::DecommitPages(Address address, size_t size) {
172 DCHECK(IsAligned(address, page_size()));
173 DCHECK(IsAligned(size, page_size()));
174
175 return OS::DecommitPages(reinterpret_cast<void*>(address), size);
176 }
177
FreeSubspace(VirtualAddressSubspace * subspace)178 void VirtualAddressSpace::FreeSubspace(VirtualAddressSubspace* subspace) {
179 OS::FreeAddressSpaceReservation(subspace->reservation_);
180 }
181
VirtualAddressSubspace(AddressSpaceReservation reservation,VirtualAddressSpaceBase * parent_space,PagePermissions max_page_permissions)182 VirtualAddressSubspace::VirtualAddressSubspace(
183 AddressSpaceReservation reservation, VirtualAddressSpaceBase* parent_space,
184 PagePermissions max_page_permissions)
185 : VirtualAddressSpaceBase(parent_space->page_size(),
186 parent_space->allocation_granularity(),
187 reinterpret_cast<Address>(reservation.base()),
188 reservation.size(), max_page_permissions),
189 reservation_(reservation),
190 region_allocator_(reinterpret_cast<Address>(reservation.base()),
191 reservation.size(),
192 parent_space->allocation_granularity()),
193 parent_space_(parent_space) {
194 #if V8_OS_WIN
195 // On Windows, the address space reservation needs to be split and merged at
196 // the OS level as well.
197 region_allocator_.set_on_split_callback([this](Address start, size_t size) {
198 DCHECK(IsAligned(start, allocation_granularity()));
199 CHECK(reservation_.SplitPlaceholder(reinterpret_cast<void*>(start), size));
200 });
201 region_allocator_.set_on_merge_callback([this](Address start, size_t size) {
202 DCHECK(IsAligned(start, allocation_granularity()));
203 CHECK(reservation_.MergePlaceholders(reinterpret_cast<void*>(start), size));
204 });
205 #endif // V8_OS_WIN
206 }
207
~VirtualAddressSubspace()208 VirtualAddressSubspace::~VirtualAddressSubspace() {
209 parent_space_->FreeSubspace(this);
210 }
211
SetRandomSeed(int64_t seed)212 void VirtualAddressSubspace::SetRandomSeed(int64_t seed) {
213 MutexGuard guard(&mutex_);
214 rng_.SetSeed(seed);
215 }
216
RandomPageAddress()217 Address VirtualAddressSubspace::RandomPageAddress() {
218 MutexGuard guard(&mutex_);
219 // Note: the random numbers generated here aren't uniformly distributed if the
220 // size isn't a power of two.
221 Address addr = base() + (static_cast<uint64_t>(rng_.NextInt64()) % size());
222 return RoundDown(addr, allocation_granularity());
223 }
224
AllocatePages(Address hint,size_t size,size_t alignment,PagePermissions permissions)225 Address VirtualAddressSubspace::AllocatePages(Address hint, size_t size,
226 size_t alignment,
227 PagePermissions permissions) {
228 DCHECK(IsAligned(alignment, allocation_granularity()));
229 DCHECK(IsAligned(hint, alignment));
230 DCHECK(IsAligned(size, allocation_granularity()));
231 DCHECK(IsSubset(permissions, max_page_permissions()));
232
233 MutexGuard guard(&mutex_);
234
235 Address address = region_allocator_.AllocateRegion(hint, size, alignment);
236 if (address == RegionAllocator::kAllocationFailure) return kNullAddress;
237
238 if (!reservation_.Allocate(reinterpret_cast<void*>(address), size,
239 static_cast<OS::MemoryPermission>(permissions))) {
240 // This most likely means that we ran out of memory.
241 CHECK_EQ(size, region_allocator_.FreeRegion(address));
242 return kNullAddress;
243 }
244
245 return address;
246 }
247
FreePages(Address address,size_t size)248 void VirtualAddressSubspace::FreePages(Address address, size_t size) {
249 DCHECK(IsAligned(address, allocation_granularity()));
250 DCHECK(IsAligned(size, allocation_granularity()));
251
252 MutexGuard guard(&mutex_);
253 // The order here is important: on Windows, the allocation first has to be
254 // freed to a placeholder before the placeholder can be merged (during the
255 // merge_callback) with any surrounding placeholder mappings.
256 CHECK(reservation_.Free(reinterpret_cast<void*>(address), size));
257 CHECK_EQ(size, region_allocator_.FreeRegion(address));
258 }
259
SetPagePermissions(Address address,size_t size,PagePermissions permissions)260 bool VirtualAddressSubspace::SetPagePermissions(Address address, size_t size,
261 PagePermissions permissions) {
262 DCHECK(IsAligned(address, page_size()));
263 DCHECK(IsAligned(size, page_size()));
264 DCHECK(IsSubset(permissions, max_page_permissions()));
265
266 return reservation_.SetPermissions(
267 reinterpret_cast<void*>(address), size,
268 static_cast<OS::MemoryPermission>(permissions));
269 }
270
AllocateGuardRegion(Address address,size_t size)271 bool VirtualAddressSubspace::AllocateGuardRegion(Address address, size_t size) {
272 DCHECK(IsAligned(address, allocation_granularity()));
273 DCHECK(IsAligned(size, allocation_granularity()));
274
275 MutexGuard guard(&mutex_);
276
277 // It is guaranteed that reserved address space is inaccessible, so we just
278 // need to mark the region as in-use in the region allocator.
279 return region_allocator_.AllocateRegionAt(address, size);
280 }
281
FreeGuardRegion(Address address,size_t size)282 void VirtualAddressSubspace::FreeGuardRegion(Address address, size_t size) {
283 DCHECK(IsAligned(address, allocation_granularity()));
284 DCHECK(IsAligned(size, allocation_granularity()));
285
286 MutexGuard guard(&mutex_);
287 CHECK_EQ(size, region_allocator_.FreeRegion(address));
288 }
289
AllocateSharedPages(Address hint,size_t size,PagePermissions permissions,PlatformSharedMemoryHandle handle,uint64_t offset)290 Address VirtualAddressSubspace::AllocateSharedPages(
291 Address hint, size_t size, PagePermissions permissions,
292 PlatformSharedMemoryHandle handle, uint64_t offset) {
293 DCHECK(IsAligned(hint, allocation_granularity()));
294 DCHECK(IsAligned(size, allocation_granularity()));
295 DCHECK(IsAligned(offset, allocation_granularity()));
296
297 MutexGuard guard(&mutex_);
298
299 Address address =
300 region_allocator_.AllocateRegion(hint, size, allocation_granularity());
301 if (address == RegionAllocator::kAllocationFailure) return kNullAddress;
302
303 if (!reservation_.AllocateShared(
304 reinterpret_cast<void*>(address), size,
305 static_cast<OS::MemoryPermission>(permissions), handle, offset)) {
306 CHECK_EQ(size, region_allocator_.FreeRegion(address));
307 return kNullAddress;
308 }
309
310 return address;
311 }
312
FreeSharedPages(Address address,size_t size)313 void VirtualAddressSubspace::FreeSharedPages(Address address, size_t size) {
314 DCHECK(IsAligned(address, allocation_granularity()));
315 DCHECK(IsAligned(size, allocation_granularity()));
316
317 MutexGuard guard(&mutex_);
318 // The order here is important: on Windows, the allocation first has to be
319 // freed to a placeholder before the placeholder can be merged (during the
320 // merge_callback) with any surrounding placeholder mappings.
321 CHECK(reservation_.FreeShared(reinterpret_cast<void*>(address), size));
322 CHECK_EQ(size, region_allocator_.FreeRegion(address));
323 }
324
325 std::unique_ptr<v8::VirtualAddressSpace>
AllocateSubspace(Address hint,size_t size,size_t alignment,PagePermissions max_page_permissions)326 VirtualAddressSubspace::AllocateSubspace(Address hint, size_t size,
327 size_t alignment,
328 PagePermissions max_page_permissions) {
329 DCHECK(IsAligned(alignment, allocation_granularity()));
330 DCHECK(IsAligned(hint, alignment));
331 DCHECK(IsAligned(size, allocation_granularity()));
332 DCHECK(IsSubset(max_page_permissions, this->max_page_permissions()));
333
334 MutexGuard guard(&mutex_);
335
336 Address address = region_allocator_.AllocateRegion(hint, size, alignment);
337 if (address == RegionAllocator::kAllocationFailure) {
338 return std::unique_ptr<v8::VirtualAddressSpace>();
339 }
340
341 base::Optional<AddressSpaceReservation> reservation =
342 reservation_.CreateSubReservation(
343 reinterpret_cast<void*>(address), size,
344 static_cast<OS::MemoryPermission>(max_page_permissions));
345 if (!reservation.has_value()) {
346 CHECK_EQ(size, region_allocator_.FreeRegion(address));
347 return nullptr;
348 }
349 return std::unique_ptr<v8::VirtualAddressSpace>(
350 new VirtualAddressSubspace(*reservation, this, max_page_permissions));
351 }
352
DiscardSystemPages(Address address,size_t size)353 bool VirtualAddressSubspace::DiscardSystemPages(Address address, size_t size) {
354 DCHECK(IsAligned(address, page_size()));
355 DCHECK(IsAligned(size, page_size()));
356
357 return reservation_.DiscardSystemPages(reinterpret_cast<void*>(address),
358 size);
359 }
360
DecommitPages(Address address,size_t size)361 bool VirtualAddressSubspace::DecommitPages(Address address, size_t size) {
362 DCHECK(IsAligned(address, page_size()));
363 DCHECK(IsAligned(size, page_size()));
364
365 return reservation_.DecommitPages(reinterpret_cast<void*>(address), size);
366 }
367
FreeSubspace(VirtualAddressSubspace * subspace)368 void VirtualAddressSubspace::FreeSubspace(VirtualAddressSubspace* subspace) {
369 MutexGuard guard(&mutex_);
370
371 AddressSpaceReservation reservation = subspace->reservation_;
372 Address base = reinterpret_cast<Address>(reservation.base());
373 CHECK_EQ(reservation.size(), region_allocator_.FreeRegion(base));
374 CHECK(reservation_.FreeSubReservation(reservation));
375 }
376
377 } // namespace base
378 } // namespace v8
379