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