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/emulated-virtual-address-subspace.h"
6
7 #include "src/base/bits.h"
8 #include "src/base/platform/platform.h"
9 #include "src/base/platform/wrappers.h"
10
11 namespace v8 {
12 namespace base {
13
EmulatedVirtualAddressSubspace(VirtualAddressSpace * parent_space,Address base,size_t mapped_size,size_t total_size)14 EmulatedVirtualAddressSubspace::EmulatedVirtualAddressSubspace(
15 VirtualAddressSpace* parent_space, Address base, size_t mapped_size,
16 size_t total_size)
17 : VirtualAddressSpace(parent_space->page_size(),
18 parent_space->allocation_granularity(), base,
19 total_size, parent_space->max_page_permissions()),
20 mapped_size_(mapped_size),
21 parent_space_(parent_space),
22 region_allocator_(base, mapped_size, parent_space_->page_size()) {
23 // For simplicity, we currently require both the mapped and total size to be
24 // a power of two. This simplifies some things later on, for example, random
25 // addresses can be generated with a simply bitmask, and will then be inside
26 // the unmapped space with a probability >= 50% (mapped size == unmapped
27 // size) or never (mapped size == total size).
28 DCHECK(base::bits::IsPowerOfTwo(mapped_size));
29 DCHECK(base::bits::IsPowerOfTwo(total_size));
30 }
31
~EmulatedVirtualAddressSubspace()32 EmulatedVirtualAddressSubspace::~EmulatedVirtualAddressSubspace() {
33 parent_space_->FreePages(base(), mapped_size_);
34 }
35
SetRandomSeed(int64_t seed)36 void EmulatedVirtualAddressSubspace::SetRandomSeed(int64_t seed) {
37 MutexGuard guard(&mutex_);
38 rng_.SetSeed(seed);
39 }
40
RandomPageAddress()41 Address EmulatedVirtualAddressSubspace::RandomPageAddress() {
42 MutexGuard guard(&mutex_);
43 Address addr = base() + (static_cast<uint64_t>(rng_.NextInt64()) % size());
44 return RoundDown(addr, allocation_granularity());
45 }
46
AllocatePages(Address hint,size_t size,size_t alignment,PagePermissions permissions)47 Address EmulatedVirtualAddressSubspace::AllocatePages(
48 Address hint, size_t size, size_t alignment, PagePermissions permissions) {
49 if (hint == kNoHint || MappedRegionContains(hint, size)) {
50 MutexGuard guard(&mutex_);
51
52 // Attempt to find a region in the mapped region.
53 Address address = region_allocator_.AllocateRegion(hint, size, alignment);
54 if (address != RegionAllocator::kAllocationFailure) {
55 // Success. Only need to adjust the page permissions.
56 if (parent_space_->SetPagePermissions(address, size, permissions)) {
57 return address;
58 }
59 // Probably ran out of memory, but still try to allocate in the unmapped
60 // space.
61 CHECK_EQ(size, region_allocator_.FreeRegion(address));
62 }
63 }
64
65 // No luck or hint is outside of the mapped region. Try to allocate pages in
66 // the unmapped space using page allocation hints instead.
67 if (!IsUsableSizeForUnmappedRegion(size)) return kNullAddress;
68
69 static constexpr int kMaxAttempts = 10;
70 for (int i = 0; i < kMaxAttempts; i++) {
71 // If an unmapped region exists, it must cover at least 50% of the whole
72 // space (unmapped + mapped region). Since we limit the size of allocation
73 // to 50% of the unmapped region (see IsUsableSizeForUnmappedRegion), a
74 // random page address has at least a 25% chance of being a usable base. As
75 // such, this loop should usually terminate quickly.
76 DCHECK_GE(unmapped_size(), mapped_size());
77 while (!UnmappedRegionContains(hint, size)) {
78 hint = RandomPageAddress();
79 }
80 hint = RoundDown(hint, alignment);
81
82 const Address result =
83 parent_space_->AllocatePages(hint, size, alignment, permissions);
84 if (UnmappedRegionContains(result, size)) {
85 return result;
86 } else if (result) {
87 parent_space_->FreePages(result, size);
88 }
89
90 // Retry at a different address.
91 hint = RandomPageAddress();
92 }
93
94 return kNullAddress;
95 }
96
FreePages(Address address,size_t size)97 void EmulatedVirtualAddressSubspace::FreePages(Address address, size_t size) {
98 if (MappedRegionContains(address, size)) {
99 MutexGuard guard(&mutex_);
100 CHECK_EQ(size, region_allocator_.FreeRegion(address));
101 CHECK(parent_space_->DecommitPages(address, size));
102 } else {
103 DCHECK(UnmappedRegionContains(address, size));
104 parent_space_->FreePages(address, size);
105 }
106 }
107
AllocateSharedPages(Address hint,size_t size,PagePermissions permissions,PlatformSharedMemoryHandle handle,uint64_t offset)108 Address EmulatedVirtualAddressSubspace::AllocateSharedPages(
109 Address hint, size_t size, PagePermissions permissions,
110 PlatformSharedMemoryHandle handle, uint64_t offset) {
111 // Can only allocate shared pages in the unmapped region.
112 if (!IsUsableSizeForUnmappedRegion(size)) return kNullAddress;
113
114 static constexpr int kMaxAttempts = 10;
115 for (int i = 0; i < kMaxAttempts; i++) {
116 // See AllocatePages() for why this loop usually terminates quickly.
117 DCHECK_GE(unmapped_size(), mapped_size());
118 while (!UnmappedRegionContains(hint, size)) {
119 hint = RandomPageAddress();
120 }
121
122 Address region = parent_space_->AllocateSharedPages(hint, size, permissions,
123 handle, offset);
124 if (UnmappedRegionContains(region, size)) {
125 return region;
126 } else if (region) {
127 parent_space_->FreeSharedPages(region, size);
128 }
129
130 hint = RandomPageAddress();
131 }
132
133 return kNullAddress;
134 }
135
FreeSharedPages(Address address,size_t size)136 void EmulatedVirtualAddressSubspace::FreeSharedPages(Address address,
137 size_t size) {
138 DCHECK(UnmappedRegionContains(address, size));
139 parent_space_->FreeSharedPages(address, size);
140 }
141
SetPagePermissions(Address address,size_t size,PagePermissions permissions)142 bool EmulatedVirtualAddressSubspace::SetPagePermissions(
143 Address address, size_t size, PagePermissions permissions) {
144 DCHECK(Contains(address, size));
145 return parent_space_->SetPagePermissions(address, size, permissions);
146 }
147
AllocateGuardRegion(Address address,size_t size)148 bool EmulatedVirtualAddressSubspace::AllocateGuardRegion(Address address,
149 size_t size) {
150 if (MappedRegionContains(address, size)) {
151 MutexGuard guard(&mutex_);
152 return region_allocator_.AllocateRegionAt(address, size);
153 }
154 if (!UnmappedRegionContains(address, size)) return false;
155 return parent_space_->AllocateGuardRegion(address, size);
156 }
157
FreeGuardRegion(Address address,size_t size)158 void EmulatedVirtualAddressSubspace::FreeGuardRegion(Address address,
159 size_t size) {
160 if (MappedRegionContains(address, size)) {
161 MutexGuard guard(&mutex_);
162 CHECK_EQ(size, region_allocator_.FreeRegion(address));
163 } else {
164 DCHECK(UnmappedRegionContains(address, size));
165 parent_space_->FreeGuardRegion(address, size);
166 }
167 }
168
CanAllocateSubspaces()169 bool EmulatedVirtualAddressSubspace::CanAllocateSubspaces() {
170 // This is not supported, mostly because it's not (yet) needed in practice.
171 return false;
172 }
173
174 std::unique_ptr<v8::VirtualAddressSpace>
AllocateSubspace(Address hint,size_t size,size_t alignment,PagePermissions max_page_permissions)175 EmulatedVirtualAddressSubspace::AllocateSubspace(
176 Address hint, size_t size, size_t alignment,
177 PagePermissions max_page_permissions) {
178 UNREACHABLE();
179 }
180
DiscardSystemPages(Address address,size_t size)181 bool EmulatedVirtualAddressSubspace::DiscardSystemPages(Address address,
182 size_t size) {
183 DCHECK(Contains(address, size));
184 return parent_space_->DiscardSystemPages(address, size);
185 }
186
DecommitPages(Address address,size_t size)187 bool EmulatedVirtualAddressSubspace::DecommitPages(Address address,
188 size_t size) {
189 DCHECK(Contains(address, size));
190 return parent_space_->DecommitPages(address, size);
191 }
192
193 } // namespace base
194 } // namespace v8
195