summaryrefslogtreecommitdiffstats
path: root/chromium/v8/src/heap/paged-spaces.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/src/heap/paged-spaces.cc')
-rw-r--r--chromium/v8/src/heap/paged-spaces.cc1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/chromium/v8/src/heap/paged-spaces.cc b/chromium/v8/src/heap/paged-spaces.cc
new file mode 100644
index 00000000000..dabdf2d5a0e
--- /dev/null
+++ b/chromium/v8/src/heap/paged-spaces.cc
@@ -0,0 +1,1047 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/heap/paged-spaces.h"
+
+#include "src/base/optional.h"
+#include "src/base/platform/mutex.h"
+#include "src/execution/isolate.h"
+#include "src/execution/vm-state-inl.h"
+#include "src/heap/array-buffer-sweeper.h"
+#include "src/heap/array-buffer-tracker-inl.h"
+#include "src/heap/heap.h"
+#include "src/heap/incremental-marking.h"
+#include "src/heap/memory-allocator.h"
+#include "src/heap/memory-chunk-inl.h"
+#include "src/heap/paged-spaces-inl.h"
+#include "src/heap/read-only-heap.h"
+#include "src/logging/counters.h"
+#include "src/objects/string.h"
+#include "src/utils/utils.h"
+
+namespace v8 {
+namespace internal {
+
+// ----------------------------------------------------------------------------
+// PagedSpaceObjectIterator
+
+PagedSpaceObjectIterator::PagedSpaceObjectIterator(Heap* heap,
+ PagedSpace* space)
+ : cur_addr_(kNullAddress),
+ cur_end_(kNullAddress),
+ space_(space),
+ page_range_(space->first_page(), nullptr),
+ current_page_(page_range_.begin()) {
+ space_->MakeLinearAllocationAreaIterable();
+ heap->mark_compact_collector()->EnsureSweepingCompleted();
+}
+
+PagedSpaceObjectIterator::PagedSpaceObjectIterator(Heap* heap,
+ PagedSpace* space,
+ Page* page)
+ : cur_addr_(kNullAddress),
+ cur_end_(kNullAddress),
+ space_(space),
+ page_range_(page),
+ current_page_(page_range_.begin()) {
+ space_->MakeLinearAllocationAreaIterable();
+ heap->mark_compact_collector()->EnsureSweepingCompleted();
+#ifdef DEBUG
+ AllocationSpace owner = page->owner_identity();
+ DCHECK(owner == OLD_SPACE || owner == MAP_SPACE || owner == CODE_SPACE);
+#endif // DEBUG
+}
+
+PagedSpaceObjectIterator::PagedSpaceObjectIterator(OffThreadSpace* space)
+ : cur_addr_(kNullAddress),
+ cur_end_(kNullAddress),
+ space_(space),
+ page_range_(space->first_page(), nullptr),
+ current_page_(page_range_.begin()) {
+ space_->MakeLinearAllocationAreaIterable();
+}
+
+// We have hit the end of the page and should advance to the next block of
+// objects. This happens at the end of the page.
+bool PagedSpaceObjectIterator::AdvanceToNextPage() {
+ DCHECK_EQ(cur_addr_, cur_end_);
+ if (current_page_ == page_range_.end()) return false;
+ Page* cur_page = *(current_page_++);
+
+ cur_addr_ = cur_page->area_start();
+ cur_end_ = cur_page->area_end();
+ DCHECK(cur_page->SweepingDone());
+ return true;
+}
+Page* PagedSpace::InitializePage(MemoryChunk* chunk) {
+ Page* page = static_cast<Page*>(chunk);
+ DCHECK_EQ(
+ MemoryChunkLayout::AllocatableMemoryInMemoryChunk(page->owner_identity()),
+ page->area_size());
+ // Make sure that categories are initialized before freeing the area.
+ page->ResetAllocationStatistics();
+ page->SetOldGenerationPageFlags(!is_off_thread_space() &&
+ heap()->incremental_marking()->IsMarking());
+ page->AllocateFreeListCategories();
+ page->InitializeFreeListCategories();
+ page->list_node().Initialize();
+ page->InitializationMemoryFence();
+ return page;
+}
+
+PagedSpace::PagedSpace(Heap* heap, AllocationSpace space,
+ Executability executable, FreeList* free_list,
+ LocalSpaceKind local_space_kind)
+ : SpaceWithLinearArea(heap, space, free_list),
+ executable_(executable),
+ local_space_kind_(local_space_kind) {
+ area_size_ = MemoryChunkLayout::AllocatableMemoryInMemoryChunk(space);
+ accounting_stats_.Clear();
+}
+
+void PagedSpace::TearDown() {
+ while (!memory_chunk_list_.Empty()) {
+ MemoryChunk* chunk = memory_chunk_list_.front();
+ memory_chunk_list_.Remove(chunk);
+ heap()->memory_allocator()->Free<MemoryAllocator::kFull>(chunk);
+ }
+ accounting_stats_.Clear();
+}
+
+void PagedSpace::RefillFreeList() {
+ // Any PagedSpace might invoke RefillFreeList. We filter all but our old
+ // generation spaces out.
+ if (identity() != OLD_SPACE && identity() != CODE_SPACE &&
+ identity() != MAP_SPACE) {
+ return;
+ }
+ DCHECK_NE(local_space_kind(), LocalSpaceKind::kOffThreadSpace);
+ DCHECK_IMPLIES(is_local_space(), is_compaction_space());
+ MarkCompactCollector* collector = heap()->mark_compact_collector();
+ size_t added = 0;
+
+ {
+ Page* p = nullptr;
+ while ((p = collector->sweeper()->GetSweptPageSafe(this)) != nullptr) {
+ // We regularly sweep NEVER_ALLOCATE_ON_PAGE pages. We drop the freelist
+ // entries here to make them unavailable for allocations.
+ if (p->IsFlagSet(Page::NEVER_ALLOCATE_ON_PAGE)) {
+ p->ForAllFreeListCategories([this](FreeListCategory* category) {
+ category->Reset(free_list());
+ });
+ }
+
+ // Also merge old-to-new remembered sets if not scavenging because of
+ // data races: One thread might iterate remembered set, while another
+ // thread merges them.
+ if (local_space_kind() != LocalSpaceKind::kCompactionSpaceForScavenge) {
+ p->MergeOldToNewRememberedSets();
+ }
+
+ // Only during compaction pages can actually change ownership. This is
+ // safe because there exists no other competing action on the page links
+ // during compaction.
+ if (is_compaction_space()) {
+ DCHECK_NE(this, p->owner());
+ PagedSpace* owner = reinterpret_cast<PagedSpace*>(p->owner());
+ base::MutexGuard guard(owner->mutex());
+ owner->RefineAllocatedBytesAfterSweeping(p);
+ owner->RemovePage(p);
+ added += AddPage(p);
+ } else {
+ base::MutexGuard guard(mutex());
+ DCHECK_EQ(this, p->owner());
+ RefineAllocatedBytesAfterSweeping(p);
+ added += RelinkFreeListCategories(p);
+ }
+ added += p->wasted_memory();
+ if (is_compaction_space() && (added > kCompactionMemoryWanted)) break;
+ }
+ }
+}
+
+void OffThreadSpace::RefillFreeList() {
+ // We should never try to refill the free list in off-thread space, because
+ // we know it will always be fully linear.
+ UNREACHABLE();
+}
+
+void PagedSpace::MergeLocalSpace(LocalSpace* other) {
+ base::MutexGuard guard(mutex());
+
+ DCHECK(identity() == other->identity());
+
+ // Unmerged fields:
+ // area_size_
+ other->FreeLinearAllocationArea();
+
+ for (int i = static_cast<int>(AllocationOrigin::kFirstAllocationOrigin);
+ i <= static_cast<int>(AllocationOrigin::kLastAllocationOrigin); i++) {
+ allocations_origins_[i] += other->allocations_origins_[i];
+ }
+
+ // The linear allocation area of {other} should be destroyed now.
+ DCHECK_EQ(kNullAddress, other->top());
+ DCHECK_EQ(kNullAddress, other->limit());
+
+ bool merging_from_off_thread = other->is_off_thread_space();
+
+ // Move over pages.
+ for (auto it = other->begin(); it != other->end();) {
+ Page* p = *(it++);
+
+ if (merging_from_off_thread) {
+ DCHECK_NULL(p->sweeping_slot_set());
+
+ // Make sure the page is entirely white.
+ CHECK(heap()
+ ->incremental_marking()
+ ->non_atomic_marking_state()
+ ->bitmap(p)
+ ->IsClean());
+
+ p->SetOldGenerationPageFlags(heap()->incremental_marking()->IsMarking());
+ if (heap()->incremental_marking()->black_allocation()) {
+ p->CreateBlackArea(p->area_start(), p->HighWaterMark());
+ }
+ } else {
+ p->MergeOldToNewRememberedSets();
+ }
+
+ // Ensure that pages are initialized before objects on it are discovered by
+ // concurrent markers.
+ p->InitializationMemoryFence();
+
+ // Relinking requires the category to be unlinked.
+ other->RemovePage(p);
+ AddPage(p);
+ heap()->NotifyOldGenerationExpansion(identity(), p);
+ DCHECK_IMPLIES(
+ !p->IsFlagSet(Page::NEVER_ALLOCATE_ON_PAGE),
+ p->AvailableInFreeList() == p->AvailableInFreeListFromAllocatedBytes());
+
+ // TODO(leszeks): Here we should allocation step, but:
+ // 1. Allocation groups are currently not handled properly by the sampling
+ // allocation profiler, and
+ // 2. Observers might try to take the space lock, which isn't reentrant.
+ // We'll have to come up with a better solution for allocation stepping
+ // before shipping, which will likely be using LocalHeap.
+ }
+
+ DCHECK_EQ(0u, other->Size());
+ DCHECK_EQ(0u, other->Capacity());
+}
+
+size_t PagedSpace::CommittedPhysicalMemory() {
+ if (!base::OS::HasLazyCommits()) return CommittedMemory();
+ BasicMemoryChunk::UpdateHighWaterMark(allocation_info_.top());
+ size_t size = 0;
+ for (Page* page : *this) {
+ size += page->CommittedPhysicalMemory();
+ }
+ return size;
+}
+
+bool PagedSpace::ContainsSlow(Address addr) const {
+ Page* p = Page::FromAddress(addr);
+ for (const Page* page : *this) {
+ if (page == p) return true;
+ }
+ return false;
+}
+
+void PagedSpace::RefineAllocatedBytesAfterSweeping(Page* page) {
+ CHECK(page->SweepingDone());
+ auto marking_state =
+ heap()->incremental_marking()->non_atomic_marking_state();
+ // The live_byte on the page was accounted in the space allocated
+ // bytes counter. After sweeping allocated_bytes() contains the
+ // accurate live byte count on the page.
+ size_t old_counter = marking_state->live_bytes(page);
+ size_t new_counter = page->allocated_bytes();
+ DCHECK_GE(old_counter, new_counter);
+ if (old_counter > new_counter) {
+ DecreaseAllocatedBytes(old_counter - new_counter, page);
+ // Give the heap a chance to adjust counters in response to the
+ // more precise and smaller old generation size.
+ heap()->NotifyRefinedOldGenerationSize(old_counter - new_counter);
+ }
+ marking_state->SetLiveBytes(page, 0);
+}
+
+Page* PagedSpace::RemovePageSafe(int size_in_bytes) {
+ base::MutexGuard guard(mutex());
+ Page* page = free_list()->GetPageForSize(size_in_bytes);
+ if (!page) return nullptr;
+ RemovePage(page);
+ return page;
+}
+
+size_t PagedSpace::AddPage(Page* page) {
+ CHECK(page->SweepingDone());
+ page->set_owner(this);
+ memory_chunk_list_.PushBack(page);
+ AccountCommitted(page->size());
+ IncreaseCapacity(page->area_size());
+ IncreaseAllocatedBytes(page->allocated_bytes(), page);
+ for (size_t i = 0; i < ExternalBackingStoreType::kNumTypes; i++) {
+ ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
+ IncrementExternalBackingStoreBytes(t, page->ExternalBackingStoreBytes(t));
+ }
+ return RelinkFreeListCategories(page);
+}
+
+void PagedSpace::RemovePage(Page* page) {
+ CHECK(page->SweepingDone());
+ memory_chunk_list_.Remove(page);
+ UnlinkFreeListCategories(page);
+ DecreaseAllocatedBytes(page->allocated_bytes(), page);
+ DecreaseCapacity(page->area_size());
+ AccountUncommitted(page->size());
+ for (size_t i = 0; i < ExternalBackingStoreType::kNumTypes; i++) {
+ ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
+ DecrementExternalBackingStoreBytes(t, page->ExternalBackingStoreBytes(t));
+ }
+}
+
+size_t PagedSpace::ShrinkPageToHighWaterMark(Page* page) {
+ size_t unused = page->ShrinkToHighWaterMark();
+ accounting_stats_.DecreaseCapacity(static_cast<intptr_t>(unused));
+ AccountUncommitted(unused);
+ return unused;
+}
+
+void PagedSpace::ResetFreeList() {
+ for (Page* page : *this) {
+ free_list_->EvictFreeListItems(page);
+ }
+ DCHECK(free_list_->IsEmpty());
+}
+
+void PagedSpace::ShrinkImmortalImmovablePages() {
+ DCHECK(!heap()->deserialization_complete());
+ BasicMemoryChunk::UpdateHighWaterMark(allocation_info_.top());
+ FreeLinearAllocationArea();
+ ResetFreeList();
+ for (Page* page : *this) {
+ DCHECK(page->IsFlagSet(Page::NEVER_EVACUATE));
+ ShrinkPageToHighWaterMark(page);
+ }
+}
+
+Page* PagedSpace::AllocatePage() {
+ return heap()->memory_allocator()->AllocatePage(AreaSize(), this,
+ executable());
+}
+
+Page* PagedSpace::Expand() {
+ Page* page = AllocatePage();
+ if (page == nullptr) return nullptr;
+ AddPage(page);
+ Free(page->area_start(), page->area_size(),
+ SpaceAccountingMode::kSpaceAccounted);
+ return page;
+}
+
+Page* PagedSpace::ExpandBackground(LocalHeap* local_heap) {
+ Page* page = AllocatePage();
+ if (page == nullptr) return nullptr;
+ ParkedMutexGuard lock(local_heap, &allocation_mutex_);
+ AddPage(page);
+ Free(page->area_start(), page->area_size(),
+ SpaceAccountingMode::kSpaceAccounted);
+ return page;
+}
+
+int PagedSpace::CountTotalPages() {
+ int count = 0;
+ for (Page* page : *this) {
+ count++;
+ USE(page);
+ }
+ return count;
+}
+
+void PagedSpace::SetLinearAllocationArea(Address top, Address limit) {
+ SetTopAndLimit(top, limit);
+ if (top != kNullAddress && top != limit && !is_off_thread_space() &&
+ heap()->incremental_marking()->black_allocation()) {
+ Page::FromAllocationAreaAddress(top)->CreateBlackArea(top, limit);
+ }
+}
+
+void PagedSpace::DecreaseLimit(Address new_limit) {
+ Address old_limit = limit();
+ DCHECK_LE(top(), new_limit);
+ DCHECK_GE(old_limit, new_limit);
+ if (new_limit != old_limit) {
+ SetTopAndLimit(top(), new_limit);
+ Free(new_limit, old_limit - new_limit,
+ SpaceAccountingMode::kSpaceAccounted);
+ if (heap()->incremental_marking()->black_allocation()) {
+ Page::FromAllocationAreaAddress(new_limit)->DestroyBlackArea(new_limit,
+ old_limit);
+ }
+ }
+}
+
+void PagedSpace::MarkLinearAllocationAreaBlack() {
+ DCHECK(heap()->incremental_marking()->black_allocation());
+ Address current_top = top();
+ Address current_limit = limit();
+ if (current_top != kNullAddress && current_top != current_limit) {
+ Page::FromAllocationAreaAddress(current_top)
+ ->CreateBlackArea(current_top, current_limit);
+ }
+}
+
+void PagedSpace::UnmarkLinearAllocationArea() {
+ Address current_top = top();
+ Address current_limit = limit();
+ if (current_top != kNullAddress && current_top != current_limit) {
+ Page::FromAllocationAreaAddress(current_top)
+ ->DestroyBlackArea(current_top, current_limit);
+ }
+}
+
+void PagedSpace::MakeLinearAllocationAreaIterable() {
+ Address current_top = top();
+ Address current_limit = limit();
+ if (current_top != kNullAddress && current_top != current_limit) {
+ base::Optional<CodePageMemoryModificationScope> optional_scope;
+
+ if (identity() == CODE_SPACE) {
+ MemoryChunk* chunk = MemoryChunk::FromAddress(current_top);
+ optional_scope.emplace(chunk);
+ }
+
+ heap_->CreateFillerObjectAt(current_top,
+ static_cast<int>(current_limit - current_top),
+ ClearRecordedSlots::kNo);
+ }
+}
+
+void PagedSpace::FreeLinearAllocationArea() {
+ // Mark the old linear allocation area with a free space map so it can be
+ // skipped when scanning the heap.
+ Address current_top = top();
+ Address current_limit = limit();
+ if (current_top == kNullAddress) {
+ DCHECK_EQ(kNullAddress, current_limit);
+ return;
+ }
+
+ if (!is_off_thread_space() &&
+ heap()->incremental_marking()->black_allocation()) {
+ Page* page = Page::FromAllocationAreaAddress(current_top);
+
+ // Clear the bits in the unused black area.
+ if (current_top != current_limit) {
+ IncrementalMarking::MarkingState* marking_state =
+ heap()->incremental_marking()->marking_state();
+ marking_state->bitmap(page)->ClearRange(
+ page->AddressToMarkbitIndex(current_top),
+ page->AddressToMarkbitIndex(current_limit));
+ marking_state->IncrementLiveBytes(
+ page, -static_cast<int>(current_limit - current_top));
+ }
+ }
+
+ if (!is_local_space()) {
+ InlineAllocationStep(current_top, kNullAddress, kNullAddress, 0);
+ }
+
+ SetTopAndLimit(kNullAddress, kNullAddress);
+ DCHECK_GE(current_limit, current_top);
+
+ // The code page of the linear allocation area needs to be unprotected
+ // because we are going to write a filler into that memory area below.
+ if (identity() == CODE_SPACE) {
+ heap()->UnprotectAndRegisterMemoryChunk(
+ MemoryChunk::FromAddress(current_top));
+ }
+ Free(current_top, current_limit - current_top,
+ SpaceAccountingMode::kSpaceAccounted);
+}
+
+void PagedSpace::ReleasePage(Page* page) {
+ DCHECK_EQ(
+ 0, heap()->incremental_marking()->non_atomic_marking_state()->live_bytes(
+ page));
+ DCHECK_EQ(page->owner(), this);
+
+ free_list_->EvictFreeListItems(page);
+
+ if (Page::FromAllocationAreaAddress(allocation_info_.top()) == page) {
+ DCHECK(!top_on_previous_step_);
+ allocation_info_.Reset(kNullAddress, kNullAddress);
+ }
+
+ heap()->isolate()->RemoveCodeMemoryChunk(page);
+
+ AccountUncommitted(page->size());
+ accounting_stats_.DecreaseCapacity(page->area_size());
+ heap()->memory_allocator()->Free<MemoryAllocator::kPreFreeAndQueue>(page);
+}
+
+void PagedSpace::SetReadable() {
+ DCHECK(identity() == CODE_SPACE);
+ for (Page* page : *this) {
+ CHECK(heap()->memory_allocator()->IsMemoryChunkExecutable(page));
+ page->SetReadable();
+ }
+}
+
+void PagedSpace::SetReadAndExecutable() {
+ DCHECK(identity() == CODE_SPACE);
+ for (Page* page : *this) {
+ CHECK(heap()->memory_allocator()->IsMemoryChunkExecutable(page));
+ page->SetReadAndExecutable();
+ }
+}
+
+void PagedSpace::SetReadAndWritable() {
+ DCHECK(identity() == CODE_SPACE);
+ for (Page* page : *this) {
+ CHECK(heap()->memory_allocator()->IsMemoryChunkExecutable(page));
+ page->SetReadAndWritable();
+ }
+}
+
+std::unique_ptr<ObjectIterator> PagedSpace::GetObjectIterator(Heap* heap) {
+ return std::unique_ptr<ObjectIterator>(
+ new PagedSpaceObjectIterator(heap, this));
+}
+
+bool PagedSpace::RefillLinearAllocationAreaFromFreeList(
+ size_t size_in_bytes, AllocationOrigin origin) {
+ DCHECK(IsAligned(size_in_bytes, kTaggedSize));
+ DCHECK_LE(top(), limit());
+#ifdef DEBUG
+ if (top() != limit()) {
+ DCHECK_EQ(Page::FromAddress(top()), Page::FromAddress(limit() - 1));
+ }
+#endif
+ // Don't free list allocate if there is linear space available.
+ DCHECK_LT(static_cast<size_t>(limit() - top()), size_in_bytes);
+
+ // Mark the old linear allocation area with a free space map so it can be
+ // skipped when scanning the heap. This also puts it back in the free list
+ // if it is big enough.
+ FreeLinearAllocationArea();
+
+ if (!is_local_space()) {
+ heap()->StartIncrementalMarkingIfAllocationLimitIsReached(
+ heap()->GCFlagsForIncrementalMarking(),
+ kGCCallbackScheduleIdleGarbageCollection);
+ }
+
+ size_t new_node_size = 0;
+ FreeSpace new_node =
+ free_list_->Allocate(size_in_bytes, &new_node_size, origin);
+ if (new_node.is_null()) return false;
+ DCHECK_GE(new_node_size, size_in_bytes);
+
+ // The old-space-step might have finished sweeping and restarted marking.
+ // Verify that it did not turn the page of the new node into an evacuation
+ // candidate.
+ DCHECK(!MarkCompactCollector::IsOnEvacuationCandidate(new_node));
+
+ // Memory in the linear allocation area is counted as allocated. We may free
+ // a little of this again immediately - see below.
+ Page* page = Page::FromHeapObject(new_node);
+ IncreaseAllocatedBytes(new_node_size, page);
+
+ Address start = new_node.address();
+ Address end = new_node.address() + new_node_size;
+ Address limit = ComputeLimit(start, end, size_in_bytes);
+ DCHECK_LE(limit, end);
+ DCHECK_LE(size_in_bytes, limit - start);
+ if (limit != end) {
+ if (identity() == CODE_SPACE) {
+ heap()->UnprotectAndRegisterMemoryChunk(page);
+ }
+ Free(limit, end - limit, SpaceAccountingMode::kSpaceAccounted);
+ }
+ SetLinearAllocationArea(start, limit);
+
+ return true;
+}
+
+base::Optional<std::pair<Address, size_t>>
+PagedSpace::SlowGetLinearAllocationAreaBackground(LocalHeap* local_heap,
+ size_t min_size_in_bytes,
+ size_t max_size_in_bytes,
+ AllocationAlignment alignment,
+ AllocationOrigin origin) {
+ DCHECK(!is_local_space() && identity() == OLD_SPACE);
+ DCHECK_EQ(origin, AllocationOrigin::kRuntime);
+
+ auto result = TryAllocationFromFreeListBackground(
+ local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
+ if (result) return result;
+
+ MarkCompactCollector* collector = heap()->mark_compact_collector();
+ // Sweeping is still in progress.
+ if (collector->sweeping_in_progress()) {
+ // First try to refill the free-list, concurrent sweeper threads
+ // may have freed some objects in the meantime.
+ {
+ ParkedMutexGuard lock(local_heap, &allocation_mutex_);
+ RefillFreeList();
+ }
+
+ // Retry the free list allocation.
+ auto result = TryAllocationFromFreeListBackground(
+ local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
+ if (result) return result;
+
+ Sweeper::FreeSpaceMayContainInvalidatedSlots
+ invalidated_slots_in_free_space =
+ Sweeper::FreeSpaceMayContainInvalidatedSlots::kNo;
+
+ const int kMaxPagesToSweep = 1;
+ int max_freed = collector->sweeper()->ParallelSweepSpace(
+ identity(), static_cast<int>(min_size_in_bytes), kMaxPagesToSweep,
+ invalidated_slots_in_free_space);
+
+ {
+ ParkedMutexGuard lock(local_heap, &allocation_mutex_);
+ RefillFreeList();
+ }
+
+ if (static_cast<size_t>(max_freed) >= min_size_in_bytes) {
+ auto result = TryAllocationFromFreeListBackground(
+ local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
+ if (result) return result;
+ }
+ }
+
+ if (heap()->ShouldExpandOldGenerationOnSlowAllocation(local_heap) &&
+ heap()->CanExpandOldGenerationBackground(AreaSize()) &&
+ ExpandBackground(local_heap)) {
+ DCHECK((CountTotalPages() > 1) ||
+ (min_size_in_bytes <= free_list_->Available()));
+ auto result = TryAllocationFromFreeListBackground(
+ local_heap, min_size_in_bytes, max_size_in_bytes, alignment, origin);
+ if (result) return result;
+ }
+
+ // TODO(dinfuehr): Complete sweeping here and try allocation again.
+
+ return {};
+}
+
+base::Optional<std::pair<Address, size_t>>
+PagedSpace::TryAllocationFromFreeListBackground(LocalHeap* local_heap,
+ size_t min_size_in_bytes,
+ size_t max_size_in_bytes,
+ AllocationAlignment alignment,
+ AllocationOrigin origin) {
+ ParkedMutexGuard lock(local_heap, &allocation_mutex_);
+ DCHECK_LE(min_size_in_bytes, max_size_in_bytes);
+ DCHECK_EQ(identity(), OLD_SPACE);
+
+ size_t new_node_size = 0;
+ FreeSpace new_node =
+ free_list_->Allocate(min_size_in_bytes, &new_node_size, origin);
+ if (new_node.is_null()) return {};
+ DCHECK_GE(new_node_size, min_size_in_bytes);
+
+ // The old-space-step might have finished sweeping and restarted marking.
+ // Verify that it did not turn the page of the new node into an evacuation
+ // candidate.
+ DCHECK(!MarkCompactCollector::IsOnEvacuationCandidate(new_node));
+
+ // Memory in the linear allocation area is counted as allocated. We may free
+ // a little of this again immediately - see below.
+ Page* page = Page::FromHeapObject(new_node);
+ IncreaseAllocatedBytes(new_node_size, page);
+
+ heap()->StartIncrementalMarkingIfAllocationLimitIsReachedBackground();
+
+ size_t used_size_in_bytes = Min(new_node_size, max_size_in_bytes);
+
+ Address start = new_node.address();
+ Address end = new_node.address() + new_node_size;
+ Address limit = new_node.address() + used_size_in_bytes;
+ DCHECK_LE(limit, end);
+ DCHECK_LE(min_size_in_bytes, limit - start);
+ if (limit != end) {
+ Free(limit, end - limit, SpaceAccountingMode::kSpaceAccounted);
+ }
+
+ return std::make_pair(start, used_size_in_bytes);
+}
+
+#ifdef DEBUG
+void PagedSpace::Print() {}
+#endif
+
+#ifdef VERIFY_HEAP
+void PagedSpace::Verify(Isolate* isolate, ObjectVisitor* visitor) {
+ bool allocation_pointer_found_in_space =
+ (allocation_info_.top() == allocation_info_.limit());
+ size_t external_space_bytes[kNumTypes];
+ size_t external_page_bytes[kNumTypes];
+
+ for (int i = 0; i < kNumTypes; i++) {
+ external_space_bytes[static_cast<ExternalBackingStoreType>(i)] = 0;
+ }
+
+ for (Page* page : *this) {
+ CHECK_EQ(page->owner(), this);
+
+ for (int i = 0; i < kNumTypes; i++) {
+ external_page_bytes[static_cast<ExternalBackingStoreType>(i)] = 0;
+ }
+
+ if (page == Page::FromAllocationAreaAddress(allocation_info_.top())) {
+ allocation_pointer_found_in_space = true;
+ }
+ CHECK(page->SweepingDone());
+ PagedSpaceObjectIterator it(isolate->heap(), this, page);
+ Address end_of_previous_object = page->area_start();
+ Address top = page->area_end();
+
+ for (HeapObject object = it.Next(); !object.is_null(); object = it.Next()) {
+ CHECK(end_of_previous_object <= object.address());
+
+ // The first word should be a map, and we expect all map pointers to
+ // be in map space.
+ Map map = object.map();
+ CHECK(map.IsMap());
+ CHECK(ReadOnlyHeap::Contains(map) ||
+ isolate->heap()->map_space()->Contains(map));
+
+ // Perform space-specific object verification.
+ VerifyObject(object);
+
+ // The object itself should look OK.
+ object.ObjectVerify(isolate);
+
+ if (identity() != RO_SPACE && !FLAG_verify_heap_skip_remembered_set) {
+ isolate->heap()->VerifyRememberedSetFor(object);
+ }
+
+ // All the interior pointers should be contained in the heap.
+ int size = object.Size();
+ object.IterateBody(map, size, visitor);
+ CHECK(object.address() + size <= top);
+ end_of_previous_object = object.address() + size;
+
+ if (object.IsExternalString()) {
+ ExternalString external_string = ExternalString::cast(object);
+ size_t size = external_string.ExternalPayloadSize();
+ external_page_bytes[ExternalBackingStoreType::kExternalString] += size;
+ } else if (object.IsJSArrayBuffer()) {
+ JSArrayBuffer array_buffer = JSArrayBuffer::cast(object);
+ if (ArrayBufferTracker::IsTracked(array_buffer)) {
+ size_t size =
+ ArrayBufferTracker::Lookup(isolate->heap(), array_buffer)
+ ->PerIsolateAccountingLength();
+ external_page_bytes[ExternalBackingStoreType::kArrayBuffer] += size;
+ }
+ }
+ }
+ for (int i = 0; i < kNumTypes; i++) {
+ ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
+ CHECK_EQ(external_page_bytes[t], page->ExternalBackingStoreBytes(t));
+ external_space_bytes[t] += external_page_bytes[t];
+ }
+ }
+ for (int i = 0; i < kNumTypes; i++) {
+ if (V8_ARRAY_BUFFER_EXTENSION_BOOL &&
+ i == ExternalBackingStoreType::kArrayBuffer)
+ continue;
+ ExternalBackingStoreType t = static_cast<ExternalBackingStoreType>(i);
+ CHECK_EQ(external_space_bytes[t], ExternalBackingStoreBytes(t));
+ }
+ CHECK(allocation_pointer_found_in_space);
+
+ if (identity() == OLD_SPACE && V8_ARRAY_BUFFER_EXTENSION_BOOL) {
+ size_t bytes = heap()->array_buffer_sweeper()->old().BytesSlow();
+ CHECK_EQ(bytes,
+ ExternalBackingStoreBytes(ExternalBackingStoreType::kArrayBuffer));
+ }
+
+#ifdef DEBUG
+ VerifyCountersAfterSweeping(isolate->heap());
+#endif
+}
+
+void PagedSpace::VerifyLiveBytes() {
+ IncrementalMarking::MarkingState* marking_state =
+ heap()->incremental_marking()->marking_state();
+ for (Page* page : *this) {
+ CHECK(page->SweepingDone());
+ PagedSpaceObjectIterator it(heap(), this, page);
+ int black_size = 0;
+ for (HeapObject object = it.Next(); !object.is_null(); object = it.Next()) {
+ // All the interior pointers should be contained in the heap.
+ if (marking_state->IsBlack(object)) {
+ black_size += object.Size();
+ }
+ }
+ CHECK_LE(black_size, marking_state->live_bytes(page));
+ }
+}
+#endif // VERIFY_HEAP
+
+#ifdef DEBUG
+void PagedSpace::VerifyCountersAfterSweeping(Heap* heap) {
+ size_t total_capacity = 0;
+ size_t total_allocated = 0;
+ for (Page* page : *this) {
+ DCHECK(page->SweepingDone());
+ total_capacity += page->area_size();
+ PagedSpaceObjectIterator it(heap, this, page);
+ size_t real_allocated = 0;
+ for (HeapObject object = it.Next(); !object.is_null(); object = it.Next()) {
+ if (!object.IsFreeSpaceOrFiller()) {
+ real_allocated += object.Size();
+ }
+ }
+ total_allocated += page->allocated_bytes();
+ // The real size can be smaller than the accounted size if array trimming,
+ // object slack tracking happened after sweeping.
+ DCHECK_LE(real_allocated, accounting_stats_.AllocatedOnPage(page));
+ DCHECK_EQ(page->allocated_bytes(), accounting_stats_.AllocatedOnPage(page));
+ }
+ DCHECK_EQ(total_capacity, accounting_stats_.Capacity());
+ DCHECK_EQ(total_allocated, accounting_stats_.Size());
+}
+
+void PagedSpace::VerifyCountersBeforeConcurrentSweeping() {
+ // We need to refine the counters on pages that are already swept and have
+ // not been moved over to the actual space. Otherwise, the AccountingStats
+ // are just an over approximation.
+ RefillFreeList();
+
+ size_t total_capacity = 0;
+ size_t total_allocated = 0;
+ auto marking_state =
+ heap()->incremental_marking()->non_atomic_marking_state();
+ for (Page* page : *this) {
+ size_t page_allocated =
+ page->SweepingDone()
+ ? page->allocated_bytes()
+ : static_cast<size_t>(marking_state->live_bytes(page));
+ total_capacity += page->area_size();
+ total_allocated += page_allocated;
+ DCHECK_EQ(page_allocated, accounting_stats_.AllocatedOnPage(page));
+ }
+ DCHECK_EQ(total_capacity, accounting_stats_.Capacity());
+ DCHECK_EQ(total_allocated, accounting_stats_.Size());
+}
+#endif
+
+void PagedSpace::UpdateInlineAllocationLimit(size_t min_size) {
+ Address new_limit = ComputeLimit(top(), limit(), min_size);
+ DCHECK_LE(new_limit, limit());
+ DecreaseLimit(new_limit);
+}
+
+// -----------------------------------------------------------------------------
+// OldSpace implementation
+
+void PagedSpace::PrepareForMarkCompact() {
+ // We don't have a linear allocation area while sweeping. It will be restored
+ // on the first allocation after the sweep.
+ FreeLinearAllocationArea();
+
+ // Clear the free list before a full GC---it will be rebuilt afterward.
+ free_list_->Reset();
+}
+
+size_t PagedSpace::SizeOfObjects() {
+ CHECK_GE(limit(), top());
+ DCHECK_GE(Size(), static_cast<size_t>(limit() - top()));
+ return Size() - (limit() - top());
+}
+
+bool PagedSpace::EnsureSweptAndRetryAllocation(int size_in_bytes,
+ AllocationOrigin origin) {
+ DCHECK(!is_local_space());
+ MarkCompactCollector* collector = heap()->mark_compact_collector();
+ if (collector->sweeping_in_progress()) {
+ // Wait for the sweeper threads here and complete the sweeping phase.
+ collector->EnsureSweepingCompleted();
+
+ // After waiting for the sweeper threads, there may be new free-list
+ // entries.
+ return RefillLinearAllocationAreaFromFreeList(size_in_bytes, origin);
+ }
+ return false;
+}
+
+bool PagedSpace::SlowRefillLinearAllocationArea(int size_in_bytes,
+ AllocationOrigin origin) {
+ VMState<GC> state(heap()->isolate());
+ RuntimeCallTimerScope runtime_timer(
+ heap()->isolate(), RuntimeCallCounterId::kGC_Custom_SlowAllocateRaw);
+ base::Optional<base::MutexGuard> optional_mutex;
+
+ if (FLAG_concurrent_allocation && origin != AllocationOrigin::kGC &&
+ identity() == OLD_SPACE) {
+ optional_mutex.emplace(&allocation_mutex_);
+ }
+
+ return RawSlowRefillLinearAllocationArea(size_in_bytes, origin);
+}
+
+bool CompactionSpace::SlowRefillLinearAllocationArea(int size_in_bytes,
+ AllocationOrigin origin) {
+ return RawSlowRefillLinearAllocationArea(size_in_bytes, origin);
+}
+
+bool OffThreadSpace::SlowRefillLinearAllocationArea(int size_in_bytes,
+ AllocationOrigin origin) {
+ if (RefillLinearAllocationAreaFromFreeList(size_in_bytes, origin))
+ return true;
+
+ if (heap()->CanExpandOldGenerationBackground(size_in_bytes) && Expand()) {
+ DCHECK((CountTotalPages() > 1) ||
+ (static_cast<size_t>(size_in_bytes) <= free_list_->Available()));
+ return RefillLinearAllocationAreaFromFreeList(
+ static_cast<size_t>(size_in_bytes), origin);
+ }
+
+ return false;
+}
+
+bool PagedSpace::RawSlowRefillLinearAllocationArea(int size_in_bytes,
+ AllocationOrigin origin) {
+ // Non-compaction local spaces are not supported.
+ DCHECK_IMPLIES(is_local_space(), is_compaction_space());
+
+ // Allocation in this space has failed.
+ DCHECK_GE(size_in_bytes, 0);
+ const int kMaxPagesToSweep = 1;
+
+ if (RefillLinearAllocationAreaFromFreeList(size_in_bytes, origin))
+ return true;
+
+ MarkCompactCollector* collector = heap()->mark_compact_collector();
+ // Sweeping is still in progress.
+ if (collector->sweeping_in_progress()) {
+ if (FLAG_concurrent_sweeping && !is_compaction_space() &&
+ !collector->sweeper()->AreSweeperTasksRunning()) {
+ collector->EnsureSweepingCompleted();
+ }
+
+ // First try to refill the free-list, concurrent sweeper threads
+ // may have freed some objects in the meantime.
+ RefillFreeList();
+
+ // Retry the free list allocation.
+ if (RefillLinearAllocationAreaFromFreeList(
+ static_cast<size_t>(size_in_bytes), origin))
+ return true;
+
+ if (SweepAndRetryAllocation(size_in_bytes, kMaxPagesToSweep, size_in_bytes,
+ origin))
+ return true;
+ }
+
+ if (is_compaction_space()) {
+ // The main thread may have acquired all swept pages. Try to steal from
+ // it. This can only happen during young generation evacuation.
+ PagedSpace* main_space = heap()->paged_space(identity());
+ Page* page = main_space->RemovePageSafe(size_in_bytes);
+ if (page != nullptr) {
+ AddPage(page);
+ if (RefillLinearAllocationAreaFromFreeList(
+ static_cast<size_t>(size_in_bytes), origin))
+ return true;
+ }
+ }
+
+ if (heap()->ShouldExpandOldGenerationOnSlowAllocation() &&
+ heap()->CanExpandOldGeneration(AreaSize())) {
+ Page* page = Expand();
+ if (page) {
+ if (!is_compaction_space()) {
+ heap()->NotifyOldGenerationExpansion(identity(), page);
+ }
+ DCHECK((CountTotalPages() > 1) ||
+ (static_cast<size_t>(size_in_bytes) <= free_list_->Available()));
+ return RefillLinearAllocationAreaFromFreeList(
+ static_cast<size_t>(size_in_bytes), origin);
+ }
+ }
+
+ if (is_compaction_space()) {
+ return SweepAndRetryAllocation(0, 0, size_in_bytes, origin);
+
+ } else {
+ // If sweeper threads are active, wait for them at that point and steal
+ // elements from their free-lists. Allocation may still fail here which
+ // would indicate that there is not enough memory for the given allocation.
+ return EnsureSweptAndRetryAllocation(size_in_bytes, origin);
+ }
+}
+
+bool PagedSpace::SweepAndRetryAllocation(int required_freed_bytes,
+ int max_pages, int size_in_bytes,
+ AllocationOrigin origin) {
+ // Cleanup invalidated old-to-new refs for compaction space in the
+ // final atomic pause.
+ Sweeper::FreeSpaceMayContainInvalidatedSlots invalidated_slots_in_free_space =
+ is_compaction_space() ? Sweeper::FreeSpaceMayContainInvalidatedSlots::kYes
+ : Sweeper::FreeSpaceMayContainInvalidatedSlots::kNo;
+
+ MarkCompactCollector* collector = heap()->mark_compact_collector();
+ if (collector->sweeping_in_progress()) {
+ int max_freed = collector->sweeper()->ParallelSweepSpace(
+ identity(), required_freed_bytes, max_pages,
+ invalidated_slots_in_free_space);
+ RefillFreeList();
+ if (max_freed >= size_in_bytes)
+ return RefillLinearAllocationAreaFromFreeList(size_in_bytes, origin);
+ }
+ return false;
+}
+
+// -----------------------------------------------------------------------------
+// MapSpace implementation
+
+// TODO(dmercadier): use a heap instead of sorting like that.
+// Using a heap will have multiple benefits:
+// - for now, SortFreeList is only called after sweeping, which is somewhat
+// late. Using a heap, sorting could be done online: FreeListCategories would
+// be inserted in a heap (ie, in a sorted manner).
+// - SortFreeList is a bit fragile: any change to FreeListMap (or to
+// MapSpace::free_list_) could break it.
+void MapSpace::SortFreeList() {
+ using LiveBytesPagePair = std::pair<size_t, Page*>;
+ std::vector<LiveBytesPagePair> pages;
+ pages.reserve(CountTotalPages());
+
+ for (Page* p : *this) {
+ free_list()->RemoveCategory(p->free_list_category(kFirstCategory));
+ pages.push_back(std::make_pair(p->allocated_bytes(), p));
+ }
+
+ // Sorting by least-allocated-bytes first.
+ std::sort(pages.begin(), pages.end(),
+ [](const LiveBytesPagePair& a, const LiveBytesPagePair& b) {
+ return a.first < b.first;
+ });
+
+ for (LiveBytesPagePair const& p : pages) {
+ // Since AddCategory inserts in head position, it reverts the order produced
+ // by the sort above: least-allocated-bytes will be Added first, and will
+ // therefore be the last element (and the first one will be
+ // most-allocated-bytes).
+ free_list()->AddCategory(p.second->free_list_category(kFirstCategory));
+ }
+}
+
+#ifdef VERIFY_HEAP
+void MapSpace::VerifyObject(HeapObject object) { CHECK(object.IsMap()); }
+#endif
+
+} // namespace internal
+} // namespace v8