• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "src/heap/free-list.h"
6 
7 #include "src/base/macros.h"
8 #include "src/common/globals.h"
9 #include "src/heap/free-list-inl.h"
10 #include "src/heap/heap.h"
11 #include "src/heap/memory-chunk-inl.h"
12 #include "src/objects/free-space-inl.h"
13 
14 namespace v8 {
15 namespace internal {
16 
17 // -----------------------------------------------------------------------------
18 // Free lists for old object spaces implementation
19 
Reset(FreeList * owner)20 void FreeListCategory::Reset(FreeList* owner) {
21   if (is_linked(owner) && !top().is_null()) {
22     owner->DecreaseAvailableBytes(available_);
23   }
24   set_top(FreeSpace());
25   set_prev(nullptr);
26   set_next(nullptr);
27   available_ = 0;
28 }
29 
PickNodeFromList(size_t minimum_size,size_t * node_size)30 FreeSpace FreeListCategory::PickNodeFromList(size_t minimum_size,
31                                              size_t* node_size) {
32   FreeSpace node = top();
33   DCHECK(!node.is_null());
34   DCHECK(Page::FromHeapObject(node)->CanAllocate());
35   if (static_cast<size_t>(node.Size()) < minimum_size) {
36     *node_size = 0;
37     return FreeSpace();
38   }
39   set_top(node.next());
40   *node_size = node.Size();
41   UpdateCountersAfterAllocation(*node_size);
42   return node;
43 }
44 
SearchForNodeInList(size_t minimum_size,size_t * node_size)45 FreeSpace FreeListCategory::SearchForNodeInList(size_t minimum_size,
46                                                 size_t* node_size) {
47   FreeSpace prev_non_evac_node;
48   for (FreeSpace cur_node = top(); !cur_node.is_null();
49        cur_node = cur_node.next()) {
50     DCHECK(Page::FromHeapObject(cur_node)->CanAllocate());
51     size_t size = cur_node.size(kRelaxedLoad);
52     if (size >= minimum_size) {
53       DCHECK_GE(available_, size);
54       UpdateCountersAfterAllocation(size);
55       if (cur_node == top()) {
56         set_top(cur_node.next());
57       }
58       if (!prev_non_evac_node.is_null()) {
59         MemoryChunk* chunk = MemoryChunk::FromHeapObject(prev_non_evac_node);
60         if (chunk->owner_identity() == CODE_SPACE) {
61           chunk->heap()->UnprotectAndRegisterMemoryChunk(
62               chunk, UnprotectMemoryOrigin::kMaybeOffMainThread);
63         }
64         prev_non_evac_node.set_next(cur_node.next());
65       }
66       *node_size = size;
67       return cur_node;
68     }
69 
70     prev_non_evac_node = cur_node;
71   }
72   return FreeSpace();
73 }
74 
Free(Address start,size_t size_in_bytes,FreeMode mode,FreeList * owner)75 void FreeListCategory::Free(Address start, size_t size_in_bytes, FreeMode mode,
76                             FreeList* owner) {
77   FreeSpace free_space = FreeSpace::cast(HeapObject::FromAddress(start));
78   free_space.set_next(top());
79   set_top(free_space);
80   available_ += size_in_bytes;
81   if (mode == kLinkCategory) {
82     if (is_linked(owner)) {
83       owner->IncreaseAvailableBytes(size_in_bytes);
84     } else {
85       owner->AddCategory(this);
86     }
87   }
88 }
89 
RepairFreeList(Heap * heap)90 void FreeListCategory::RepairFreeList(Heap* heap) {
91   Map free_space_map = ReadOnlyRoots(heap).free_space_map();
92   FreeSpace n = top();
93   while (!n.is_null()) {
94     ObjectSlot map_slot = n.map_slot();
95     if (map_slot.contains_map_value(kNullAddress)) {
96       map_slot.store_map(free_space_map);
97     } else {
98       DCHECK(map_slot.contains_map_value(free_space_map.ptr()));
99     }
100     n = n.next();
101   }
102 }
103 
Relink(FreeList * owner)104 void FreeListCategory::Relink(FreeList* owner) {
105   DCHECK(!is_linked(owner));
106   owner->AddCategory(this);
107 }
108 
109 // ------------------------------------------------
110 // Generic FreeList methods (alloc/free related)
111 
CreateFreeList()112 FreeList* FreeList::CreateFreeList() { return new FreeListManyCachedOrigin(); }
113 
TryFindNodeIn(FreeListCategoryType type,size_t minimum_size,size_t * node_size)114 FreeSpace FreeList::TryFindNodeIn(FreeListCategoryType type,
115                                   size_t minimum_size, size_t* node_size) {
116   FreeListCategory* category = categories_[type];
117   if (category == nullptr) return FreeSpace();
118   FreeSpace node = category->PickNodeFromList(minimum_size, node_size);
119   if (!node.is_null()) {
120     DecreaseAvailableBytes(*node_size);
121     DCHECK(IsVeryLong() || Available() == SumFreeLists());
122   }
123   if (category->is_empty()) {
124     RemoveCategory(category);
125   }
126   return node;
127 }
128 
SearchForNodeInList(FreeListCategoryType type,size_t minimum_size,size_t * node_size)129 FreeSpace FreeList::SearchForNodeInList(FreeListCategoryType type,
130                                         size_t minimum_size,
131                                         size_t* node_size) {
132   FreeListCategoryIterator it(this, type);
133   FreeSpace node;
134   while (it.HasNext()) {
135     FreeListCategory* current = it.Next();
136     node = current->SearchForNodeInList(minimum_size, node_size);
137     if (!node.is_null()) {
138       DecreaseAvailableBytes(*node_size);
139       DCHECK(IsVeryLong() || Available() == SumFreeLists());
140       if (current->is_empty()) {
141         RemoveCategory(current);
142       }
143       return node;
144     }
145   }
146   return node;
147 }
148 
Free(Address start,size_t size_in_bytes,FreeMode mode)149 size_t FreeList::Free(Address start, size_t size_in_bytes, FreeMode mode) {
150   Page* page = Page::FromAddress(start);
151   page->DecreaseAllocatedBytes(size_in_bytes);
152 
153   // Blocks have to be a minimum size to hold free list items.
154   if (size_in_bytes < min_block_size_) {
155     page->add_wasted_memory(size_in_bytes);
156     wasted_bytes_ += size_in_bytes;
157     return size_in_bytes;
158   }
159 
160   // Insert other blocks at the head of a free list of the appropriate
161   // magnitude.
162   FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes);
163   page->free_list_category(type)->Free(start, size_in_bytes, mode, this);
164   DCHECK_EQ(page->AvailableInFreeList(),
165             page->AvailableInFreeListFromAllocatedBytes());
166   return 0;
167 }
168 
169 // ------------------------------------------------
170 // FreeListMany implementation
171 
172 constexpr unsigned int FreeListMany::categories_min[kNumberOfCategories];
173 
FreeListMany()174 FreeListMany::FreeListMany() {
175   // Initializing base (FreeList) fields
176   number_of_categories_ = kNumberOfCategories;
177   last_category_ = number_of_categories_ - 1;
178   min_block_size_ = kMinBlockSize;
179   categories_ = new FreeListCategory*[number_of_categories_]();
180 
181   Reset();
182 }
183 
~FreeListMany()184 FreeListMany::~FreeListMany() { delete[] categories_; }
185 
GuaranteedAllocatable(size_t maximum_freed)186 size_t FreeListMany::GuaranteedAllocatable(size_t maximum_freed) {
187   if (maximum_freed < categories_min[0]) {
188     return 0;
189   }
190   for (int cat = kFirstCategory + 1; cat <= last_category_; cat++) {
191     if (maximum_freed < categories_min[cat]) {
192       return categories_min[cat - 1];
193     }
194   }
195   return maximum_freed;
196 }
197 
GetPageForSize(size_t size_in_bytes)198 Page* FreeListMany::GetPageForSize(size_t size_in_bytes) {
199   FreeListCategoryType minimum_category =
200       SelectFreeListCategoryType(size_in_bytes);
201   Page* page = nullptr;
202   for (int cat = minimum_category + 1; !page && cat <= last_category_; cat++) {
203     page = GetPageForCategoryType(cat);
204   }
205   if (!page) {
206     // Might return a page in which |size_in_bytes| will not fit.
207     page = GetPageForCategoryType(minimum_category);
208   }
209   return page;
210 }
211 
Allocate(size_t size_in_bytes,size_t * node_size,AllocationOrigin origin)212 FreeSpace FreeListMany::Allocate(size_t size_in_bytes, size_t* node_size,
213                                  AllocationOrigin origin) {
214   DCHECK_GE(kMaxBlockSize, size_in_bytes);
215   FreeSpace node;
216   FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes);
217   for (int i = type; i < last_category_ && node.is_null(); i++) {
218     node = TryFindNodeIn(static_cast<FreeListCategoryType>(i), size_in_bytes,
219                          node_size);
220   }
221 
222   if (node.is_null()) {
223     // Searching each element of the last category.
224     node = SearchForNodeInList(last_category_, size_in_bytes, node_size);
225   }
226 
227   if (!node.is_null()) {
228     Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size);
229   }
230 
231   DCHECK(IsVeryLong() || Available() == SumFreeLists());
232   return node;
233 }
234 
235 // ------------------------------------------------
236 // FreeListManyCached implementation
237 
FreeListManyCached()238 FreeListManyCached::FreeListManyCached() { ResetCache(); }
239 
Reset()240 void FreeListManyCached::Reset() {
241   ResetCache();
242   FreeListMany::Reset();
243 }
244 
AddCategory(FreeListCategory * category)245 bool FreeListManyCached::AddCategory(FreeListCategory* category) {
246   bool was_added = FreeList::AddCategory(category);
247 
248   // Updating cache
249   if (was_added) {
250     UpdateCacheAfterAddition(category->type_);
251   }
252 
253 #ifdef DEBUG
254   CheckCacheIntegrity();
255 #endif
256 
257   return was_added;
258 }
259 
RemoveCategory(FreeListCategory * category)260 void FreeListManyCached::RemoveCategory(FreeListCategory* category) {
261   FreeList::RemoveCategory(category);
262 
263   // Updating cache
264   int type = category->type_;
265   if (categories_[type] == nullptr) {
266     UpdateCacheAfterRemoval(type);
267   }
268 
269 #ifdef DEBUG
270   CheckCacheIntegrity();
271 #endif
272 }
273 
Free(Address start,size_t size_in_bytes,FreeMode mode)274 size_t FreeListManyCached::Free(Address start, size_t size_in_bytes,
275                                 FreeMode mode) {
276   Page* page = Page::FromAddress(start);
277   page->DecreaseAllocatedBytes(size_in_bytes);
278 
279   // Blocks have to be a minimum size to hold free list items.
280   if (size_in_bytes < min_block_size_) {
281     page->add_wasted_memory(size_in_bytes);
282     wasted_bytes_ += size_in_bytes;
283     return size_in_bytes;
284   }
285 
286   // Insert other blocks at the head of a free list of the appropriate
287   // magnitude.
288   FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes);
289   page->free_list_category(type)->Free(start, size_in_bytes, mode, this);
290 
291   // Updating cache
292   if (mode == kLinkCategory) {
293     UpdateCacheAfterAddition(type);
294 
295 #ifdef DEBUG
296     CheckCacheIntegrity();
297 #endif
298   }
299 
300   DCHECK_EQ(page->AvailableInFreeList(),
301             page->AvailableInFreeListFromAllocatedBytes());
302   return 0;
303 }
304 
Allocate(size_t size_in_bytes,size_t * node_size,AllocationOrigin origin)305 FreeSpace FreeListManyCached::Allocate(size_t size_in_bytes, size_t* node_size,
306                                        AllocationOrigin origin) {
307   USE(origin);
308   DCHECK_GE(kMaxBlockSize, size_in_bytes);
309 
310   FreeSpace node;
311   FreeListCategoryType type = SelectFreeListCategoryType(size_in_bytes);
312   type = next_nonempty_category[type];
313   for (; type < last_category_; type = next_nonempty_category[type + 1]) {
314     node = TryFindNodeIn(type, size_in_bytes, node_size);
315     if (!node.is_null()) break;
316   }
317 
318   if (node.is_null()) {
319     // Searching each element of the last category.
320     type = last_category_;
321     node = SearchForNodeInList(type, size_in_bytes, node_size);
322   }
323 
324   // Updating cache
325   if (!node.is_null() && categories_[type] == nullptr) {
326     UpdateCacheAfterRemoval(type);
327   }
328 
329 #ifdef DEBUG
330   CheckCacheIntegrity();
331 #endif
332 
333   if (!node.is_null()) {
334     Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size);
335   }
336 
337   DCHECK(IsVeryLong() || Available() == SumFreeLists());
338   return node;
339 }
340 
341 // ------------------------------------------------
342 // FreeListManyCachedFastPath implementation
343 
Allocate(size_t size_in_bytes,size_t * node_size,AllocationOrigin origin)344 FreeSpace FreeListManyCachedFastPath::Allocate(size_t size_in_bytes,
345                                                size_t* node_size,
346                                                AllocationOrigin origin) {
347   USE(origin);
348   DCHECK_GE(kMaxBlockSize, size_in_bytes);
349   FreeSpace node;
350 
351   // Fast path part 1: searching the last categories
352   FreeListCategoryType first_category =
353       SelectFastAllocationFreeListCategoryType(size_in_bytes);
354   FreeListCategoryType type = first_category;
355   for (type = next_nonempty_category[type]; type <= last_category_;
356        type = next_nonempty_category[type + 1]) {
357     node = TryFindNodeIn(type, size_in_bytes, node_size);
358     if (!node.is_null()) break;
359   }
360 
361   // Fast path part 2: searching the medium categories for tiny objects
362   if (node.is_null()) {
363     if (size_in_bytes <= kTinyObjectMaxSize) {
364       for (type = next_nonempty_category[kFastPathFallBackTiny];
365            type < kFastPathFirstCategory;
366            type = next_nonempty_category[type + 1]) {
367         node = TryFindNodeIn(type, size_in_bytes, node_size);
368         if (!node.is_null()) break;
369       }
370     }
371   }
372 
373   // Searching the last category
374   if (node.is_null()) {
375     // Searching each element of the last category.
376     type = last_category_;
377     node = SearchForNodeInList(type, size_in_bytes, node_size);
378   }
379 
380   // Finally, search the most precise category
381   if (node.is_null()) {
382     type = SelectFreeListCategoryType(size_in_bytes);
383     for (type = next_nonempty_category[type]; type < first_category;
384          type = next_nonempty_category[type + 1]) {
385       node = TryFindNodeIn(type, size_in_bytes, node_size);
386       if (!node.is_null()) break;
387     }
388   }
389 
390   // Updating cache
391   if (!node.is_null() && categories_[type] == nullptr) {
392     UpdateCacheAfterRemoval(type);
393   }
394 
395 #ifdef DEBUG
396   CheckCacheIntegrity();
397 #endif
398 
399   if (!node.is_null()) {
400     Page::FromHeapObject(node)->IncreaseAllocatedBytes(*node_size);
401   }
402 
403   DCHECK(IsVeryLong() || Available() == SumFreeLists());
404   return node;
405 }
406 
407 // ------------------------------------------------
408 // FreeListManyCachedOrigin implementation
409 
Allocate(size_t size_in_bytes,size_t * node_size,AllocationOrigin origin)410 FreeSpace FreeListManyCachedOrigin::Allocate(size_t size_in_bytes,
411                                              size_t* node_size,
412                                              AllocationOrigin origin) {
413   if (origin == AllocationOrigin::kGC) {
414     return FreeListManyCached::Allocate(size_in_bytes, node_size, origin);
415   } else {
416     return FreeListManyCachedFastPath::Allocate(size_in_bytes, node_size,
417                                                 origin);
418   }
419 }
420 
421 // ------------------------------------------------
422 // Generic FreeList methods (non alloc/free related)
423 
Reset()424 void FreeList::Reset() {
425   ForAllFreeListCategories(
426       [this](FreeListCategory* category) { category->Reset(this); });
427   for (int i = kFirstCategory; i < number_of_categories_; i++) {
428     categories_[i] = nullptr;
429   }
430   wasted_bytes_ = 0;
431   available_ = 0;
432 }
433 
EvictFreeListItems(Page * page)434 size_t FreeList::EvictFreeListItems(Page* page) {
435   size_t sum = 0;
436   page->ForAllFreeListCategories([this, &sum](FreeListCategory* category) {
437     sum += category->available();
438     RemoveCategory(category);
439     category->Reset(this);
440   });
441   return sum;
442 }
443 
RepairLists(Heap * heap)444 void FreeList::RepairLists(Heap* heap) {
445   ForAllFreeListCategories(
446       [heap](FreeListCategory* category) { category->RepairFreeList(heap); });
447 }
448 
AddCategory(FreeListCategory * category)449 bool FreeList::AddCategory(FreeListCategory* category) {
450   FreeListCategoryType type = category->type_;
451   DCHECK_LT(type, number_of_categories_);
452   FreeListCategory* top = categories_[type];
453 
454   if (category->is_empty()) return false;
455   DCHECK_NE(top, category);
456 
457   // Common double-linked list insertion.
458   if (top != nullptr) {
459     top->set_prev(category);
460   }
461   category->set_next(top);
462   categories_[type] = category;
463 
464   IncreaseAvailableBytes(category->available());
465   return true;
466 }
467 
RemoveCategory(FreeListCategory * category)468 void FreeList::RemoveCategory(FreeListCategory* category) {
469   FreeListCategoryType type = category->type_;
470   DCHECK_LT(type, number_of_categories_);
471   FreeListCategory* top = categories_[type];
472 
473   if (category->is_linked(this)) {
474     DecreaseAvailableBytes(category->available());
475   }
476 
477   // Common double-linked list removal.
478   if (top == category) {
479     categories_[type] = category->next();
480   }
481   if (category->prev() != nullptr) {
482     category->prev()->set_next(category->next());
483   }
484   if (category->next() != nullptr) {
485     category->next()->set_prev(category->prev());
486   }
487   category->set_next(nullptr);
488   category->set_prev(nullptr);
489 }
490 
PrintCategories(FreeListCategoryType type)491 void FreeList::PrintCategories(FreeListCategoryType type) {
492   FreeListCategoryIterator it(this, type);
493   PrintF("FreeList[%p, top=%p, %d] ", static_cast<void*>(this),
494          static_cast<void*>(categories_[type]), type);
495   while (it.HasNext()) {
496     FreeListCategory* current = it.Next();
497     PrintF("%p -> ", static_cast<void*>(current));
498   }
499   PrintF("null\n");
500 }
501 
SumFreeList()502 size_t FreeListCategory::SumFreeList() {
503   size_t sum = 0;
504   FreeSpace cur = top();
505   while (!cur.is_null()) {
506     // We can't use "cur->map()" here because both cur's map and the
507     // root can be null during bootstrapping.
508     DCHECK(
509         cur.map_slot().contains_map_value(Page::FromHeapObject(cur)
510                                               ->heap()
511                                               ->isolate()
512                                               ->root(RootIndex::kFreeSpaceMap)
513                                               .ptr()));
514     sum += cur.size(kRelaxedLoad);
515     cur = cur.next();
516   }
517   return sum;
518 }
FreeListLength()519 int FreeListCategory::FreeListLength() {
520   int length = 0;
521   FreeSpace cur = top();
522   while (!cur.is_null()) {
523     length++;
524     cur = cur.next();
525   }
526   return length;
527 }
528 
529 #ifdef DEBUG
IsVeryLong()530 bool FreeList::IsVeryLong() {
531   int len = 0;
532   for (int i = kFirstCategory; i < number_of_categories_; i++) {
533     FreeListCategoryIterator it(this, static_cast<FreeListCategoryType>(i));
534     while (it.HasNext()) {
535       len += it.Next()->FreeListLength();
536       if (len >= FreeListCategory::kVeryLongFreeList) return true;
537     }
538   }
539   return false;
540 }
541 
542 // This can take a very long time because it is linear in the number of entries
543 // on the free list, so it should not be called if FreeListLength returns
544 // kVeryLongFreeList.
SumFreeLists()545 size_t FreeList::SumFreeLists() {
546   size_t sum = 0;
547   ForAllFreeListCategories(
548       [&sum](FreeListCategory* category) { sum += category->SumFreeList(); });
549   return sum;
550 }
551 #endif
552 
553 }  // namespace internal
554 }  // namespace v8
555