• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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