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