1 // Copyright 2018 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/region-allocator.h"
6
7 #include "src/base/bits.h"
8 #include "src/base/logging.h"
9 #include "src/base/macros.h"
10
11 namespace v8 {
12 namespace base {
13
14 // If |free_size| < |region_size| * |kMaxLoadFactorForRandomization| stop trying
15 // to randomize region allocation.
16 constexpr double kMaxLoadFactorForRandomization = 0.40;
17
18 // Max number of attempts to allocate page at random address.
19 constexpr int kMaxRandomizationAttempts = 3;
20
RegionAllocator(Address memory_region_begin,size_t memory_region_size,size_t page_size)21 RegionAllocator::RegionAllocator(Address memory_region_begin,
22 size_t memory_region_size, size_t page_size)
23 : whole_region_(memory_region_begin, memory_region_size,
24 RegionState::kFree),
25 region_size_in_pages_(size() / page_size),
26 max_load_for_randomization_(
27 static_cast<size_t>(size() * kMaxLoadFactorForRandomization)),
28 free_size_(0),
29 page_size_(page_size) {
30 CHECK_LT(begin(), end());
31 CHECK(base::bits::IsPowerOfTwo(page_size_));
32 CHECK(IsAligned(size(), page_size_));
33 CHECK(IsAligned(begin(), page_size_));
34
35 // Initial region.
36 Region* region = new Region(whole_region_);
37
38 all_regions_.insert(region);
39
40 FreeListAddRegion(region);
41 }
42
~RegionAllocator()43 RegionAllocator::~RegionAllocator() {
44 // TODO(chromium:1218005) either (D)CHECK that all allocated regions have
45 // been freed again (and thus merged into a single region) or do that now.
46 for (Region* region : all_regions_) {
47 delete region;
48 }
49 }
50
FindRegion(Address address)51 RegionAllocator::AllRegionsSet::iterator RegionAllocator::FindRegion(
52 Address address) {
53 if (!whole_region_.contains(address)) return all_regions_.end();
54
55 Region key(address, 0, RegionState::kFree);
56 AllRegionsSet::iterator iter = all_regions_.upper_bound(&key);
57 // Regions in |all_regions_| are compared by end() values and key's end()
58 // points exactly to the address we are querying, so the upper_bound will
59 // find the region whose |end()| is greater than the requested address.
60 DCHECK_NE(iter, all_regions_.end());
61 DCHECK((*iter)->contains(address));
62 return iter;
63 }
64
FreeListAddRegion(Region * region)65 void RegionAllocator::FreeListAddRegion(Region* region) {
66 free_size_ += region->size();
67 free_regions_.insert(region);
68 }
69
FreeListFindRegion(size_t size)70 RegionAllocator::Region* RegionAllocator::FreeListFindRegion(size_t size) {
71 Region key(0, size, RegionState::kFree);
72 auto iter = free_regions_.lower_bound(&key);
73 return iter == free_regions_.end() ? nullptr : *iter;
74 }
75
FreeListRemoveRegion(Region * region)76 void RegionAllocator::FreeListRemoveRegion(Region* region) {
77 DCHECK(region->is_free());
78 auto iter = free_regions_.find(region);
79 DCHECK_NE(iter, free_regions_.end());
80 DCHECK_EQ(region, *iter);
81 DCHECK_LE(region->size(), free_size_);
82 free_size_ -= region->size();
83 free_regions_.erase(iter);
84 }
85
Split(Region * region,size_t new_size)86 RegionAllocator::Region* RegionAllocator::Split(Region* region,
87 size_t new_size) {
88 DCHECK(IsAligned(new_size, page_size_));
89 DCHECK_NE(new_size, 0);
90 DCHECK_GT(region->size(), new_size);
91
92 if (on_split_) on_split_(region->begin(), new_size);
93
94 // Create new region and put it to the lists after the |region|.
95 DCHECK(!region->is_excluded());
96 RegionState state = region->state();
97 Region* new_region =
98 new Region(region->begin() + new_size, region->size() - new_size, state);
99 if (state == RegionState::kFree) {
100 // Remove region from the free list before updating it's size.
101 FreeListRemoveRegion(region);
102 }
103 region->set_size(new_size);
104
105 all_regions_.insert(new_region);
106
107 if (state == RegionState::kFree) {
108 FreeListAddRegion(region);
109 FreeListAddRegion(new_region);
110 }
111 return new_region;
112 }
113
Merge(AllRegionsSet::iterator prev_iter,AllRegionsSet::iterator next_iter)114 void RegionAllocator::Merge(AllRegionsSet::iterator prev_iter,
115 AllRegionsSet::iterator next_iter) {
116 Region* prev = *prev_iter;
117 Region* next = *next_iter;
118 DCHECK_EQ(prev->end(), next->begin());
119
120 if (on_merge_) on_merge_(prev->begin(), prev->size() + next->size());
121
122 prev->set_size(prev->size() + next->size());
123
124 all_regions_.erase(next_iter); // prev_iter stays valid.
125
126 // The |next| region must already not be in the free list.
127 DCHECK_EQ(free_regions_.find(next), free_regions_.end());
128 delete next;
129 }
130
AllocateRegion(size_t size)131 RegionAllocator::Address RegionAllocator::AllocateRegion(size_t size) {
132 DCHECK_NE(size, 0);
133 DCHECK(IsAligned(size, page_size_));
134
135 Region* region = FreeListFindRegion(size);
136 if (region == nullptr) return kAllocationFailure;
137
138 if (region->size() != size) {
139 Split(region, size);
140 }
141 DCHECK(IsAligned(region->begin(), page_size_));
142 DCHECK_EQ(region->size(), size);
143
144 // Mark region as used.
145 FreeListRemoveRegion(region);
146 region->set_state(RegionState::kAllocated);
147 return region->begin();
148 }
149
AllocateRegion(RandomNumberGenerator * rng,size_t size)150 RegionAllocator::Address RegionAllocator::AllocateRegion(
151 RandomNumberGenerator* rng, size_t size) {
152 if (free_size() >= max_load_for_randomization_) {
153 // There is enough free space for trying to randomize the address.
154 size_t random = 0;
155
156 for (int i = 0; i < kMaxRandomizationAttempts; i++) {
157 rng->NextBytes(&random, sizeof(random));
158 size_t random_offset = page_size_ * (random % region_size_in_pages_);
159 Address address = begin() + random_offset;
160 if (AllocateRegionAt(address, size, RegionState::kAllocated)) {
161 return address;
162 }
163 }
164 // Fall back to free list allocation.
165 }
166 return AllocateRegion(size);
167 }
168
AllocateRegionAt(Address requested_address,size_t size,RegionState region_state)169 bool RegionAllocator::AllocateRegionAt(Address requested_address, size_t size,
170 RegionState region_state) {
171 DCHECK(IsAligned(requested_address, page_size_));
172 DCHECK_NE(size, 0);
173 DCHECK(IsAligned(size, page_size_));
174 DCHECK_NE(region_state, RegionState::kFree);
175
176 Address requested_end = requested_address + size;
177 DCHECK_LE(requested_end, end());
178
179 Region* region;
180 {
181 AllRegionsSet::iterator region_iter = FindRegion(requested_address);
182 if (region_iter == all_regions_.end()) {
183 return false;
184 }
185 region = *region_iter;
186 }
187 if (!region->is_free() || region->end() < requested_end) {
188 return false;
189 }
190 // Found free region that includes the requested one.
191 if (region->begin() != requested_address) {
192 // Split the region at the |requested_address| boundary.
193 size_t new_size = requested_address - region->begin();
194 DCHECK(IsAligned(new_size, page_size_));
195 region = Split(region, new_size);
196 }
197 if (region->end() != requested_end) {
198 // Split the region at the |requested_end| boundary.
199 Split(region, size);
200 }
201 DCHECK_EQ(region->begin(), requested_address);
202 DCHECK_EQ(region->size(), size);
203
204 // Mark region as used.
205 FreeListRemoveRegion(region);
206 region->set_state(region_state);
207 return true;
208 }
209
AllocateAlignedRegion(size_t size,size_t alignment)210 RegionAllocator::Address RegionAllocator::AllocateAlignedRegion(
211 size_t size, size_t alignment) {
212 DCHECK(IsAligned(size, page_size_));
213 DCHECK(IsAligned(alignment, page_size_));
214 DCHECK_GE(alignment, page_size_);
215
216 const size_t padded_size = size + alignment - page_size_;
217 Region* region = FreeListFindRegion(padded_size);
218 if (region == nullptr) return kAllocationFailure;
219
220 if (!IsAligned(region->begin(), alignment)) {
221 size_t start = RoundUp(region->begin(), alignment);
222 region = Split(region, start - region->begin());
223 DCHECK_EQ(region->begin(), start);
224 DCHECK(IsAligned(region->begin(), alignment));
225 }
226
227 if (region->size() != size) {
228 Split(region, size);
229 }
230 DCHECK(IsAligned(region->begin(), alignment));
231 DCHECK_EQ(region->size(), size);
232
233 // Mark region as used.
234 FreeListRemoveRegion(region);
235 region->set_state(RegionState::kAllocated);
236 return region->begin();
237 }
238
AllocateRegion(Address hint,size_t size,size_t alignment)239 RegionAllocator::Address RegionAllocator::AllocateRegion(Address hint,
240 size_t size,
241 size_t alignment) {
242 DCHECK(IsAligned(alignment, page_size()));
243 DCHECK(IsAligned(hint, alignment));
244
245 if (hint && contains(hint, size)) {
246 if (AllocateRegionAt(hint, size)) {
247 return hint;
248 }
249 }
250
251 Address address;
252 if (alignment <= page_size()) {
253 // TODO(chromium:1218005): Consider using randomized version here.
254 address = AllocateRegion(size);
255 } else {
256 address = AllocateAlignedRegion(size, alignment);
257 }
258
259 return address;
260 }
261
TrimRegion(Address address,size_t new_size)262 size_t RegionAllocator::TrimRegion(Address address, size_t new_size) {
263 DCHECK(IsAligned(new_size, page_size_));
264
265 AllRegionsSet::iterator region_iter = FindRegion(address);
266 if (region_iter == all_regions_.end()) {
267 return 0;
268 }
269 Region* region = *region_iter;
270 if (region->begin() != address || !region->is_allocated()) {
271 return 0;
272 }
273
274 // The region must not be in the free list.
275 DCHECK_EQ(free_regions_.find(*region_iter), free_regions_.end());
276
277 if (new_size > 0) {
278 region = Split(region, new_size);
279 ++region_iter;
280 }
281 size_t size = region->size();
282 region->set_state(RegionState::kFree);
283
284 // Merge current region with the surrounding ones if they are free.
285 if (region->end() != whole_region_.end()) {
286 // There must be a range after the current one.
287 AllRegionsSet::iterator next_iter = std::next(region_iter);
288 DCHECK_NE(next_iter, all_regions_.end());
289 if ((*next_iter)->is_free()) {
290 // |next| region object will be deleted during merge, remove it from
291 // the free list.
292 FreeListRemoveRegion(*next_iter);
293 Merge(region_iter, next_iter);
294 }
295 }
296 if (new_size == 0 && region->begin() != whole_region_.begin()) {
297 // There must be a range before the current one.
298 AllRegionsSet::iterator prev_iter = std::prev(region_iter);
299 DCHECK_NE(prev_iter, all_regions_.end());
300 if ((*prev_iter)->is_free()) {
301 // |prev| region's size will change, we'll have to re-insert it into
302 // the proper place of the free list.
303 FreeListRemoveRegion(*prev_iter);
304 Merge(prev_iter, region_iter);
305 // |prev| region becomes the current region.
306 region_iter = prev_iter;
307 region = *region_iter;
308 }
309 }
310 FreeListAddRegion(region);
311 return size;
312 }
313
CheckRegion(Address address)314 size_t RegionAllocator::CheckRegion(Address address) {
315 AllRegionsSet::iterator region_iter = FindRegion(address);
316 if (region_iter == all_regions_.end()) {
317 return 0;
318 }
319 Region* region = *region_iter;
320 if (region->begin() != address || region->is_free()) {
321 return 0;
322 }
323 return region->size();
324 }
325
IsFree(Address address,size_t size)326 bool RegionAllocator::IsFree(Address address, size_t size) {
327 CHECK(contains(address, size));
328 AllRegionsSet::iterator region_iter = FindRegion(address);
329 if (region_iter == all_regions_.end()) {
330 return true;
331 }
332 Region* region = *region_iter;
333 return region->is_free() && region->contains(address, size);
334 }
335
336 namespace {
RegionStateToString(RegionAllocator::RegionState state)337 const char* RegionStateToString(RegionAllocator::RegionState state) {
338 switch (state) {
339 case RegionAllocator::RegionState::kFree:
340 return "free";
341 case RegionAllocator::RegionState::kExcluded:
342 return "excluded";
343 case RegionAllocator::RegionState::kAllocated:
344 return "used";
345 default:
346 UNREACHABLE();
347 }
348 }
349 } // namespace
350
Print(std::ostream & os) const351 void RegionAllocator::Region::Print(std::ostream& os) const {
352 std::ios::fmtflags flags = os.flags(std::ios::hex | std::ios::showbase);
353 os << "[" << begin() << ", " << end() << "), size: " << size();
354 os << ", " << RegionStateToString(state_);
355 os.flags(flags);
356 }
357
Print(std::ostream & os) const358 void RegionAllocator::Print(std::ostream& os) const {
359 std::ios::fmtflags flags = os.flags(std::ios::hex | std::ios::showbase);
360 os << "RegionAllocator: [" << begin() << ", " << end() << ")";
361 os << "\nsize: " << size();
362 os << "\nfree_size: " << free_size();
363 os << "\npage_size: " << page_size_;
364
365 os << "\nall regions: ";
366 for (const Region* region : all_regions_) {
367 os << "\n ";
368 region->Print(os);
369 }
370
371 os << "\nfree regions: ";
372 for (const Region* region : free_regions_) {
373 os << "\n ";
374 region->Print(os);
375 }
376 os << "\n";
377 os.flags(flags);
378 }
379
380 } // namespace base
381 } // namespace v8
382