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/paged-spaces.h"
6
7 #include "src/base/optional.h"
8 #include "src/base/platform/mutex.h"
9 #include "src/execution/isolate.h"
10 #include "src/execution/vm-state-inl.h"
11 #include "src/heap/array-buffer-sweeper.h"
12 #include "src/heap/heap.h"
13 #include "src/heap/incremental-marking.h"
14 #include "src/heap/memory-allocator.h"
15 #include "src/heap/memory-chunk-inl.h"
16 #include "src/heap/paged-spaces-inl.h"
17 #include "src/heap/read-only-heap.h"
18 #include "src/logging/counters.h"
19 #include "src/objects/string.h"
20 #include "src/utils/utils.h"
21
22 namespace v8 {
23 namespace internal {
24
25 // ----------------------------------------------------------------------------
26 // PagedSpaceObjectIterator
27
PagedSpaceObjectIterator(Heap * heap,PagedSpace * space)28 PagedSpaceObjectIterator::PagedSpaceObjectIterator(Heap* heap,
29 PagedSpace* space)
30 : cur_addr_(kNullAddress),
31 cur_end_(kNullAddress),
32 space_(space),
33 page_range_(space->first_page(), nullptr),
34 current_page_(page_range_.begin()) {
35 space_->MakeLinearAllocationAreaIterable();
36 heap->mark_compact_collector()->EnsureSweepingCompleted();
37 }
38
PagedSpaceObjectIterator(Heap * heap,PagedSpace * space,Page * page)39 PagedSpaceObjectIterator::PagedSpaceObjectIterator(Heap* heap,
40 PagedSpace* space,
41 Page* page)
42 : cur_addr_(kNullAddress),
43 cur_end_(kNullAddress),
44 space_(space),
45 page_range_(page),
46 current_page_(page_range_.begin()) {
47 space_->MakeLinearAllocationAreaIterable();
48 heap->mark_compact_collector()->EnsureSweepingCompleted();
49 #ifdef DEBUG
50 AllocationSpace owner = page->owner_identity();
51 DCHECK(owner == OLD_SPACE || owner == MAP_SPACE || owner == CODE_SPACE);
52 #endif // DEBUG
53 }
54
55 // We have hit the end of the page and should advance to the next block of
56 // objects. This happens at the end of the page.
AdvanceToNextPage()57 bool PagedSpaceObjectIterator::AdvanceToNextPage() {
58 DCHECK_EQ(cur_addr_, cur_end_);
59 if (current_page_ == page_range_.end()) return false;
60 Page* cur_page = *(current_page_++);
61
62 cur_addr_ = cur_page->area_start();
63 cur_end_ = cur_page->area_end();
64 DCHECK(cur_page->SweepingDone());
65 return true;
66 }
InitializePage(MemoryChunk * chunk)67 Page* PagedSpace::InitializePage(MemoryChunk* chunk) {
68 Page* page = static_cast<Page*>(chunk);
69 DCHECK_EQ(
70 MemoryChunkLayout::AllocatableMemoryInMemoryChunk(page->owner_identity()),
71 page->area_size());
72 // Make sure that categories are initialized before freeing the area.
73 page->ResetAllocationStatistics();
74 page->SetOldGenerationPageFlags(heap()->incremental_marking()->IsMarking());
75 page->AllocateFreeListCategories();
76 page->InitializeFreeListCategories();
77 page->list_node().Initialize();
78 page->InitializationMemoryFence();
79 return page;
80 }
81
PagedSpace(Heap * heap,AllocationSpace space,Executability executable,FreeList * free_list,LocalSpaceKind local_space_kind)82 PagedSpace::PagedSpace(Heap* heap, AllocationSpace space,
83 Executability executable, FreeList* free_list,
84 LocalSpaceKind local_space_kind)
85 : SpaceWithLinearArea(heap, space, free_list),
86 executable_(executable),
87 local_space_kind_(local_space_kind) {
88 area_size_ = MemoryChunkLayout::AllocatableMemoryInMemoryChunk(space);
89 accounting_stats_.Clear();
90 }
91
TearDown()92 void PagedSpace::TearDown() {
93 while (!memory_chunk_list_.Empty()) {
94 MemoryChunk* chunk = memory_chunk_list_.front();
95 memory_chunk_list_.Remove(chunk);
96 heap()->memory_allocator()->Free<MemoryAllocator::kFull>(chunk);
97 }
98 accounting_stats_.Clear();
99 }
100
RefillFreeList()101 void PagedSpace::RefillFreeList() {
102 // Any PagedSpace might invoke RefillFreeList. We filter all but our old
103 // generation spaces out.
104 if (identity() != OLD_SPACE && identity() != CODE_SPACE &&
105 identity() != MAP_SPACE) {
106 return;
107 }
108 DCHECK_IMPLIES(is_local_space(), is_compaction_space());
109 MarkCompactCollector* collector = heap()->mark_compact_collector();
110 size_t added = 0;
111
112 {
113 Page* p = nullptr;
114 while ((p = collector->sweeper()->GetSweptPageSafe(this)) != nullptr) {
115 // We regularly sweep NEVER_ALLOCATE_ON_PAGE pages. We drop the freelist
116 // entries here to make them unavailable for allocations.
117 if (p->IsFlagSet(Page::NEVER_ALLOCATE_ON_PAGE)) {
118 p->ForAllFreeListCategories([this](FreeListCategory* category) {
119 category->Reset(free_list());
120 });
121 }
122
123 // Also merge old-to-new remembered sets if not scavenging because of
124 // data races: One thread might iterate remembered set, while another
125 // thread merges them.
126 if (local_space_kind() != LocalSpaceKind::kCompactionSpaceForScavenge) {
127 p->MergeOldToNewRememberedSets();
128 }
129
130 // Only during compaction pages can actually change ownership. This is
131 // safe because there exists no other competing action on the page links
132 // during compaction.
133 if (is_compaction_space()) {
134 DCHECK_NE(this, p->owner());
135 PagedSpace* owner = reinterpret_cast<PagedSpace*>(p->owner());
136 base::MutexGuard guard(owner->mutex());
137 owner->RefineAllocatedBytesAfterSweeping(p);
138 owner->RemovePage(p);
139 added += AddPage(p);
140 added += p->wasted_memory();
141 } else {
142 base::MutexGuard guard(mutex());
143 DCHECK_EQ(this, p->owner());
144 RefineAllocatedBytesAfterSweeping(p);
145 added += RelinkFreeListCategories(p);
146 added += p->wasted_memory();
147 }
148 if (is_compaction_space() && (added > kCompactionMemoryWanted)) break;
149 }
150 }
151 }
152
MergeLocalSpace(LocalSpace * other)153 void PagedSpace::MergeLocalSpace(LocalSpace* other) {
154 base::MutexGuard guard(mutex());
155
156 DCHECK(identity() == other->identity());
157
158 // Unmerged fields:
159 // area_size_
160 other->FreeLinearAllocationArea();
161
162 for (int i = static_cast<int>(AllocationOrigin::kFirstAllocationOrigin);
163 i <= static_cast<int>(AllocationOrigin::kLastAllocationOrigin); i++) {
164 allocations_origins_[i] += other->allocations_origins_[i];
165 }
166
167 // The linear allocation area of {other} should be destroyed now.
168 DCHECK_EQ(kNullAddress, other->top());
169 DCHECK_EQ(kNullAddress, other->limit());
170
171 // Move over pages.
172 for (auto it = other->begin(); it != other->end();) {
173 Page* p = *(it++);
174
175 p->MergeOldToNewRememberedSets();
176
177 // Ensure that pages are initialized before objects on it are discovered by
178 // concurrent markers.
179 p->InitializationMemoryFence();
180
181 // Relinking requires the category to be unlinked.
182 other->RemovePage(p);
183 AddPage(p);
184 DCHECK_IMPLIES(
185 !p->IsFlagSet(Page::NEVER_ALLOCATE_ON_PAGE),
186 p->AvailableInFreeList() == p->AvailableInFreeListFromAllocatedBytes());
187
188 // TODO(leszeks): Here we should allocation step, but:
189 // 1. Allocation groups are currently not handled properly by the sampling
190 // allocation profiler, and
191 // 2. Observers might try to take the space lock, which isn't reentrant.
192 // We'll have to come up with a better solution for allocation stepping
193 // before shipping, which will likely be using LocalHeap.
194 }
195 for (auto p : other->GetNewPages()) {
196 heap()->NotifyOldGenerationExpansion(identity(), p);
197 }
198
199 DCHECK_EQ(0u, other->Size());
200 DCHECK_EQ(0u, other->Capacity());
201 }
202
CommittedPhysicalMemory()203 size_t PagedSpace::CommittedPhysicalMemory() {
204 if (!base::OS::HasLazyCommits()) return CommittedMemory();
205 BasicMemoryChunk::UpdateHighWaterMark(allocation_info_.top());
206 base::MutexGuard guard(mutex());
207 size_t size = 0;
208 for (Page* page : *this) {
209 size += page->CommittedPhysicalMemory();
210 }
211 return size;
212 }
213
ContainsSlow(Address addr) const214 bool PagedSpace::ContainsSlow(Address addr) const {
215 Page* p = Page::FromAddress(addr);
216 for (const Page* page : *this) {
217 if (page == p) return true;
218 }
219 return false;
220 }
221
RefineAllocatedBytesAfterSweeping(Page * page)222 void PagedSpace::RefineAllocatedBytesAfterSweeping(Page* page) {
223 CHECK(page->SweepingDone());
224 auto marking_state =
225 heap()->incremental_marking()->non_atomic_marking_state();
226 // The live_byte on the page was accounted in the space allocated
227 // bytes counter. After sweeping allocated_bytes() contains the
228 // accurate live byte count on the page.
229 size_t old_counter = marking_state->live_bytes(page);
230 size_t new_counter = page->allocated_bytes();
231 DCHECK_GE(old_counter, new_counter);
232 if (old_counter > new_counter) {
233 DecreaseAllocatedBytes(old_counter - new_counter, page);
234 }
235 marking_state->SetLiveBytes(page, 0);
236 }
237
RemovePageSafe(int size_in_bytes)238 Page* PagedSpace::RemovePageSafe(int size_in_bytes) {
239 base::MutexGuard guard(mutex());
240 Page* page = free_list()->GetPageForSize(size_in_bytes);
241 if (!page) return nullptr;
242 RemovePage(page);
243 return page;
244 }
245
AddPage(Page * page)246 size_t PagedSpace::AddPage(Page* page) {
247 CHECK(page->SweepingDone());
248 page->set_owner(this);
249 memory_chunk_list_.PushBack(page);
250 AccountCommitted(page->size());
251 IncreaseCapacity(page->area_size());
252 IncreaseAllocatedBytes(page->allocated_bytes(), page);
253 for (size_t i = 0; i < ExternalBackingStoreType::kNumTypes; i++) {
254 ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
255 IncrementExternalBackingStoreBytes(t, page->ExternalBackingStoreBytes(t));
256 }
257 return RelinkFreeListCategories(page);
258 }
259
RemovePage(Page * page)260 void PagedSpace::RemovePage(Page* page) {
261 CHECK(page->SweepingDone());
262 memory_chunk_list_.Remove(page);
263 UnlinkFreeListCategories(page);
264 DecreaseAllocatedBytes(page->allocated_bytes(), page);
265 DecreaseCapacity(page->area_size());
266 AccountUncommitted(page->size());
267 for (size_t i = 0; i < ExternalBackingStoreType::kNumTypes; i++) {
268 ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
269 DecrementExternalBackingStoreBytes(t, page->ExternalBackingStoreBytes(t));
270 }
271 }
272
SetTopAndLimit(Address top,Address limit)273 void PagedSpace::SetTopAndLimit(Address top, Address limit) {
274 DCHECK(top == limit ||
275 Page::FromAddress(top) == Page::FromAddress(limit - 1));
276 BasicMemoryChunk::UpdateHighWaterMark(allocation_info_.top());
277 allocation_info_.Reset(top, limit);
278 }
279
ShrinkPageToHighWaterMark(Page * page)280 size_t PagedSpace::ShrinkPageToHighWaterMark(Page* page) {
281 size_t unused = page->ShrinkToHighWaterMark();
282 accounting_stats_.DecreaseCapacity(static_cast<intptr_t>(unused));
283 AccountUncommitted(unused);
284 return unused;
285 }
286
ResetFreeList()287 void PagedSpace::ResetFreeList() {
288 for (Page* page : *this) {
289 free_list_->EvictFreeListItems(page);
290 }
291 DCHECK(free_list_->IsEmpty());
292 }
293
ShrinkImmortalImmovablePages()294 void PagedSpace::ShrinkImmortalImmovablePages() {
295 DCHECK(!heap()->deserialization_complete());
296 BasicMemoryChunk::UpdateHighWaterMark(allocation_info_.top());
297 FreeLinearAllocationArea();
298 ResetFreeList();
299 for (Page* page : *this) {
300 DCHECK(page->IsFlagSet(Page::NEVER_EVACUATE));
301 ShrinkPageToHighWaterMark(page);
302 }
303 }
304
AllocatePage()305 Page* PagedSpace::AllocatePage() {
306 return heap()->memory_allocator()->AllocatePage(AreaSize(), this,
307 executable());
308 }
309
Expand()310 Page* PagedSpace::Expand() {
311 Page* page = AllocatePage();
312 if (page == nullptr) return nullptr;
313 ConcurrentAllocationMutex guard(this);
314 AddPage(page);
315 Free(page->area_start(), page->area_size(),
316 SpaceAccountingMode::kSpaceAccounted);
317 return page;
318 }
319
ExpandBackground(LocalHeap * local_heap)320 Page* PagedSpace::ExpandBackground(LocalHeap* local_heap) {
321 Page* page = AllocatePage();
322 if (page == nullptr) return nullptr;
323 base::MutexGuard lock(&space_mutex_);
324 AddPage(page);
325 Free(page->area_start(), page->area_size(),
326 SpaceAccountingMode::kSpaceAccounted);
327 return page;
328 }
329
CountTotalPages()330 int PagedSpace::CountTotalPages() {
331 int count = 0;
332 for (Page* page : *this) {
333 count++;
334 USE(page);
335 }
336 return count;
337 }
338
SetLinearAllocationArea(Address top,Address limit)339 void PagedSpace::SetLinearAllocationArea(Address top, Address limit) {
340 SetTopAndLimit(top, limit);
341 if (top != kNullAddress && top != limit &&
342 heap()->incremental_marking()->black_allocation()) {
343 Page::FromAllocationAreaAddress(top)->CreateBlackArea(top, limit);
344 }
345 }
346
DecreaseLimit(Address new_limit)347 void PagedSpace::DecreaseLimit(Address new_limit) {
348 Address old_limit = limit();
349 DCHECK_LE(top(), new_limit);
350 DCHECK_GE(old_limit, new_limit);
351 if (new_limit != old_limit) {
352 base::Optional<CodePageMemoryModificationScope> optional_scope;
353
354 if (identity() == CODE_SPACE) {
355 MemoryChunk* chunk = MemoryChunk::FromAddress(new_limit);
356 optional_scope.emplace(chunk);
357 }
358
359 SetTopAndLimit(top(), new_limit);
360 Free(new_limit, old_limit - new_limit,
361 SpaceAccountingMode::kSpaceAccounted);
362 if (heap()->incremental_marking()->black_allocation()) {
363 Page::FromAllocationAreaAddress(new_limit)->DestroyBlackArea(new_limit,
364 old_limit);
365 }
366 }
367 }
368
MarkLinearAllocationAreaBlack()369 void PagedSpace::MarkLinearAllocationAreaBlack() {
370 DCHECK(heap()->incremental_marking()->black_allocation());
371 Address current_top = top();
372 Address current_limit = limit();
373 if (current_top != kNullAddress && current_top != current_limit) {
374 Page::FromAllocationAreaAddress(current_top)
375 ->CreateBlackArea(current_top, current_limit);
376 }
377 }
378
UnmarkLinearAllocationArea()379 void PagedSpace::UnmarkLinearAllocationArea() {
380 Address current_top = top();
381 Address current_limit = limit();
382 if (current_top != kNullAddress && current_top != current_limit) {
383 Page::FromAllocationAreaAddress(current_top)
384 ->DestroyBlackArea(current_top, current_limit);
385 }
386 }
387
MakeLinearAllocationAreaIterable()388 void PagedSpace::MakeLinearAllocationAreaIterable() {
389 Address current_top = top();
390 Address current_limit = limit();
391 if (current_top != kNullAddress && current_top != current_limit) {
392 base::Optional<CodePageMemoryModificationScope> optional_scope;
393
394 if (identity() == CODE_SPACE) {
395 MemoryChunk* chunk = MemoryChunk::FromAddress(current_top);
396 optional_scope.emplace(chunk);
397 }
398
399 heap_->CreateFillerObjectAt(current_top,
400 static_cast<int>(current_limit - current_top),
401 ClearRecordedSlots::kNo);
402 }
403 }
404
Available()405 size_t PagedSpace::Available() {
406 ConcurrentAllocationMutex guard(this);
407 return free_list_->Available();
408 }
409
FreeLinearAllocationArea()410 void PagedSpace::FreeLinearAllocationArea() {
411 // Mark the old linear allocation area with a free space map so it can be
412 // skipped when scanning the heap.
413 Address current_top = top();
414 Address current_limit = limit();
415 if (current_top == kNullAddress) {
416 DCHECK_EQ(kNullAddress, current_limit);
417 return;
418 }
419
420 AdvanceAllocationObservers();
421
422 if (current_top != current_limit &&
423 heap()->incremental_marking()->black_allocation()) {
424 Page::FromAddress(current_top)
425 ->DestroyBlackArea(current_top, current_limit);
426 }
427
428 SetTopAndLimit(kNullAddress, kNullAddress);
429 DCHECK_GE(current_limit, current_top);
430
431 // The code page of the linear allocation area needs to be unprotected
432 // because we are going to write a filler into that memory area below.
433 if (identity() == CODE_SPACE) {
434 heap()->UnprotectAndRegisterMemoryChunk(
435 MemoryChunk::FromAddress(current_top));
436 }
437
438 DCHECK_IMPLIES(current_limit - current_top >= 2 * kTaggedSize,
439 heap()->incremental_marking()->marking_state()->IsWhite(
440 HeapObject::FromAddress(current_top)));
441 Free(current_top, current_limit - current_top,
442 SpaceAccountingMode::kSpaceAccounted);
443 }
444
ReleasePage(Page * page)445 void PagedSpace::ReleasePage(Page* page) {
446 DCHECK_EQ(
447 0, heap()->incremental_marking()->non_atomic_marking_state()->live_bytes(
448 page));
449 DCHECK_EQ(page->owner(), this);
450
451 free_list_->EvictFreeListItems(page);
452
453 if (Page::FromAllocationAreaAddress(allocation_info_.top()) == page) {
454 SetTopAndLimit(kNullAddress, kNullAddress);
455 }
456
457 if (identity() == CODE_SPACE) {
458 heap()->isolate()->RemoveCodeMemoryChunk(page);
459 }
460
461 AccountUncommitted(page->size());
462 accounting_stats_.DecreaseCapacity(page->area_size());
463 heap()->memory_allocator()->Free<MemoryAllocator::kPreFreeAndQueue>(page);
464 }
465
SetReadable()466 void PagedSpace::SetReadable() {
467 DCHECK(identity() == CODE_SPACE);
468 for (Page* page : *this) {
469 CHECK(heap()->memory_allocator()->IsMemoryChunkExecutable(page));
470 page->SetReadable();
471 }
472 }
473
SetReadAndExecutable()474 void PagedSpace::SetReadAndExecutable() {
475 DCHECK(identity() == CODE_SPACE);
476 for (Page* page : *this) {
477 CHECK(heap()->memory_allocator()->IsMemoryChunkExecutable(page));
478 page->SetReadAndExecutable();
479 }
480 }
481
SetReadAndWritable()482 void PagedSpace::SetReadAndWritable() {
483 DCHECK(identity() == CODE_SPACE);
484 for (Page* page : *this) {
485 CHECK(heap()->memory_allocator()->IsMemoryChunkExecutable(page));
486 page->SetReadAndWritable();
487 }
488 }
489
GetObjectIterator(Heap * heap)490 std::unique_ptr<ObjectIterator> PagedSpace::GetObjectIterator(Heap* heap) {
491 return std::unique_ptr<ObjectIterator>(
492 new PagedSpaceObjectIterator(heap, this));
493 }
494
TryAllocationFromFreeListMain(size_t size_in_bytes,AllocationOrigin origin)495 bool PagedSpace::TryAllocationFromFreeListMain(size_t size_in_bytes,
496 AllocationOrigin origin) {
497 ConcurrentAllocationMutex guard(this);
498 DCHECK(IsAligned(size_in_bytes, kTaggedSize));
499 DCHECK_LE(top(), limit());
500 #ifdef DEBUG
501 if (top() != limit()) {
502 DCHECK_EQ(Page::FromAddress(top()), Page::FromAddress(limit() - 1));
503 }
504 #endif
505 // Don't free list allocate if there is linear space available.
506 DCHECK_LT(static_cast<size_t>(limit() - top()), size_in_bytes);
507
508 // Mark the old linear allocation area with a free space map so it can be
509 // skipped when scanning the heap. This also puts it back in the free list
510 // if it is big enough.
511 FreeLinearAllocationArea();
512
513 size_t new_node_size = 0;
514 FreeSpace new_node =
515 free_list_->Allocate(size_in_bytes, &new_node_size, origin);
516 if (new_node.is_null()) return false;
517 DCHECK_GE(new_node_size, size_in_bytes);
518
519 // The old-space-step might have finished sweeping and restarted marking.
520 // Verify that it did not turn the page of the new node into an evacuation
521 // candidate.
522 DCHECK(!MarkCompactCollector::IsOnEvacuationCandidate(new_node));
523
524 // Memory in the linear allocation area is counted as allocated. We may free
525 // a little of this again immediately - see below.
526 Page* page = Page::FromHeapObject(new_node);
527 IncreaseAllocatedBytes(new_node_size, page);
528
529 DCHECK_EQ(allocation_info_.start(), allocation_info_.top());
530 Address start = new_node.address();
531 Address end = new_node.address() + new_node_size;
532 Address limit = ComputeLimit(start, end, size_in_bytes);
533 DCHECK_LE(limit, end);
534 DCHECK_LE(size_in_bytes, limit - start);
535 if (limit != end) {
536 if (identity() == CODE_SPACE) {
537 heap()->UnprotectAndRegisterMemoryChunk(page);
538 }
539 Free(limit, end - limit, SpaceAccountingMode::kSpaceAccounted);
540 }
541 SetLinearAllocationArea(start, limit);
542
543 return true;
544 }
545
RawRefillLabBackground(LocalHeap * local_heap,size_t min_size_in_bytes,size_t max_size_in_bytes,AllocationAlignment alignment,AllocationOrigin origin)546 base::Optional<std::pair<Address, size_t>> PagedSpace::RawRefillLabBackground(
547 LocalHeap* local_heap, size_t min_size_in_bytes, size_t max_size_in_bytes,
548 AllocationAlignment alignment, AllocationOrigin origin) {
549 DCHECK(!is_local_space() && identity() == OLD_SPACE);
550 DCHECK_EQ(origin, AllocationOrigin::kRuntime);
551
552 auto result = TryAllocationFromFreeListBackground(
553 local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
554 if (result) return result;
555
556 MarkCompactCollector* collector = heap()->mark_compact_collector();
557 // Sweeping is still in progress.
558 if (collector->sweeping_in_progress()) {
559 // First try to refill the free-list, concurrent sweeper threads
560 // may have freed some objects in the meantime.
561 RefillFreeList();
562
563 // Retry the free list allocation.
564 auto result = TryAllocationFromFreeListBackground(
565 local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
566 if (result) return result;
567
568 // Now contribute to sweeping from background thread and then try to
569 // reallocate.
570 Sweeper::FreeSpaceMayContainInvalidatedSlots
571 invalidated_slots_in_free_space =
572 Sweeper::FreeSpaceMayContainInvalidatedSlots::kNo;
573
574 const int kMaxPagesToSweep = 1;
575 int max_freed = collector->sweeper()->ParallelSweepSpace(
576 identity(), static_cast<int>(min_size_in_bytes), kMaxPagesToSweep,
577 invalidated_slots_in_free_space);
578
579 RefillFreeList();
580
581 if (static_cast<size_t>(max_freed) >= min_size_in_bytes) {
582 auto result = TryAllocationFromFreeListBackground(
583 local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
584 if (result) return result;
585 }
586 }
587
588 if (heap()->ShouldExpandOldGenerationOnSlowAllocation(local_heap) &&
589 heap()->CanExpandOldGenerationBackground(AreaSize()) &&
590 ExpandBackground(local_heap)) {
591 DCHECK((CountTotalPages() > 1) ||
592 (min_size_in_bytes <= free_list_->Available()));
593 auto result = TryAllocationFromFreeListBackground(
594 local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
595 if (result) return result;
596 }
597
598 if (collector->sweeping_in_progress()) {
599 // Complete sweeping for this space.
600 collector->DrainSweepingWorklistForSpace(identity());
601
602 RefillFreeList();
603
604 // Last try to acquire memory from free list.
605 return TryAllocationFromFreeListBackground(
606 local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
607 }
608
609 return {};
610 }
611
612 base::Optional<std::pair<Address, size_t>>
TryAllocationFromFreeListBackground(LocalHeap * local_heap,size_t min_size_in_bytes,size_t max_size_in_bytes,AllocationAlignment alignment,AllocationOrigin origin)613 PagedSpace::TryAllocationFromFreeListBackground(LocalHeap* local_heap,
614 size_t min_size_in_bytes,
615 size_t max_size_in_bytes,
616 AllocationAlignment alignment,
617 AllocationOrigin origin) {
618 base::MutexGuard lock(&space_mutex_);
619 DCHECK_LE(min_size_in_bytes, max_size_in_bytes);
620 DCHECK_EQ(identity(), OLD_SPACE);
621
622 size_t new_node_size = 0;
623 FreeSpace new_node =
624 free_list_->Allocate(min_size_in_bytes, &new_node_size, origin);
625 if (new_node.is_null()) return {};
626 DCHECK_GE(new_node_size, min_size_in_bytes);
627
628 // The old-space-step might have finished sweeping and restarted marking.
629 // Verify that it did not turn the page of the new node into an evacuation
630 // candidate.
631 DCHECK(!MarkCompactCollector::IsOnEvacuationCandidate(new_node));
632
633 // Memory in the linear allocation area is counted as allocated. We may free
634 // a little of this again immediately - see below.
635 Page* page = Page::FromHeapObject(new_node);
636 IncreaseAllocatedBytes(new_node_size, page);
637
638 heap()->StartIncrementalMarkingIfAllocationLimitIsReachedBackground();
639
640 size_t used_size_in_bytes = Min(new_node_size, max_size_in_bytes);
641
642 Address start = new_node.address();
643 Address end = new_node.address() + new_node_size;
644 Address limit = new_node.address() + used_size_in_bytes;
645 DCHECK_LE(limit, end);
646 DCHECK_LE(min_size_in_bytes, limit - start);
647 if (limit != end) {
648 Free(limit, end - limit, SpaceAccountingMode::kSpaceAccounted);
649 }
650
651 return std::make_pair(start, used_size_in_bytes);
652 }
653
654 #ifdef DEBUG
Print()655 void PagedSpace::Print() {}
656 #endif
657
658 #ifdef VERIFY_HEAP
Verify(Isolate * isolate,ObjectVisitor * visitor)659 void PagedSpace::Verify(Isolate* isolate, ObjectVisitor* visitor) {
660 bool allocation_pointer_found_in_space =
661 (allocation_info_.top() == allocation_info_.limit());
662 size_t external_space_bytes[kNumTypes];
663 size_t external_page_bytes[kNumTypes];
664
665 for (int i = 0; i < kNumTypes; i++) {
666 external_space_bytes[static_cast<ExternalBackingStoreType>(i)] = 0;
667 }
668
669 for (Page* page : *this) {
670 CHECK_EQ(page->owner(), this);
671
672 for (int i = 0; i < kNumTypes; i++) {
673 external_page_bytes[static_cast<ExternalBackingStoreType>(i)] = 0;
674 }
675
676 if (page == Page::FromAllocationAreaAddress(allocation_info_.top())) {
677 allocation_pointer_found_in_space = true;
678 }
679 CHECK(page->SweepingDone());
680 PagedSpaceObjectIterator it(isolate->heap(), this, page);
681 Address end_of_previous_object = page->area_start();
682 Address top = page->area_end();
683
684 for (HeapObject object = it.Next(); !object.is_null(); object = it.Next()) {
685 CHECK(end_of_previous_object <= object.address());
686
687 // The first word should be a map, and we expect all map pointers to
688 // be in map space.
689 Map map = object.map();
690 CHECK(map.IsMap());
691 CHECK(ReadOnlyHeap::Contains(map) ||
692 isolate->heap()->map_space()->Contains(map));
693
694 // Perform space-specific object verification.
695 VerifyObject(object);
696
697 // The object itself should look OK.
698 object.ObjectVerify(isolate);
699
700 if (identity() != RO_SPACE && !FLAG_verify_heap_skip_remembered_set) {
701 isolate->heap()->VerifyRememberedSetFor(object);
702 }
703
704 // All the interior pointers should be contained in the heap.
705 int size = object.Size();
706 object.IterateBody(map, size, visitor);
707 CHECK(object.address() + size <= top);
708 end_of_previous_object = object.address() + size;
709
710 if (object.IsExternalString()) {
711 ExternalString external_string = ExternalString::cast(object);
712 size_t size = external_string.ExternalPayloadSize();
713 external_page_bytes[ExternalBackingStoreType::kExternalString] += size;
714 }
715 }
716 for (int i = 0; i < kNumTypes; i++) {
717 ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
718 CHECK_EQ(external_page_bytes[t], page->ExternalBackingStoreBytes(t));
719 external_space_bytes[t] += external_page_bytes[t];
720 }
721 }
722 for (int i = 0; i < kNumTypes; i++) {
723 if (i == ExternalBackingStoreType::kArrayBuffer) continue;
724 ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
725 CHECK_EQ(external_space_bytes[t], ExternalBackingStoreBytes(t));
726 }
727 CHECK(allocation_pointer_found_in_space);
728
729 if (identity() == OLD_SPACE) {
730 size_t bytes = heap()->array_buffer_sweeper()->old().BytesSlow();
731 CHECK_EQ(bytes,
732 ExternalBackingStoreBytes(ExternalBackingStoreType::kArrayBuffer));
733 }
734
735 #ifdef DEBUG
736 VerifyCountersAfterSweeping(isolate->heap());
737 #endif
738 }
739
VerifyLiveBytes()740 void PagedSpace::VerifyLiveBytes() {
741 IncrementalMarking::MarkingState* marking_state =
742 heap()->incremental_marking()->marking_state();
743 for (Page* page : *this) {
744 CHECK(page->SweepingDone());
745 PagedSpaceObjectIterator it(heap(), this, page);
746 int black_size = 0;
747 for (HeapObject object = it.Next(); !object.is_null(); object = it.Next()) {
748 // All the interior pointers should be contained in the heap.
749 if (marking_state->IsBlack(object)) {
750 black_size += object.Size();
751 }
752 }
753 CHECK_LE(black_size, marking_state->live_bytes(page));
754 }
755 }
756 #endif // VERIFY_HEAP
757
758 #ifdef DEBUG
VerifyCountersAfterSweeping(Heap * heap)759 void PagedSpace::VerifyCountersAfterSweeping(Heap* heap) {
760 size_t total_capacity = 0;
761 size_t total_allocated = 0;
762 for (Page* page : *this) {
763 DCHECK(page->SweepingDone());
764 total_capacity += page->area_size();
765 PagedSpaceObjectIterator it(heap, this, page);
766 size_t real_allocated = 0;
767 for (HeapObject object = it.Next(); !object.is_null(); object = it.Next()) {
768 if (!object.IsFreeSpaceOrFiller()) {
769 real_allocated += object.Size();
770 }
771 }
772 total_allocated += page->allocated_bytes();
773 // The real size can be smaller than the accounted size if array trimming,
774 // object slack tracking happened after sweeping.
775 DCHECK_LE(real_allocated, accounting_stats_.AllocatedOnPage(page));
776 DCHECK_EQ(page->allocated_bytes(), accounting_stats_.AllocatedOnPage(page));
777 }
778 DCHECK_EQ(total_capacity, accounting_stats_.Capacity());
779 DCHECK_EQ(total_allocated, accounting_stats_.Size());
780 }
781
VerifyCountersBeforeConcurrentSweeping()782 void PagedSpace::VerifyCountersBeforeConcurrentSweeping() {
783 // We need to refine the counters on pages that are already swept and have
784 // not been moved over to the actual space. Otherwise, the AccountingStats
785 // are just an over approximation.
786 RefillFreeList();
787
788 size_t total_capacity = 0;
789 size_t total_allocated = 0;
790 auto marking_state =
791 heap()->incremental_marking()->non_atomic_marking_state();
792 for (Page* page : *this) {
793 size_t page_allocated =
794 page->SweepingDone()
795 ? page->allocated_bytes()
796 : static_cast<size_t>(marking_state->live_bytes(page));
797 total_capacity += page->area_size();
798 total_allocated += page_allocated;
799 DCHECK_EQ(page_allocated, accounting_stats_.AllocatedOnPage(page));
800 }
801 DCHECK_EQ(total_capacity, accounting_stats_.Capacity());
802 DCHECK_EQ(total_allocated, accounting_stats_.Size());
803 }
804 #endif
805
UpdateInlineAllocationLimit(size_t min_size)806 void PagedSpace::UpdateInlineAllocationLimit(size_t min_size) {
807 // Ensure there are no unaccounted allocations.
808 DCHECK_EQ(allocation_info_.start(), allocation_info_.top());
809
810 Address new_limit = ComputeLimit(top(), limit(), min_size);
811 DCHECK_LE(top(), new_limit);
812 DCHECK_LE(new_limit, limit());
813 DecreaseLimit(new_limit);
814 }
815
816 // -----------------------------------------------------------------------------
817 // OldSpace implementation
818
PrepareForMarkCompact()819 void PagedSpace::PrepareForMarkCompact() {
820 // We don't have a linear allocation area while sweeping. It will be restored
821 // on the first allocation after the sweep.
822 FreeLinearAllocationArea();
823
824 // Clear the free list before a full GC---it will be rebuilt afterward.
825 free_list_->Reset();
826 }
827
RefillLabMain(int size_in_bytes,AllocationOrigin origin)828 bool PagedSpace::RefillLabMain(int size_in_bytes, AllocationOrigin origin) {
829 VMState<GC> state(heap()->isolate());
830 RuntimeCallTimerScope runtime_timer(
831 heap()->isolate(), RuntimeCallCounterId::kGC_Custom_SlowAllocateRaw);
832 return RawRefillLabMain(size_in_bytes, origin);
833 }
834
Expand()835 Page* LocalSpace::Expand() {
836 Page* page = PagedSpace::Expand();
837 new_pages_.push_back(page);
838 return page;
839 }
840
RefillLabMain(int size_in_bytes,AllocationOrigin origin)841 bool CompactionSpace::RefillLabMain(int size_in_bytes,
842 AllocationOrigin origin) {
843 return RawRefillLabMain(size_in_bytes, origin);
844 }
845
TryExpand(int size_in_bytes,AllocationOrigin origin)846 bool PagedSpace::TryExpand(int size_in_bytes, AllocationOrigin origin) {
847 Page* page = Expand();
848 if (!page) return false;
849 if (!is_compaction_space()) {
850 heap()->NotifyOldGenerationExpansion(identity(), page);
851 }
852 DCHECK((CountTotalPages() > 1) ||
853 (static_cast<size_t>(size_in_bytes) <= free_list_->Available()));
854 return TryAllocationFromFreeListMain(static_cast<size_t>(size_in_bytes),
855 origin);
856 }
857
RawRefillLabMain(int size_in_bytes,AllocationOrigin origin)858 bool PagedSpace::RawRefillLabMain(int size_in_bytes, AllocationOrigin origin) {
859 // Non-compaction local spaces are not supported.
860 DCHECK_IMPLIES(is_local_space(), is_compaction_space());
861
862 // Allocation in this space has failed.
863 DCHECK_GE(size_in_bytes, 0);
864 const int kMaxPagesToSweep = 1;
865
866 if (TryAllocationFromFreeListMain(size_in_bytes, origin)) return true;
867
868 MarkCompactCollector* collector = heap()->mark_compact_collector();
869 // Sweeping is still in progress.
870 if (collector->sweeping_in_progress()) {
871 // First try to refill the free-list, concurrent sweeper threads
872 // may have freed some objects in the meantime.
873 RefillFreeList();
874
875 // Retry the free list allocation.
876 if (TryAllocationFromFreeListMain(static_cast<size_t>(size_in_bytes),
877 origin))
878 return true;
879
880 if (ContributeToSweepingMain(size_in_bytes, kMaxPagesToSweep, size_in_bytes,
881 origin))
882 return true;
883 }
884
885 if (is_compaction_space()) {
886 // The main thread may have acquired all swept pages. Try to steal from
887 // it. This can only happen during young generation evacuation.
888 PagedSpace* main_space = heap()->paged_space(identity());
889 Page* page = main_space->RemovePageSafe(size_in_bytes);
890 if (page != nullptr) {
891 AddPage(page);
892 if (TryAllocationFromFreeListMain(static_cast<size_t>(size_in_bytes),
893 origin))
894 return true;
895 }
896 }
897
898 if (heap()->ShouldExpandOldGenerationOnSlowAllocation() &&
899 heap()->CanExpandOldGeneration(AreaSize())) {
900 if (TryExpand(size_in_bytes, origin)) {
901 return true;
902 }
903 }
904
905 // Try sweeping all pages.
906 if (ContributeToSweepingMain(0, 0, size_in_bytes, origin)) {
907 return true;
908 }
909
910 if (heap()->gc_state() != Heap::NOT_IN_GC && !heap()->force_oom()) {
911 // Avoid OOM crash in the GC in order to invoke NearHeapLimitCallback after
912 // GC and give it a chance to increase the heap limit.
913 return TryExpand(size_in_bytes, origin);
914 }
915 return false;
916 }
917
ContributeToSweepingMain(int required_freed_bytes,int max_pages,int size_in_bytes,AllocationOrigin origin)918 bool PagedSpace::ContributeToSweepingMain(int required_freed_bytes,
919 int max_pages, int size_in_bytes,
920 AllocationOrigin origin) {
921 // Cleanup invalidated old-to-new refs for compaction space in the
922 // final atomic pause.
923 Sweeper::FreeSpaceMayContainInvalidatedSlots invalidated_slots_in_free_space =
924 is_compaction_space() ? Sweeper::FreeSpaceMayContainInvalidatedSlots::kYes
925 : Sweeper::FreeSpaceMayContainInvalidatedSlots::kNo;
926
927 MarkCompactCollector* collector = heap()->mark_compact_collector();
928 if (collector->sweeping_in_progress()) {
929 collector->sweeper()->ParallelSweepSpace(identity(), required_freed_bytes,
930 max_pages,
931 invalidated_slots_in_free_space);
932 RefillFreeList();
933 return TryAllocationFromFreeListMain(size_in_bytes, origin);
934 }
935 return false;
936 }
937
AllocateRawSlow(int size_in_bytes,AllocationAlignment alignment,AllocationOrigin origin)938 AllocationResult PagedSpace::AllocateRawSlow(int size_in_bytes,
939 AllocationAlignment alignment,
940 AllocationOrigin origin) {
941 if (!is_local_space()) {
942 // Start incremental marking before the actual allocation, this allows the
943 // allocation function to mark the object black when incremental marking is
944 // running.
945 heap()->StartIncrementalMarkingIfAllocationLimitIsReached(
946 heap()->GCFlagsForIncrementalMarking(),
947 kGCCallbackScheduleIdleGarbageCollection);
948 }
949
950 #ifdef V8_HOST_ARCH_32_BIT
951 AllocationResult result =
952 alignment != kWordAligned
953 ? AllocateRawAligned(size_in_bytes, alignment, origin)
954 : AllocateRawUnaligned(size_in_bytes, origin);
955 #else
956 AllocationResult result = AllocateRawUnaligned(size_in_bytes, origin);
957 #endif
958 return result;
959 }
960
961 // -----------------------------------------------------------------------------
962 // MapSpace implementation
963
964 // TODO(dmercadier): use a heap instead of sorting like that.
965 // Using a heap will have multiple benefits:
966 // - for now, SortFreeList is only called after sweeping, which is somewhat
967 // late. Using a heap, sorting could be done online: FreeListCategories would
968 // be inserted in a heap (ie, in a sorted manner).
969 // - SortFreeList is a bit fragile: any change to FreeListMap (or to
970 // MapSpace::free_list_) could break it.
SortFreeList()971 void MapSpace::SortFreeList() {
972 using LiveBytesPagePair = std::pair<size_t, Page*>;
973 std::vector<LiveBytesPagePair> pages;
974 pages.reserve(CountTotalPages());
975
976 for (Page* p : *this) {
977 free_list()->RemoveCategory(p->free_list_category(kFirstCategory));
978 pages.push_back(std::make_pair(p->allocated_bytes(), p));
979 }
980
981 // Sorting by least-allocated-bytes first.
982 std::sort(pages.begin(), pages.end(),
983 [](const LiveBytesPagePair& a, const LiveBytesPagePair& b) {
984 return a.first < b.first;
985 });
986
987 for (LiveBytesPagePair const& p : pages) {
988 // Since AddCategory inserts in head position, it reverts the order produced
989 // by the sort above: least-allocated-bytes will be Added first, and will
990 // therefore be the last element (and the first one will be
991 // most-allocated-bytes).
992 free_list()->AddCategory(p.second->free_list_category(kFirstCategory));
993 }
994 }
995
996 #ifdef VERIFY_HEAP
VerifyObject(HeapObject object)997 void MapSpace::VerifyObject(HeapObject object) { CHECK(object.IsMap()); }
998 #endif
999
1000 } // namespace internal
1001 } // namespace v8
1002