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