Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

121
deps/v8/test/cctest/heap/heap-tester.h vendored Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2015 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.
#ifndef HEAP_HEAP_TESTER_H_
#define HEAP_HEAP_TESTER_H_
#include "src/heap/spaces.h"
#include "src/objects/fixed-array.h"
// Tests that should have access to private methods of {v8::internal::Heap}.
// Those tests need to be defined using HEAP_TEST(Name) { ... }.
#define HEAP_TEST_METHODS(V) \
V(CodeLargeObjectSpace) \
V(CodeLargeObjectSpace64k) \
V(CompactionFullAbortedPage) \
V(CompactionPartiallyAbortedPage) \
V(CompactionPartiallyAbortedPageIntraAbortedPointers) \
V(CompactionPartiallyAbortedPageWithInvalidatedSlots) \
V(CompactionPartiallyAbortedPageWithRememberedSetEntries) \
V(CompactionSpaceDivideMultiplePages) \
V(CompactionSpaceDivideSinglePage) \
V(InvalidatedSlotsAfterTrimming) \
V(InvalidatedSlotsAllInvalidatedRanges) \
V(InvalidatedSlotsCleanupEachObject) \
V(InvalidatedSlotsCleanupFull) \
V(InvalidatedSlotsCleanupRightTrim) \
V(InvalidatedSlotsCleanupOverlapRight) \
V(InvalidatedSlotsEvacuationCandidate) \
V(InvalidatedSlotsNoInvalidatedRanges) \
V(InvalidatedSlotsResetObjectRegression) \
V(InvalidatedSlotsRightTrimFixedArray) \
V(InvalidatedSlotsRightTrimLargeFixedArray) \
V(InvalidatedSlotsFastToSlow) \
V(InvalidatedSlotsSomeInvalidatedRanges) \
V(TestNewSpaceRefsInCopiedCode) \
V(GCFlags) \
V(MarkCompactCollector) \
V(MarkCompactEpochCounter) \
V(MemoryReducerActivationForSmallHeaps) \
V(NoPromotion) \
V(NumberStringCacheSize) \
V(ObjectGroups) \
V(Promotion) \
V(Regression39128) \
V(ResetWeakHandle) \
V(StressHandles) \
V(TestMemoryReducerSampleJsCalls) \
V(TestSizeOfObjects) \
V(Regress10560) \
V(Regress538257) \
V(Regress587004) \
V(Regress589413) \
V(Regress658718) \
V(Regress670675) \
V(Regress777177) \
V(Regress779503) \
V(Regress791582) \
V(Regress845060) \
V(RegressMissingWriteBarrierInAllocate) \
V(WriteBarrier_Marking) \
V(WriteBarrier_MarkingExtension) \
V(WriteBarriersInCopyJSObject) \
V(DoNotEvacuatePinnedPages) \
V(ObjectStartBitmap)
#define HEAP_TEST(Name) \
CcTest register_test_##Name(v8::internal::heap::HeapTester::Test##Name, \
__FILE__, #Name, true, true); \
void v8::internal::heap::HeapTester::Test##Name()
#define UNINITIALIZED_HEAP_TEST(Name) \
CcTest register_test_##Name(v8::internal::heap::HeapTester::Test##Name, \
__FILE__, #Name, true, false); \
void v8::internal::heap::HeapTester::Test##Name()
#define THREADED_HEAP_TEST(Name) \
RegisterThreadedTest register_##Name( \
v8::internal::heap::HeapTester::Test##Name, #Name); \
/* */ HEAP_TEST(Name)
namespace v8 {
namespace internal {
namespace heap {
class HeapTester {
public:
#define DECLARE_STATIC(Name) static void Test##Name();
HEAP_TEST_METHODS(DECLARE_STATIC)
#undef HEAP_TEST_METHODS
// test-alloc.cc
static AllocationResult AllocateAfterFailures();
static DirectHandle<Object> TestAllocateAfterFailures();
// test-invalidated-slots.cc
static PageMetadata* AllocateByteArraysOnPage(
Heap* heap, std::vector<ByteArray>* byte_arrays);
// test-api.cc
static void ResetWeakHandle(bool global_gc);
// test-heap.cc
static AllocationResult AllocateByteArrayForTest(Heap* heap, int length,
AllocationType allocation);
static bool CodeEnsureLinearAllocationArea(Heap* heap, int size_in_bytes);
// test-mark-compact.cc
static AllocationResult AllocateMapForTest(v8::internal::Isolate* isolate);
static AllocationResult AllocateFixedArrayForTest(Heap* heap, int length,
AllocationType allocation);
static void UncommitUnusedMemory(Heap* heap);
};
} // namespace heap
} // namespace internal
} // namespace v8
#endif // HEAP_HEAP_TESTER_H_

458
deps/v8/test/cctest/heap/heap-utils.cc vendored Normal file
View File

@ -0,0 +1,458 @@
// Copyright 2016 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 "test/cctest/heap/heap-utils.h"
#include "src/base/platform/mutex.h"
#include "src/common/assert-scope.h"
#include "src/common/globals.h"
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/heap/free-list.h"
#include "src/heap/gc-tracer-inl.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/mark-compact.h"
#include "src/heap/marking-barrier.h"
#include "src/heap/mutable-page-metadata.h"
#include "src/heap/page-metadata-inl.h"
#include "src/heap/safepoint.h"
#include "src/heap/spaces.h"
#include "src/objects/free-space-inl.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace heap {
void SealCurrentObjects(Heap* heap) {
// If you see this check failing, disable the flag at the start of your test:
// v8_flags.stress_concurrent_allocation = false;
// Background thread allocating concurrently interferes with this function.
CHECK(!v8_flags.stress_concurrent_allocation);
heap::InvokeMajorGC(heap);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(Heap::SweepingForcedFinalizationMode::kV8Only);
heap->FreeMainThreadLinearAllocationAreas();
for (PageMetadata* page : *heap->old_space()) {
page->MarkNeverAllocateForTesting();
}
}
int FixedArrayLenFromSize(int size) {
return std::min({(size - OFFSET_OF_DATA_START(FixedArray)) / kTaggedSize,
FixedArray::kMaxRegularLength});
}
void FillOldSpacePageWithFixedArrays(
Heap* heap, int remainder, DirectHandleVector<FixedArray>* out_handles) {
PauseAllocationObserversScope pause_observers(heap);
Isolate* isolate = heap->isolate();
const int kArraySize = 128;
const int kArrayLen = heap::FixedArrayLenFromSize(kArraySize);
int allocated = 0;
bool empty = true;
do {
DirectHandle<FixedArray> array;
if (allocated + kArraySize * 2 >
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage())) {
int size =
kArraySize * 2 -
((allocated + kArraySize * 2) -
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage())) -
remainder;
int last_array_len = heap::FixedArrayLenFromSize(size);
array = isolate->factory()->NewFixedArray(last_array_len,
AllocationType::kOld);
CHECK_EQ(size, array->Size());
allocated += array->Size() + remainder;
} else {
array =
isolate->factory()->NewFixedArray(kArrayLen, AllocationType::kOld);
allocated += array->Size();
CHECK_EQ(kArraySize, array->Size());
}
if (empty) {
// Check that allocations started on a new page.
CHECK_EQ(array->address(),
PageMetadata::FromHeapObject(*array)->area_start());
empty = false;
}
if (out_handles) out_handles->push_back(array);
} while (allocated <
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage()));
heap->FreeMainThreadLinearAllocationAreas();
}
void CreatePadding(Heap* heap, int padding_size, AllocationType allocation,
DirectHandleVector<FixedArray>* out_handles,
int object_size) {
Isolate* isolate = heap->isolate();
int allocate_memory;
int length;
int free_memory = padding_size;
heap->FreeMainThreadLinearAllocationAreas();
if (allocation == i::AllocationType::kOld) {
int overall_free_memory = static_cast<int>(heap->old_space()->Available());
CHECK(padding_size <= overall_free_memory || overall_free_memory == 0);
} else {
int overall_free_memory = static_cast<int>(heap->new_space()->Available());
CHECK(padding_size <= overall_free_memory || overall_free_memory == 0);
}
while (free_memory > 0) {
if (free_memory > object_size) {
allocate_memory = object_size;
length = FixedArrayLenFromSize(allocate_memory);
} else {
allocate_memory = free_memory;
length = FixedArrayLenFromSize(allocate_memory);
if (length <= 0) {
// Not enough room to create another FixedArray, so create a filler.
if (allocation == i::AllocationType::kOld) {
heap->CreateFillerObjectAt(*heap->OldSpaceAllocationTopAddress(),
free_memory);
} else {
heap->CreateFillerObjectAt(*heap->NewSpaceAllocationTopAddress(),
free_memory);
}
break;
}
}
auto array = isolate->factory()->NewFixedArray(length, allocation);
if (out_handles) out_handles->push_back(array);
CHECK((allocation == AllocationType::kYoung &&
heap->new_space()->Contains(*array)) ||
(allocation == AllocationType::kOld && heap->InOldSpace(*array)) ||
v8_flags.single_generation);
free_memory -= array->Size();
}
heap->FreeMainThreadLinearAllocationAreas();
}
namespace {
void FillPageInPagedSpace(PageMetadata* page,
DirectHandleVector<FixedArray>* out_handles) {
Heap* heap = page->heap();
Isolate* isolate = heap->isolate();
DCHECK(page->SweepingDone());
SafepointScope safepoint_scope(isolate,
kGlobalSafepointForSharedSpaceIsolate);
PagedSpaceBase* paged_space = static_cast<PagedSpaceBase*>(page->owner());
heap->FreeLinearAllocationAreas();
PauseAllocationObserversScope no_observers_scope(heap);
CollectionEpoch full_epoch =
heap->tracer()->CurrentEpoch(GCTracer::Scope::ScopeId::MARK_COMPACTOR);
CollectionEpoch young_epoch = heap->tracer()->CurrentEpoch(
GCTracer::Scope::ScopeId::MINOR_MARK_SWEEPER);
for (PageMetadata* p : *paged_space) {
if (p != page) paged_space->UnlinkFreeListCategories(p);
}
// If min_block_size is larger than OFFSET_OF_DATA_START(FixedArray), all
// blocks in the free list can be used to allocate a fixed array. This
// guarantees that we can fill the whole page.
DCHECK_LT(OFFSET_OF_DATA_START(FixedArray),
paged_space->free_list()->min_block_size());
std::vector<int> available_sizes;
// Collect all free list block sizes
page->ForAllFreeListCategories(
[&available_sizes](FreeListCategory* category) {
category->IterateNodesForTesting(
[&available_sizes](Tagged<FreeSpace> node) {
int node_size = node->Size();
if (node_size >= kMaxRegularHeapObjectSize) {
available_sizes.push_back(node_size);
}
});
});
// Allocate as many max size arrays as possible, while making sure not to
// leave behind a block too small to fit a FixedArray.
const int max_array_length = FixedArrayLenFromSize(kMaxRegularHeapObjectSize);
for (size_t i = 0; i < available_sizes.size(); ++i) {
int available_size = available_sizes[i];
while (available_size > kMaxRegularHeapObjectSize) {
DirectHandle<FixedArray> fixed_array = isolate->factory()->NewFixedArray(
max_array_length, AllocationType::kYoung);
if (out_handles) out_handles->push_back(fixed_array);
available_size -= kMaxRegularHeapObjectSize;
}
}
heap->FreeLinearAllocationAreas();
// Allocate FixedArrays in remaining free list blocks, from largest
// category to smallest.
std::vector<std::vector<int>> remaining_sizes;
page->ForAllFreeListCategories(
[&remaining_sizes](FreeListCategory* category) {
remaining_sizes.push_back({});
std::vector<int>& sizes_in_category =
remaining_sizes[remaining_sizes.size() - 1];
category->IterateNodesForTesting(
[&sizes_in_category](Tagged<FreeSpace> node) {
int node_size = node->Size();
DCHECK_LT(0, FixedArrayLenFromSize(node_size));
sizes_in_category.push_back(node_size);
});
});
for (auto it = remaining_sizes.rbegin(); it != remaining_sizes.rend(); ++it) {
std::vector<int> sizes_in_category = *it;
for (int size : sizes_in_category) {
DCHECK_LE(size, kMaxRegularHeapObjectSize);
int array_length = FixedArrayLenFromSize(size);
DCHECK_LT(0, array_length);
DirectHandle<FixedArray> fixed_array = isolate->factory()->NewFixedArray(
array_length, AllocationType::kYoung);
if (out_handles) out_handles->push_back(fixed_array);
}
}
DCHECK_EQ(0, page->AvailableInFreeList());
DCHECK_EQ(0, page->AvailableInFreeListFromAllocatedBytes());
for (PageMetadata* p : *paged_space) {
if (p != page) paged_space->RelinkFreeListCategories(p);
}
// Allocations in this method should not require a GC.
CHECK_EQ(full_epoch, heap->tracer()->CurrentEpoch(
GCTracer::Scope::ScopeId::MARK_COMPACTOR));
CHECK_EQ(young_epoch, heap->tracer()->CurrentEpoch(
GCTracer::Scope::ScopeId::MINOR_MARK_SWEEPER));
heap->FreeLinearAllocationAreas();
}
} // namespace
void FillCurrentPage(v8::internal::NewSpace* space,
DirectHandleVector<FixedArray>* out_handles) {
if (v8_flags.minor_ms) {
const Address top = space->heap()->NewSpaceTop();
space->heap()->FreeMainThreadLinearAllocationAreas();
PauseAllocationObserversScope pause_observers(space->heap());
if (top == kNullAddress) return;
PageMetadata* page = PageMetadata::FromAllocationAreaAddress(top);
space->heap()->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
FillPageInPagedSpace(page, out_handles);
space->heap()->FreeMainThreadLinearAllocationAreas();
} else {
FillCurrentPageButNBytes(SemiSpaceNewSpace::From(space), 0, out_handles);
}
}
void FillCurrentPageButNBytes(v8::internal::SemiSpaceNewSpace* space,
int extra_bytes,
DirectHandleVector<FixedArray>* out_handles) {
space->heap()->FreeMainThreadLinearAllocationAreas();
PauseAllocationObserversScope pause_observers(space->heap());
// We cannot rely on `space->limit()` to point to the end of the current page
// in the case where inline allocations are disabled, it actually points to
// the current allocation pointer.
DCHECK_IMPLIES(
!space->heap()->IsInlineAllocationEnabled(),
space->heap()->NewSpaceTop() == space->heap()->NewSpaceLimit());
int space_remaining = space->GetSpaceRemainingOnCurrentPageForTesting();
CHECK(space_remaining >= extra_bytes);
int new_linear_size = space_remaining - extra_bytes;
if (new_linear_size == 0) return;
heap::CreatePadding(space->heap(), space_remaining, i::AllocationType::kYoung,
out_handles);
space->heap()->FreeMainThreadLinearAllocationAreas();
}
void SimulateIncrementalMarking(i::Heap* heap, bool force_completion) {
static constexpr auto kStepSize = v8::base::TimeDelta::FromMilliseconds(100);
CHECK(v8_flags.incremental_marking);
i::IncrementalMarking* marking = heap->incremental_marking();
if (heap->sweeping_in_progress()) {
IsolateSafepointScope scope(heap);
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
}
if (marking->IsMinorMarking()) {
// If minor incremental marking is running, we need to finalize it first
// because of the AdvanceForTesting call in this function which is currently
// only possible for MajorMC.
heap->CollectGarbage(NEW_SPACE,
GarbageCollectionReason::kFinalizeConcurrentMinorMS);
}
if (marking->IsStopped()) {
heap->StartIncrementalMarking(i::GCFlag::kNoFlags,
i::GarbageCollectionReason::kTesting);
}
CHECK(marking->IsMarking());
if (!force_completion) return;
IsolateSafepointScope scope(heap);
MarkingBarrier::PublishAll(heap);
marking->MarkRootsForTesting();
while (!marking->IsMajorMarkingComplete()) {
marking->AdvanceForTesting(kStepSize);
}
}
void SimulateFullSpace(v8::internal::PagedSpace* space) {
Heap* heap = space->heap();
IsolateSafepointScope safepoint_scope(heap);
heap->FreeLinearAllocationAreas();
// If you see this check failing, disable the flag at the start of your test:
// v8_flags.stress_concurrent_allocation = false;
// Background thread allocating concurrently interferes with this function.
CHECK(!v8_flags.stress_concurrent_allocation);
if (space->heap()->sweeping_in_progress()) {
space->heap()->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
}
space->ResetFreeList();
}
void AbandonCurrentlyFreeMemory(PagedSpace* space) {
Heap* heap = space->heap();
IsolateSafepointScope safepoint_scope(heap);
heap->FreeLinearAllocationAreas();
for (PageMetadata* page : *space) {
page->MarkNeverAllocateForTesting();
}
}
void InvokeMajorGC(Heap* heap) {
heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
}
void InvokeMajorGC(Heap* heap, GCFlag gc_flag) {
heap->CollectAllGarbage(gc_flag, GarbageCollectionReason::kTesting);
}
void InvokeMinorGC(Heap* heap) {
heap->CollectGarbage(NEW_SPACE, GarbageCollectionReason::kTesting);
}
void InvokeAtomicMajorGC(Heap* heap) {
heap->PreciseCollectAllGarbage(GCFlag::kNoFlags,
GarbageCollectionReason::kTesting);
if (heap->sweeping_in_progress()) {
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kUnifiedHeap);
}
}
void InvokeAtomicMinorGC(Heap* heap) {
InvokeMinorGC(heap);
if (heap->sweeping_in_progress()) {
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kUnifiedHeap);
}
}
void InvokeMemoryReducingMajorGCs(Heap* heap) {
heap->CollectAllAvailableGarbage(GarbageCollectionReason::kTesting);
}
void CollectSharedGarbage(Heap* heap) {
heap->CollectGarbageShared(heap->main_thread_local_heap(),
GarbageCollectionReason::kTesting);
}
void EmptyNewSpaceUsingGC(Heap* heap) { InvokeMajorGC(heap); }
void ForceEvacuationCandidate(PageMetadata* page) {
Isolate* isolate = page->owner()->heap()->isolate();
SafepointScope safepoint(isolate, kGlobalSafepointForSharedSpaceIsolate);
CHECK(v8_flags.manual_evacuation_candidates_selection);
page->Chunk()->SetFlagNonExecutable(
MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING);
page->owner()->heap()->FreeLinearAllocationAreas();
}
bool InCorrectGeneration(Tagged<HeapObject> object) {
return v8_flags.single_generation ? !i::HeapLayout::InYoungGeneration(object)
: i::HeapLayout::InYoungGeneration(object);
}
void GrowNewSpace(Heap* heap) {
IsolateSafepointScope scope(heap);
heap->ExpandNewSpaceSizeForTesting();
CHECK(heap->new_space()->EnsureCurrentCapacity());
}
void GrowNewSpaceToMaximumCapacity(Heap* heap) {
IsolateSafepointScope scope(heap);
NewSpace* new_space = heap->new_space();
while (new_space->TotalCapacity() < new_space->MaximumCapacity()) {
heap->ExpandNewSpaceSizeForTesting();
}
CHECK(new_space->EnsureCurrentCapacity());
}
} // namespace heap
ManualGCScope::ManualGCScope(Isolate* isolate)
: isolate_(isolate),
flag_concurrent_marking_(v8_flags.concurrent_marking),
flag_concurrent_sweeping_(v8_flags.concurrent_sweeping),
flag_concurrent_minor_ms_marking_(v8_flags.concurrent_minor_ms_marking),
flag_stress_concurrent_allocation_(v8_flags.stress_concurrent_allocation),
flag_stress_incremental_marking_(v8_flags.stress_incremental_marking),
flag_parallel_marking_(v8_flags.parallel_marking),
flag_detect_ineffective_gcs_near_heap_limit_(
v8_flags.detect_ineffective_gcs_near_heap_limit),
flag_cppheap_concurrent_marking_(v8_flags.cppheap_concurrent_marking) {
// Some tests run threaded (back-to-back) and thus the GC may already be
// running by the time a ManualGCScope is created. Finalizing existing marking
// prevents any undefined/unexpected behavior.
if (isolate) {
auto* heap = isolate->heap();
if (heap->incremental_marking()->IsMarking()) {
heap::InvokeAtomicMajorGC(heap);
}
}
v8_flags.concurrent_marking = false;
v8_flags.concurrent_sweeping = false;
v8_flags.concurrent_minor_ms_marking = false;
v8_flags.stress_incremental_marking = false;
v8_flags.stress_concurrent_allocation = false;
// Parallel marking has a dependency on concurrent marking.
v8_flags.parallel_marking = false;
v8_flags.detect_ineffective_gcs_near_heap_limit = false;
// CppHeap concurrent marking has a dependency on concurrent marking.
v8_flags.cppheap_concurrent_marking = false;
if (isolate_ && isolate_->heap()->cpp_heap()) {
CppHeap::From(isolate_->heap()->cpp_heap())
->UpdateGCCapabilitiesFromFlagsForTesting();
}
}
ManualGCScope::~ManualGCScope() {
v8_flags.concurrent_marking = flag_concurrent_marking_;
v8_flags.concurrent_sweeping = flag_concurrent_sweeping_;
v8_flags.concurrent_minor_ms_marking = flag_concurrent_minor_ms_marking_;
v8_flags.stress_concurrent_allocation = flag_stress_concurrent_allocation_;
v8_flags.stress_incremental_marking = flag_stress_incremental_marking_;
v8_flags.parallel_marking = flag_parallel_marking_;
v8_flags.detect_ineffective_gcs_near_heap_limit =
flag_detect_ineffective_gcs_near_heap_limit_;
v8_flags.cppheap_concurrent_marking = flag_cppheap_concurrent_marking_;
if (isolate_ && isolate_->heap()->cpp_heap()) {
CppHeap::From(isolate_->heap()->cpp_heap())
->UpdateGCCapabilitiesFromFlagsForTesting();
}
}
} // namespace internal
} // namespace v8

126
deps/v8/test/cctest/heap/heap-utils.h vendored Normal file
View File

@ -0,0 +1,126 @@
// Copyright 2016 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.
#ifndef HEAP_HEAP_UTILS_H_
#define HEAP_HEAP_UTILS_H_
#include "src/api/api-inl.h"
#include "src/flags/flags.h"
#include "src/heap/heap.h"
#include "test/cctest/cctest.h"
namespace v8::internal {
namespace heap {
void SealCurrentObjects(Heap* heap);
int FixedArrayLenFromSize(int size);
// Fill a page with fixed arrays leaving remainder behind. The function does
// not create additional fillers and assumes that the space has just been
// sealed. If out_handles is not null, it appends the fixed arrays to the
// pointed vector.
void FillOldSpacePageWithFixedArrays(
Heap* heap, int remainder,
DirectHandleVector<FixedArray>* out_handles = nullptr);
void CreatePadding(Heap* heap, int padding_size, AllocationType allocation,
DirectHandleVector<FixedArray>* out_handles = nullptr,
int object_size = kMaxRegularHeapObjectSize);
void FillCurrentPage(v8::internal::NewSpace* space,
DirectHandleVector<FixedArray>* out_handles = nullptr);
void FillCurrentPageButNBytes(
v8::internal::SemiSpaceNewSpace* space, int extra_bytes,
DirectHandleVector<FixedArray>* out_handles = nullptr);
// Helper function that simulates many incremental marking steps until
// marking is completed.
void SimulateIncrementalMarking(i::Heap* heap, bool force_completion = true);
// Helper function that simulates a full old-space in the heap.
void SimulateFullSpace(v8::internal::PagedSpace* space);
void AbandonCurrentlyFreeMemory(PagedSpace* space);
void InvokeMajorGC(Heap* heap);
void InvokeMajorGC(Heap* heap, GCFlag gc_flag);
void InvokeMinorGC(Heap* heap);
void InvokeAtomicMajorGC(Heap* heap);
void InvokeAtomicMinorGC(Heap* heap);
void InvokeMemoryReducingMajorGCs(Heap* heap);
void CollectSharedGarbage(Heap* heap);
void EmptyNewSpaceUsingGC(Heap* heap);
void ForceEvacuationCandidate(PageMetadata* page);
void GrowNewSpace(Heap* heap);
void GrowNewSpaceToMaximumCapacity(Heap* heap);
template <typename GlobalOrPersistent>
bool InYoungGeneration(v8::Isolate* isolate, const GlobalOrPersistent& global) {
v8::HandleScope scope(isolate);
auto tmp = global.Get(isolate);
return i::HeapLayout::InYoungGeneration(*v8::Utils::OpenDirectHandle(*tmp));
}
bool InCorrectGeneration(Tagged<HeapObject> object);
template <typename GlobalOrPersistent>
bool InCorrectGeneration(v8::Isolate* isolate,
const GlobalOrPersistent& global) {
v8::HandleScope scope(isolate);
auto tmp = global.Get(isolate);
return InCorrectGeneration(*v8::Utils::OpenDirectHandle(*tmp));
}
class ManualEvacuationCandidatesSelectionScope {
public:
// Marking a page as an evacuation candidate update the page flags which may
// race with reading the page flag during concurrent marking.
explicit ManualEvacuationCandidatesSelectionScope(ManualGCScope&) {
DCHECK(!v8_flags.manual_evacuation_candidates_selection);
v8_flags.manual_evacuation_candidates_selection = true;
}
~ManualEvacuationCandidatesSelectionScope() {
DCHECK(v8_flags.manual_evacuation_candidates_selection);
v8_flags.manual_evacuation_candidates_selection = false;
}
private:
};
} // namespace heap
// ManualGCScope allows for disabling GC heuristics. This is useful for tests
// that want to check specific corner cases around GC.
//
// The scope will finalize any ongoing GC on the provided Isolate. If no Isolate
// is manually provided, it is assumed that a CcTest setup (e.g.
// CcTest::InitializeVM()) is used.
class V8_NODISCARD ManualGCScope final {
public:
explicit ManualGCScope(
Isolate* isolate = reinterpret_cast<Isolate*>(CcTest::isolate_));
~ManualGCScope();
private:
Isolate* const isolate_;
const bool flag_concurrent_marking_;
const bool flag_concurrent_sweeping_;
const bool flag_concurrent_minor_ms_marking_;
const bool flag_stress_concurrent_allocation_;
const bool flag_stress_incremental_marking_;
const bool flag_parallel_marking_;
const bool flag_detect_ineffective_gcs_near_heap_limit_;
const bool flag_cppheap_concurrent_marking_;
};
} // namespace v8::internal
#endif // HEAP_HEAP_UTILS_H_

181
deps/v8/test/cctest/heap/test-alloc.cc vendored Normal file
View File

@ -0,0 +1,181 @@
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/builtins/accessors.h"
#include "src/heap/heap-inl.h"
#include "src/init/v8.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
DirectHandle<Object> HeapTester::TestAllocateAfterFailures() {
// Similar to what the factory's retrying logic does in the last-resort case,
// we wrap the allocator function in an AlwaysAllocateScope. Test that
// all allocations succeed immediately without any retry.
Heap* heap = CcTest::heap();
heap::InvokeMemoryReducingMajorGCs(heap);
AlwaysAllocateScopeForTesting scope(heap);
int size = FixedArray::SizeFor(100);
// Young generation.
Tagged<HeapObject> obj =
heap->AllocateRaw(size, AllocationType::kYoung).ToObjectChecked();
// In order to pass heap verification on Isolate teardown, mark the
// allocated area as a filler.
heap->CreateFillerObjectAt(obj.address(), size);
// Old generation.
heap::SimulateFullSpace(heap->old_space());
obj = heap->AllocateRaw(size, AllocationType::kOld).ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), size);
// Large object space.
static const size_t kLargeObjectSpaceFillerLength =
3 * (PageMetadata::kPageSize / 10);
static const size_t kLargeObjectSpaceFillerSize =
FixedArray::SizeFor(kLargeObjectSpaceFillerLength);
CHECK_GT(kLargeObjectSpaceFillerSize,
static_cast<size_t>(heap->old_space()->AreaSize()));
while (heap->OldGenerationSpaceAvailable() > kLargeObjectSpaceFillerSize) {
obj = heap->AllocateRaw(kLargeObjectSpaceFillerSize, AllocationType::kOld)
.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), size);
}
obj = heap->AllocateRaw(kLargeObjectSpaceFillerSize, AllocationType::kOld)
.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), size);
// Map space.
heap::SimulateFullSpace(heap->old_space());
obj = heap->AllocateRaw(Map::kSize, AllocationType::kMap).ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), Map::kSize);
// Code space.
heap::SimulateFullSpace(heap->code_space());
size = CcTest::i_isolate()->builtins()->code(Builtin::kIllegal)->Size();
obj =
heap->AllocateRaw(size, AllocationType::kCode, AllocationOrigin::kRuntime)
.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), size);
return CcTest::i_isolate()->factory()->true_value();
}
HEAP_TEST(StressHandles) {
// For TestAllocateAfterFailures.
v8_flags.stress_concurrent_allocation = false;
v8::HandleScope scope(CcTest::isolate());
v8::Local<v8::Context> env = v8::Context::New(CcTest::isolate());
env->Enter();
DirectHandle<Object> o = TestAllocateAfterFailures();
CHECK(IsTrue(*o, CcTest::i_isolate()));
env->Exit();
}
void TestGetter(
v8::Local<v8::Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(info.GetIsolate());
HandleScope scope(isolate);
info.GetReturnValue().Set(
v8::Utils::ToLocal(HeapTester::TestAllocateAfterFailures()));
}
void TestSetter(v8::Local<v8::Name> name, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<v8::Boolean>& info) {
UNREACHABLE();
}
DirectHandle<AccessorInfo> TestAccessorInfo(Isolate* isolate,
PropertyAttributes attributes) {
DirectHandle<String> name =
isolate->factory()->NewStringFromStaticChars("get");
return Accessors::MakeAccessor(isolate, name, &TestGetter, &TestSetter);
}
TEST(StressJS) {
// For TestAllocateAfterFailures in TestGetter.
v8_flags.stress_concurrent_allocation = false;
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
v8::HandleScope scope(CcTest::isolate());
v8::Local<v8::Context> env = v8::Context::New(CcTest::isolate());
env->Enter();
DirectHandle<NativeContext> context(isolate->native_context());
DirectHandle<SharedFunctionInfo> info =
factory->NewSharedFunctionInfoForBuiltin(
factory->function_string(), Builtin::kEmptyFunction, 0, kDontAdapt);
info->set_language_mode(LanguageMode::kStrict);
DirectHandle<JSFunction> function =
Factory::JSFunctionBuilder{isolate, info, context}.Build();
CHECK(!function->shared()->construct_as_builtin());
// Force the creation of an initial map.
factory->NewJSObject(function);
// Patch the map to have an accessor for "get".
DirectHandle<Map> map(function->initial_map(), isolate);
DirectHandle<DescriptorArray> instance_descriptors(
map->instance_descriptors(isolate), isolate);
CHECK_EQ(0, instance_descriptors->number_of_descriptors());
PropertyAttributes attrs = NONE;
DirectHandle<AccessorInfo> foreign = TestAccessorInfo(isolate, attrs);
Map::EnsureDescriptorSlack(isolate, map, 1);
Descriptor d = Descriptor::AccessorConstant(
DirectHandle<Name>(Cast<Name>(foreign->name()), isolate), foreign, attrs);
map->AppendDescriptor(isolate, &d);
// Add the Foo constructor the global object.
CHECK(env->Global()
->Set(env, v8::String::NewFromUtf8Literal(CcTest::isolate(), "Foo"),
v8::Utils::CallableToLocal(function))
.FromJust());
// Call the accessor through JavaScript.
v8::Local<v8::Value> result =
v8::Script::Compile(env, v8::String::NewFromUtf8Literal(CcTest::isolate(),
"(new Foo).get"))
.ToLocalChecked()
->Run(env)
.ToLocalChecked();
CHECK_EQ(true, result->BooleanValue(CcTest::isolate()));
env->Exit();
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,514 @@
// Copyright 2016 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/api/api-inl.h"
#include "src/common/globals.h"
#include "src/execution/isolate.h"
#include "src/flags/flags.h"
#include "src/heap/array-buffer-sweeper.h"
#include "src/heap/heap-inl.h"
#include "src/heap/spaces.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
namespace {
bool IsTrackedYoung(i::Heap* heap, i::ArrayBufferExtension* extension) {
bool in_young = heap->array_buffer_sweeper()->young().ContainsSlow(extension);
bool in_old = heap->array_buffer_sweeper()->old().ContainsSlow(extension);
CHECK(!(in_young && in_old));
return in_young;
}
bool IsTrackedOld(i::Heap* heap, i::ArrayBufferExtension* extension) {
bool in_young = heap->array_buffer_sweeper()->young().ContainsSlow(extension);
bool in_old = heap->array_buffer_sweeper()->old().ContainsSlow(extension);
CHECK(!(in_young && in_old));
return in_old;
}
bool IsTracked(i::Heap* heap, i::ArrayBufferExtension* extension) {
bool in_young = heap->array_buffer_sweeper()->young().ContainsSlow(extension);
bool in_old = heap->array_buffer_sweeper()->old().ContainsSlow(extension);
CHECK(!(in_young && in_old));
return in_young || in_old;
}
bool IsTracked(i::Heap* heap, i::Tagged<i::JSArrayBuffer> buffer) {
return IsTracked(heap, buffer->extension());
}
} // namespace
namespace v8 {
namespace internal {
namespace heap {
// The following tests make sure that JSArrayBuffer tracking works expected when
// moving the objects through various spaces during GC phases.
TEST(ArrayBuffer_OnlyMC) {
v8_flags.concurrent_array_buffer_sweeping = false;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
CcTest::heap());
ArrayBufferExtension* extension;
{
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
extension = buf->extension();
CHECK(v8_flags.single_generation ? IsTrackedOld(heap, extension)
: IsTrackedYoung(heap, extension));
heap::InvokeAtomicMajorGC(heap);
CHECK(IsTrackedOld(heap, extension));
heap::InvokeAtomicMajorGC(heap);
CHECK(IsTrackedOld(heap, extension));
}
heap::InvokeAtomicMajorGC(heap);
CHECK(!IsTracked(heap, extension));
}
TEST(ArrayBuffer_OnlyScavenge) {
if (v8_flags.single_generation) return;
v8_flags.concurrent_array_buffer_sweeping = false;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
CcTest::heap());
ArrayBufferExtension* extension;
{
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
extension = buf->extension();
CHECK(IsTrackedYoung(heap, extension));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTrackedYoung(heap, extension));
heap::InvokeAtomicMajorGC(heap);
CHECK(IsTrackedOld(heap, extension));
}
heap::InvokeAtomicMajorGC(heap);
CHECK(!IsTracked(heap, extension));
}
TEST(ArrayBuffer_ScavengeAndMC) {
if (v8_flags.single_generation) return;
v8_flags.concurrent_array_buffer_sweeping = false;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
CcTest::heap());
ArrayBufferExtension* extension;
{
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
extension = buf->extension();
CHECK(IsTrackedYoung(heap, extension));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTrackedYoung(heap, extension));
heap::InvokeAtomicMajorGC(heap);
CHECK(IsTrackedOld(heap, extension));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTrackedOld(heap, extension));
}
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTrackedOld(heap, extension));
heap::InvokeAtomicMajorGC(heap);
CHECK(!IsTracked(heap, extension));
}
TEST(ArrayBuffer_Compaction) {
if (!v8_flags.compact) return;
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
v8_flags.concurrent_array_buffer_sweeping = false;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
heap::AbandonCurrentlyFreeMemory(heap->old_space());
Global<v8::ArrayBuffer> ab1_global;
PageMetadata* page_before_gc;
{
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab1 = v8::ArrayBuffer::New(isolate, 100);
IndirectHandle<JSArrayBuffer> buf1 = v8::Utils::OpenIndirectHandle(*ab1);
CHECK(IsTracked(heap, *buf1));
heap::InvokeAtomicMajorGC(heap);
page_before_gc = PageMetadata::FromHeapObject(*buf1);
heap::ForceEvacuationCandidate(page_before_gc);
CHECK(IsTracked(heap, *buf1));
ab1_global.Reset(isolate, ab1);
}
{
// We need to invoke GC without stack, otherwise no compaction is
// performed.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap::InvokeMajorGC(heap);
}
{
v8::HandleScope scope(isolate);
IndirectHandle<JSArrayBuffer> buf1 =
v8::Utils::OpenHandle(*ab1_global.Get(isolate));
PageMetadata* page_after_gc = PageMetadata::FromHeapObject(*buf1);
CHECK(IsTracked(heap, *buf1));
CHECK_NE(page_before_gc, page_after_gc);
}
}
TEST(ArrayBuffer_UnregisterDuringSweep) {
// Regular pages in old space (without compaction) are processed concurrently
// in the sweeper. If we happen to unregister a buffer (either explicitly, or
// implicitly through e.g. |Detach|) we need to sync with the sweeper
// task.
//
// Note: This test will will only fail on TSAN configurations.
// Disable verify-heap since it forces sweeping to be completed in the
// epilogue of the GC.
#ifdef VERIFY_HEAP
i::v8_flags.verify_heap = false;
#endif // VERIFY_HEAP
ManualGCScope manual_gc_scope;
i::v8_flags.concurrent_array_buffer_sweeping = false;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
{
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
{
v8::HandleScope new_handle_scope(isolate);
// Allocate another buffer on the same page to force processing a
// non-empty set of buffers in the last GC.
Local<v8::ArrayBuffer> ab2 = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf2 = v8::Utils::OpenDirectHandle(*ab2);
CHECK(IsTracked(heap, *buf));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTracked(heap, *buf));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTracked(heap, *buf));
CHECK(IsTracked(heap, *buf2));
}
heap::InvokeMajorGC(heap);
// |Detach| will cause the buffer to be |Unregister|ed. Without
// barriers and proper synchronization this will trigger a data race on
// TSAN.
ab->Detach(v8::Local<v8::Value>()).Check();
}
}
TEST(ArrayBuffer_NonLivePromotion) {
if (!v8_flags.incremental_marking || v8_flags.separate_gc_phases) return;
v8_flags.concurrent_array_buffer_sweeping = false;
ManualGCScope manual_gc_scope;
// The test verifies that the marking state is preserved when promoting
// a buffer to old space.
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
CcTest::heap());
{
v8::HandleScope handle_scope(isolate);
DirectHandle<FixedArray> root =
heap->isolate()->factory()->NewFixedArray(1, AllocationType::kOld);
{
v8::HandleScope new_handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
root->set(0, *buf); // Buffer that should not be promoted as live.
}
heap::SimulateIncrementalMarking(heap, false);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
heap::InvokeAtomicMinorGC(heap);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
ArrayBufferExtension* extension =
Cast<JSArrayBuffer>(root->get(0))->extension();
root->set(0, ReadOnlyRoots(heap).undefined_value());
heap::SimulateIncrementalMarking(heap, true);
heap::InvokeAtomicMajorGC(heap);
CHECK(!IsTracked(heap, extension));
}
}
TEST(ArrayBuffer_LivePromotion) {
if (!v8_flags.incremental_marking || v8_flags.separate_gc_phases) return;
v8_flags.concurrent_array_buffer_sweeping = false;
ManualGCScope manual_gc_scope;
// The test verifies that the marking state is preserved when promoting
// a buffer to old space.
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
Tagged<JSArrayBuffer> raw_ab;
{
v8::HandleScope handle_scope(isolate);
DirectHandle<FixedArray> root =
heap->isolate()->factory()->NewFixedArray(1, AllocationType::kOld);
{
v8::HandleScope new_handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
root->set(0, *buf); // Buffer that should be promoted as live.
}
// Store array in Global such that it is part of the root set when
// starting incremental marking.
v8::Global<Value> global_root(CcTest::isolate(),
Utils::ToLocal(Cast<Object>(root)));
heap::SimulateIncrementalMarking(heap, true);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
heap::InvokeMinorGC(heap);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
heap::InvokeMinorGC(heap);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
raw_ab = Cast<JSArrayBuffer>(root->get(0));
root->set(0, ReadOnlyRoots(heap).undefined_value());
// Prohibit page from being released.
MemoryChunk::FromHeapObject(raw_ab)->MarkNeverEvacuate();
heap::InvokeMajorGC(heap);
CHECK(!heap->array_buffer_sweeper()->sweeping_in_progress());
CHECK(IsTracked(heap, raw_ab));
}
}
TEST(ArrayBuffer_SemiSpaceCopyThenPagePromotion) {
if (!i::v8_flags.incremental_marking) return;
if (v8_flags.minor_ms) return;
v8_flags.concurrent_array_buffer_sweeping = false;
v8_flags.scavenger_precise_object_pinning = false;
ManualGCScope manual_gc_scope;
// The test verifies that the marking state is preserved across semispace
// copy.
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
heap::SealCurrentObjects(heap);
{
v8::HandleScope handle_scope(isolate);
DirectHandle<FixedArray> root =
heap->isolate()->factory()->NewFixedArray(1, AllocationType::kOld);
{
v8::HandleScope new_handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
root->set(0, *buf); // Buffer that should be promoted as live.
MemoryChunk::FromHeapObject(*buf)->MarkNeverEvacuate();
}
DirectHandleVector<FixedArray> handles(isolate);
// Make the whole page transition from new->old, getting the buffers
// processed in the sweeper (relying on marking information) instead of
// processing during newspace evacuation.
heap::FillCurrentPage(heap->new_space(), &handles);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
{
// CSS prevent semi space copying in Scavenger.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap::InvokeAtomicMinorGC(heap);
}
heap::SimulateIncrementalMarking(heap, true);
heap::InvokeAtomicMajorGC(heap);
CHECK(IsTracked(heap, Cast<JSArrayBuffer>(root->get(0))));
}
}
TEST(ArrayBuffer_PagePromotion) {
if (!i::v8_flags.incremental_marking || i::v8_flags.single_generation) return;
i::v8_flags.concurrent_array_buffer_sweeping = false;
ManualGCScope manual_gc_scope;
// The test verifies that the marking state is preserved across semispace
// copy.
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
heap::SealCurrentObjects(heap);
{
v8::HandleScope handle_scope(isolate);
DirectHandle<FixedArray> root =
heap->isolate()->factory()->NewFixedArray(1, AllocationType::kOld);
ArrayBufferExtension* extension;
{
v8::HandleScope new_handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf = v8::Utils::OpenDirectHandle(*ab);
extension = buf->extension();
root->set(0, *buf); // Buffer that should be promoted as live.
}
DirectHandleVector<FixedArray> handles(isolate);
// Create live objects on page such that the whole page gets promoted
heap::FillCurrentPage(heap->new_space(), &handles);
CHECK(IsTrackedYoung(heap, extension));
heap::SimulateIncrementalMarking(heap, true);
heap::InvokeAtomicMajorGC(heap);
CHECK(IsTrackedOld(heap, extension));
}
}
UNINITIALIZED_TEST(ArrayBuffer_SemiSpaceCopyMultipleTasks) {
if (v8_flags.optimize_for_size || v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
// Test allocates JSArrayBuffer on different pages before triggering a
// full GC that performs the semispace copy. If parallelized, this test
// ensures proper synchronization in TSAN configurations.
v8_flags.min_semi_space_size = std::max(2 * PageMetadata::kPageSize / MB, 1);
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Context::New(isolate)->Enter();
Heap* heap = i_isolate->heap();
// Ensure heap is in a clean state.
heap::InvokeMajorGC(heap);
heap::InvokeMajorGC(heap);
Local<v8::ArrayBuffer> ab1 = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf1 = v8::Utils::OpenDirectHandle(*ab1);
heap::FillCurrentPage(heap->new_space());
Local<v8::ArrayBuffer> ab2 = v8::ArrayBuffer::New(isolate, 100);
DirectHandle<JSArrayBuffer> buf2 = v8::Utils::OpenDirectHandle(*ab2);
CHECK_NE(PageMetadata::FromHeapObject(*buf1),
PageMetadata::FromHeapObject(*buf2));
heap::InvokeAtomicMajorGC(heap);
}
isolate->Dispose();
}
TEST(ArrayBuffer_ExternalBackingStoreSizeIncreases) {
if (v8_flags.single_generation) return;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
ExternalBackingStoreType type = ExternalBackingStoreType::kArrayBuffer;
const Space* space = v8_flags.incremental_marking
? static_cast<Space*>(heap->new_space())
: static_cast<Space*>(heap->old_space());
const size_t backing_store_before = space->ExternalBackingStoreBytes(type);
{
const size_t kArraybufferSize = 117;
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, kArraybufferSize);
USE(ab);
const size_t backing_store_after = space->ExternalBackingStoreBytes(type);
CHECK_EQ(kArraybufferSize, backing_store_after - backing_store_before);
}
}
TEST(ArrayBuffer_ExternalBackingStoreSizeDecreases) {
if (v8_flags.single_generation) return;
v8_flags.concurrent_array_buffer_sweeping = false;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
ExternalBackingStoreType type = ExternalBackingStoreType::kArrayBuffer;
const size_t backing_store_before =
heap->new_space()->ExternalBackingStoreBytes(type);
{
const size_t kArraybufferSize = 117;
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, kArraybufferSize);
USE(ab);
}
heap::InvokeAtomicMajorGC(heap);
const size_t backing_store_after =
heap->new_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(0, backing_store_after - backing_store_before);
}
TEST(ArrayBuffer_ExternalBackingStoreSizeIncreasesMarkCompact) {
if (!v8_flags.compact) return;
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
v8_flags.concurrent_array_buffer_sweeping = false;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
heap::AbandonCurrentlyFreeMemory(heap->old_space());
ExternalBackingStoreType type = ExternalBackingStoreType::kArrayBuffer;
// We need to invoke GC without stack, otherwise some objects may survive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
const size_t backing_store_before =
heap->old_space()->ExternalBackingStoreBytes(type);
const size_t kArraybufferSize = 117;
{
v8::HandleScope handle_scope(isolate);
Local<v8::ArrayBuffer> ab1 =
v8::ArrayBuffer::New(isolate, kArraybufferSize);
IndirectHandle<JSArrayBuffer> buf1 = v8::Utils::OpenIndirectHandle(*ab1);
CHECK(IsTracked(heap, *buf1));
heap::InvokeAtomicMajorGC(heap);
PageMetadata* page_before_gc = PageMetadata::FromHeapObject(*buf1);
heap::ForceEvacuationCandidate(page_before_gc);
CHECK(IsTracked(heap, *buf1));
heap::InvokeMajorGC(heap);
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(kArraybufferSize, backing_store_after - backing_store_before);
}
heap::InvokeAtomicMajorGC(heap);
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(0, backing_store_after - backing_store_before);
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,440 @@
// Copyright 2015 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/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"
#include "src/heap/mark-compact.h"
#include "src/heap/marking-state-inl.h"
#include "src/heap/mutable-page-metadata.h"
#include "src/heap/remembered-set-inl.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
namespace {
void CheckInvariantsOfAbortedPage(PageMetadata* page) {
// Check invariants:
// 1) Markbits are cleared
// 2) The page is not marked as evacuation candidate anymore
// 3) The page is not marked as aborted compaction anymore.
CHECK(page->marking_bitmap()->IsClean());
CHECK(!page->Chunk()->IsEvacuationCandidate());
CHECK(!page->Chunk()->IsFlagSet(MemoryChunk::COMPACTION_WAS_ABORTED));
}
void CheckAllObjectsOnPage(const DirectHandleVector<FixedArray>& handles,
PageMetadata* page) {
for (DirectHandle<FixedArray> fixed_array : handles) {
CHECK(PageMetadata::FromHeapObject(*fixed_array) == page);
}
}
} // namespace
HEAP_TEST(CompactionFullAbortedPage) {
if (!v8_flags.compact) return;
// Test the scenario where we reach OOM during compaction and the whole page
// is aborted.
// Disable concurrent sweeping to ensure memory is in an expected state, i.e.,
// we can reach the state of a half aborted page.
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
auto reset_oom = [](void* heap, size_t limit, size_t) -> size_t {
reinterpret_cast<Heap*>(heap)->set_force_oom(false);
return limit;
};
heap->AddNearHeapLimitCallback(reset_oom, heap);
{
HandleScope scope1(isolate);
heap::SealCurrentObjects(heap);
{
HandleScope scope2(isolate);
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
DirectHandleVector<FixedArray> compaction_page_handles(isolate);
heap::CreatePadding(
heap,
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage()),
AllocationType::kOld, &compaction_page_handles);
PageMetadata* to_be_aborted_page =
PageMetadata::FromHeapObject(*compaction_page_handles.front());
to_be_aborted_page->Chunk()->SetFlagNonExecutable(
MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING);
CheckAllObjectsOnPage(compaction_page_handles, to_be_aborted_page);
heap->set_force_oom(true);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
// Check that all handles still point to the same page, i.e., compaction
// has been aborted on the page.
for (DirectHandle<FixedArray> object : compaction_page_handles) {
CHECK_EQ(to_be_aborted_page, PageMetadata::FromHeapObject(*object));
}
CheckInvariantsOfAbortedPage(to_be_aborted_page);
}
}
heap->RemoveNearHeapLimitCallback(reset_oom, 0u);
}
namespace {
int GetObjectSize(int objects_per_page) {
int allocatable =
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage());
// Make sure that object_size is a multiple of kTaggedSize.
int object_size =
((allocatable / kTaggedSize) / objects_per_page) * kTaggedSize;
return std::min(kMaxRegularHeapObjectSize, object_size);
}
} // namespace
HEAP_TEST(CompactionPartiallyAbortedPage) {
if (!v8_flags.compact) return;
// Test the scenario where we reach OOM during compaction and parts of the
// page have already been migrated to a new one.
// Disable concurrent sweeping to ensure memory is in an expected state, i.e.,
// we can reach the state of a half aborted page.
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
const int objects_per_page = 10;
const int object_size = GetObjectSize(objects_per_page);
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
auto reset_oom = [](void* heap, size_t limit, size_t) -> size_t {
reinterpret_cast<Heap*>(heap)->set_force_oom(false);
return limit;
};
heap->AddNearHeapLimitCallback(reset_oom, heap);
{
HandleScope scope1(isolate);
heap::SealCurrentObjects(heap);
{
HandleScope scope2(isolate);
// Fill another page with objects of size {object_size} (last one is
// properly adjusted).
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
DirectHandleVector<FixedArray> compaction_page_handles(isolate);
heap::CreatePadding(
heap,
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage()),
AllocationType::kOld, &compaction_page_handles, object_size);
PageMetadata* to_be_aborted_page =
PageMetadata::FromHeapObject(*compaction_page_handles.front());
to_be_aborted_page->Chunk()->SetFlagNonExecutable(
MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING);
CheckAllObjectsOnPage(compaction_page_handles, to_be_aborted_page);
{
// Add another page that is filled with {num_objects} objects of size
// {object_size}.
HandleScope scope3(isolate);
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
const int num_objects = 3;
DirectHandleVector<FixedArray> page_to_fill_handles(isolate);
heap::CreatePadding(heap, object_size * num_objects,
AllocationType::kOld, &page_to_fill_handles,
object_size);
PageMetadata* page_to_fill =
PageMetadata::FromAddress(page_to_fill_handles.front()->address());
heap->set_force_oom(true);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
bool migration_aborted = false;
for (DirectHandle<FixedArray> object : compaction_page_handles) {
// Once compaction has been aborted, all following objects still have
// to be on the initial page.
CHECK(!migration_aborted ||
(PageMetadata::FromHeapObject(*object) == to_be_aborted_page));
if (PageMetadata::FromHeapObject(*object) == to_be_aborted_page) {
// This object has not been migrated.
migration_aborted = true;
} else {
CHECK_EQ(PageMetadata::FromHeapObject(*object), page_to_fill);
}
}
// Check that we actually created a scenario with a partially aborted
// page.
CHECK(migration_aborted);
CheckInvariantsOfAbortedPage(to_be_aborted_page);
}
}
}
heap->RemoveNearHeapLimitCallback(reset_oom, 0u);
}
HEAP_TEST(CompactionPartiallyAbortedPageIntraAbortedPointers) {
if (!v8_flags.compact) return;
// Test the scenario where we reach OOM during compaction and parts of the
// page have already been migrated to a new one. Objects on the aborted page
// are linked together. This test makes sure that intra-aborted page pointers
// get properly updated.
// Disable concurrent sweeping to ensure memory is in an expected state, i.e.,
// we can reach the state of a half aborted page.
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
const int objects_per_page = 10;
const int object_size = GetObjectSize(objects_per_page);
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
auto reset_oom = [](void* heap, size_t limit, size_t) -> size_t {
reinterpret_cast<Heap*>(heap)->set_force_oom(false);
return limit;
};
heap->AddNearHeapLimitCallback(reset_oom, heap);
{
HandleScope scope1(isolate);
IndirectHandle<FixedArray> root_array =
isolate->factory()->NewFixedArray(10, AllocationType::kOld);
heap::SealCurrentObjects(heap);
PageMetadata* to_be_aborted_page = nullptr;
{
HandleScope temporary_scope(isolate);
// Fill a fresh page with objects of size {object_size} (last one is
// properly adjusted).
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
DirectHandleVector<FixedArray> compaction_page_handles(isolate);
heap::CreatePadding(
heap,
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage()),
AllocationType::kOld, &compaction_page_handles, object_size);
to_be_aborted_page =
PageMetadata::FromHeapObject(*compaction_page_handles.front());
to_be_aborted_page->Chunk()->SetFlagNonExecutable(
MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING);
for (size_t i = compaction_page_handles.size() - 1; i > 0; i--) {
compaction_page_handles[i]->set(0, *compaction_page_handles[i - 1]);
}
root_array->set(0, *compaction_page_handles.back());
CheckAllObjectsOnPage(compaction_page_handles, to_be_aborted_page);
}
{
// Add another page that is filled with {num_objects} objects of size
// {object_size}.
HandleScope scope3(isolate);
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
const int num_objects = 2;
int used_memory = object_size * num_objects;
DirectHandleVector<FixedArray> page_to_fill_handles(isolate);
heap::CreatePadding(heap, used_memory, AllocationType::kOld,
&page_to_fill_handles, object_size);
PageMetadata* page_to_fill =
PageMetadata::FromHeapObject(*page_to_fill_handles.front());
// We need to invoke GC without stack, otherwise no compaction is
// performed.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap->set_force_oom(true);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
// The following check makes sure that we compacted "some" objects, while
// leaving others in place.
bool in_place = true;
IndirectHandle<FixedArray> current = root_array;
while (current->get(0) != ReadOnlyRoots(heap).undefined_value()) {
current = IndirectHandle<FixedArray>(Cast<FixedArray>(current->get(0)),
isolate);
CHECK(IsFixedArray(*current));
if (PageMetadata::FromHeapObject(*current) != to_be_aborted_page) {
in_place = false;
}
bool on_aborted_page =
PageMetadata::FromHeapObject(*current) == to_be_aborted_page;
bool on_fill_page =
PageMetadata::FromHeapObject(*current) == page_to_fill;
CHECK((in_place && on_aborted_page) || (!in_place && on_fill_page));
}
// Check that we at least migrated one object, as otherwise the test would
// not trigger.
CHECK(!in_place);
CheckInvariantsOfAbortedPage(to_be_aborted_page);
}
}
heap->RemoveNearHeapLimitCallback(reset_oom, 0u);
}
HEAP_TEST(CompactionPartiallyAbortedPageWithRememberedSetEntries) {
if (!v8_flags.compact || v8_flags.single_generation) return;
// Test the scenario where we reach OOM during compaction and parts of the
// page have already been migrated to a new one. Objects on the aborted page
// are linked together and the very first object on the aborted page points
// into new space. The test verifies that the remembered set entries are
// properly cleared and rebuilt after aborting a page. Failing to do so can
// result in other objects being allocated in the free space where their
// payload looks like a valid new space pointer.
// Disable concurrent sweeping to ensure memory is in an expected state, i.e.,
// we can reach the state of a half aborted page.
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
const int objects_per_page = 10;
const int object_size = GetObjectSize(objects_per_page);
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
auto reset_oom = [](void* heap, size_t limit, size_t) -> size_t {
reinterpret_cast<Heap*>(heap)->set_force_oom(false);
return limit;
};
heap->AddNearHeapLimitCallback(reset_oom, heap);
{
HandleScope scope1(isolate);
IndirectHandle<FixedArray> root_array =
isolate->factory()->NewFixedArray(10, AllocationType::kOld);
heap::SealCurrentObjects(heap);
PageMetadata* to_be_aborted_page = nullptr;
{
HandleScope temporary_scope(isolate);
// Fill another page with objects of size {object_size} (last one is
// properly adjusted).
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
DirectHandleVector<FixedArray> compaction_page_handles(isolate);
heap::CreatePadding(
heap,
static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage()),
AllocationType::kOld, &compaction_page_handles, object_size);
// Sanity check that we have enough space for linking up arrays.
CHECK_GE(compaction_page_handles.front()->length(), 2);
to_be_aborted_page =
PageMetadata::FromHeapObject(*compaction_page_handles.front());
to_be_aborted_page->Chunk()->SetFlagNonExecutable(
MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING);
for (size_t i = compaction_page_handles.size() - 1; i > 0; i--) {
compaction_page_handles[i]->set(0, *compaction_page_handles[i - 1]);
}
root_array->set(0, *compaction_page_handles.back());
DirectHandle<FixedArray> new_space_array =
isolate->factory()->NewFixedArray(1, AllocationType::kYoung);
CHECK(HeapLayout::InYoungGeneration(*new_space_array));
compaction_page_handles.front()->set(1, *new_space_array);
CheckAllObjectsOnPage(compaction_page_handles, to_be_aborted_page);
}
{
// Add another page that is filled with {num_objects} objects of size
// {object_size}.
HandleScope scope3(isolate);
CHECK(heap->old_space()->TryExpand(heap->main_thread_local_heap(),
AllocationOrigin::kRuntime));
const int num_objects = 2;
int used_memory = object_size * num_objects;
DirectHandleVector<FixedArray> page_to_fill_handles(isolate);
heap::CreatePadding(heap, used_memory, AllocationType::kOld,
&page_to_fill_handles, object_size);
PageMetadata* page_to_fill =
PageMetadata::FromHeapObject(*page_to_fill_handles.front());
// We need to invoke GC without stack, otherwise no compaction is
// performed.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap->set_force_oom(true);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
// The following check makes sure that we compacted "some" objects, while
// leaving others in place.
bool in_place = true;
IndirectHandle<FixedArray> current = root_array;
while (current->get(0) != ReadOnlyRoots(heap).undefined_value()) {
current = IndirectHandle<FixedArray>(Cast<FixedArray>(current->get(0)),
isolate);
CHECK(!HeapLayout::InYoungGeneration(*current));
CHECK(IsFixedArray(*current));
if (PageMetadata::FromHeapObject(*current) != to_be_aborted_page) {
in_place = false;
}
bool on_aborted_page =
PageMetadata::FromHeapObject(*current) == to_be_aborted_page;
bool on_fill_page =
PageMetadata::FromHeapObject(*current) == page_to_fill;
CHECK((in_place && on_aborted_page) || (!in_place && on_fill_page));
}
// Check that we at least migrated one object, as otherwise the test would
// not trigger.
CHECK(!in_place);
CheckInvariantsOfAbortedPage(to_be_aborted_page);
// Allocate a new object in new space.
IndirectHandle<FixedArray> holder =
isolate->factory()->NewFixedArray(10, AllocationType::kYoung);
// Create a broken address that looks like a tagged pointer to a new space
// object.
Address broken_address = holder->address() + 2 * kTaggedSize + 1;
// Convert it to a vector to create a string from it.
base::Vector<const uint8_t> string_to_broken_address(
reinterpret_cast<const uint8_t*>(&broken_address), kTaggedSize);
IndirectHandle<String> string;
do {
// We know that the interesting slot will be on the aborted page and
// hence we allocate until we get our string on the aborted page.
// We used slot 1 in the fixed size array which corresponds to the
// the first word in the string. Since the first object definitely
// migrated we can just allocate until we hit the aborted page.
string = isolate->factory()
->NewStringFromOneByte(string_to_broken_address,
AllocationType::kOld)
.ToHandleChecked();
} while (PageMetadata::FromHeapObject(*string) != to_be_aborted_page);
// If remembered set entries are not properly filtered/reset for aborted
// pages we have now a broken address at an object slot in old space and
// the following scavenge will crash.
heap::InvokeMinorGC(CcTest::heap());
}
}
heap->RemoveNearHeapLimitCallback(reset_oom, 0u);
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,605 @@
// 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 <memory>
#include "src/api/api.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/semaphore.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/assembler.h"
#include "src/codegen/macro-assembler-inl.h"
#include "src/codegen/macro-assembler.h"
#include "src/codegen/reloc-info-inl.h"
#include "src/common/globals.h"
#include "src/common/ptr-compr.h"
#include "src/handles/global-handles-inl.h"
#include "src/handles/handles-inl.h"
#include "src/handles/handles.h"
#include "src/handles/local-handles-inl.h"
#include "src/handles/persistent-handles.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap-inl.h"
#include "src/heap/marking-state-inl.h"
#include "src/heap/parked-scope.h"
#include "src/heap/safepoint.h"
#include "src/objects/heap-number.h"
#include "src/objects/heap-object.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace {
void CreateFixedArray(Heap* heap, Address start, int size) {
Tagged<HeapObject> object = HeapObject::FromAddress(start);
object->set_map_after_allocation(heap->isolate(),
ReadOnlyRoots(heap).fixed_array_map(),
SKIP_WRITE_BARRIER);
Tagged<FixedArray> array = Cast<FixedArray>(object);
int length = (size - OFFSET_OF_DATA_START(FixedArray)) / kTaggedSize;
array->set_length(length);
MemsetTagged(array->RawFieldOfFirstElement(),
ReadOnlyRoots(heap).undefined_value(), length);
}
const int kNumIterations = 2000;
const int kSmallObjectSize = 10 * kTaggedSize;
const int kMediumObjectSize = 8 * KB;
void AllocateSomeObjects(LocalHeap* local_heap) {
for (int i = 0; i < kNumIterations; i++) {
AllocationResult result = local_heap->AllocateRaw(
kSmallObjectSize, AllocationType::kOld, AllocationOrigin::kRuntime,
AllocationAlignment::kTaggedAligned);
if (!result.IsFailure()) {
CreateFixedArray(local_heap->heap(), result.ToAddress(),
kSmallObjectSize);
}
result = local_heap->AllocateRaw(kMediumObjectSize, AllocationType::kOld,
AllocationOrigin::kRuntime,
AllocationAlignment::kTaggedAligned);
if (!result.IsFailure()) {
CreateFixedArray(local_heap->heap(), result.ToAddress(),
kMediumObjectSize);
}
if (i % 10 == 0) {
local_heap->Safepoint();
}
}
}
} // namespace
class ConcurrentAllocationThread final : public v8::base::Thread {
public:
explicit ConcurrentAllocationThread(Heap* heap,
std::atomic<int>* pending = nullptr)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
pending_(pending) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&local_heap);
AllocateSomeObjects(&local_heap);
if (pending_) pending_->fetch_sub(1);
}
Heap* heap_;
std::atomic<int>* pending_;
};
UNINITIALIZED_TEST(ConcurrentAllocationInOldSpace) {
v8_flags.detect_ineffective_gcs_near_heap_limit = false;
v8_flags.max_old_space_size = 32;
v8_flags.stress_concurrent_allocation = false;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
std::vector<std::unique_ptr<ConcurrentAllocationThread>> threads;
const int kThreads = 4;
std::atomic<int> pending(kThreads);
for (int i = 0; i < kThreads; i++) {
auto thread = std::make_unique<ConcurrentAllocationThread>(
i_isolate->heap(), &pending);
CHECK(thread->Start());
threads.push_back(std::move(thread));
}
while (pending > 0) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(), isolate);
}
for (auto& thread : threads) {
thread->Join();
}
isolate->Dispose();
}
UNINITIALIZED_TEST(ConcurrentAllocationInOldSpaceFromMainThread) {
v8_flags.max_old_space_size = 4;
v8_flags.stress_concurrent_allocation = false;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
{
v8::Isolate::Scope isolate_scope(isolate);
AllocateSomeObjects(i_isolate->main_thread_local_heap());
}
isolate->Dispose();
}
UNINITIALIZED_TEST(ConcurrentAllocationWhileMainThreadIsParked) {
#ifndef V8_ENABLE_CONSERVATIVE_STACK_SCANNING
v8_flags.max_old_space_size = 4;
#else
// With CSS, it is expected that the GCs triggered by concurrent allocation
// will reclaim less memory. If this test fails, this limit should probably
// be further increased.
v8_flags.max_old_space_size = 10;
#endif
v8_flags.stress_concurrent_allocation = false;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
std::vector<std::unique_ptr<ConcurrentAllocationThread>> threads;
const int kThreads = 4;
i_isolate->main_thread_local_isolate()->ExecuteMainThreadWhileParked(
[i_isolate, &threads]() {
for (int i = 0; i < kThreads; i++) {
auto thread =
std::make_unique<ConcurrentAllocationThread>(i_isolate->heap());
CHECK(thread->Start());
threads.push_back(std::move(thread));
}
for (auto& thread : threads) {
thread->Join();
}
});
isolate->Dispose();
}
UNINITIALIZED_TEST(ConcurrentAllocationWhileMainThreadParksAndUnparks) {
#ifndef V8_ENABLE_CONSERVATIVE_STACK_SCANNING
v8_flags.max_old_space_size = 4;
#else
// With CSS, it is expected that the GCs triggered by concurrent allocation
// will reclaim less memory. If this test fails, this limit should probably
// be further increased.
v8_flags.max_old_space_size = 10;
#endif
v8_flags.stress_concurrent_allocation = false;
v8_flags.incremental_marking = false;
i::FlagList::EnforceFlagImplications();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
std::vector<std::unique_ptr<ConcurrentAllocationThread>> threads;
const int kThreads = 4;
{
for (int i = 0; i < kThreads; i++) {
auto thread =
std::make_unique<ConcurrentAllocationThread>(i_isolate->heap());
CHECK(thread->Start());
threads.push_back(std::move(thread));
}
for (int i = 0; i < 300'000; i++) {
i_isolate->main_thread_local_isolate()->ExecuteMainThreadWhileParked(
[]() { /* nothing */ });
}
i_isolate->main_thread_local_isolate()->ExecuteMainThreadWhileParked(
[&threads]() {
for (auto& thread : threads) {
thread->Join();
}
});
}
isolate->Dispose();
}
UNINITIALIZED_TEST(ConcurrentAllocationWhileMainThreadRunsWithSafepoints) {
#ifndef V8_ENABLE_CONSERVATIVE_STACK_SCANNING
v8_flags.max_old_space_size = 4;
#else
// With CSS, it is expected that the GCs triggered by concurrent allocation
// will reclaim less memory. If this test fails, this limit should probably
// be further increased.
v8_flags.max_old_space_size = 10;
#endif
v8_flags.stress_concurrent_allocation = false;
v8_flags.incremental_marking = false;
i::FlagList::EnforceFlagImplications();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
std::vector<std::unique_ptr<ConcurrentAllocationThread>> threads;
const int kThreads = 4;
{
for (int i = 0; i < kThreads; i++) {
auto thread =
std::make_unique<ConcurrentAllocationThread>(i_isolate->heap());
CHECK(thread->Start());
threads.push_back(std::move(thread));
}
// Some of the following Safepoint() invocations are supposed to perform a
// GC.
for (int i = 0; i < 1'000'000; i++) {
i_isolate->main_thread_local_heap()->Safepoint();
}
i_isolate->main_thread_local_isolate()->ExecuteMainThreadWhileParked(
[&threads]() {
for (auto& thread : threads) {
thread->Join();
}
});
}
i_isolate->main_thread_local_heap()->Safepoint();
isolate->Dispose();
}
class LargeObjectConcurrentAllocationThread final : public v8::base::Thread {
public:
explicit LargeObjectConcurrentAllocationThread(Heap* heap,
std::atomic<int>* pending)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
pending_(pending) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&local_heap);
const size_t kLargeObjectSize = kMaxRegularHeapObjectSize * 2;
for (int i = 0; i < kNumIterations; i++) {
AllocationResult result = local_heap.AllocateRaw(
kLargeObjectSize, AllocationType::kOld, AllocationOrigin::kRuntime,
AllocationAlignment::kTaggedAligned);
if (result.IsFailure()) {
heap_->CollectGarbageFromAnyThread(&local_heap);
} else {
Address address = result.ToAddress();
CreateFixedArray(heap_, address, kLargeObjectSize);
}
local_heap.Safepoint();
}
pending_->fetch_sub(1);
}
Heap* heap_;
std::atomic<int>* pending_;
};
UNINITIALIZED_TEST(ConcurrentAllocationInLargeSpace) {
v8_flags.detect_ineffective_gcs_near_heap_limit = false;
v8_flags.max_old_space_size = 32;
v8_flags.stress_concurrent_allocation = false;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
std::vector<std::unique_ptr<LargeObjectConcurrentAllocationThread>> threads;
const int kThreads = 4;
std::atomic<int> pending(kThreads);
for (int i = 0; i < kThreads; i++) {
auto thread = std::make_unique<LargeObjectConcurrentAllocationThread>(
i_isolate->heap(), &pending);
CHECK(thread->Start());
threads.push_back(std::move(thread));
}
while (pending > 0) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(), isolate);
}
for (auto& thread : threads) {
thread->Join();
}
isolate->Dispose();
}
const int kWhiteIterations = 1000;
class ConcurrentBlackAllocationThread final : public v8::base::Thread {
public:
explicit ConcurrentBlackAllocationThread(
Heap* heap, std::vector<Address>* objects, base::Semaphore* sema_white,
base::Semaphore* sema_marking_started)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
objects_(objects),
sema_white_(sema_white),
sema_marking_started_(sema_marking_started) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&local_heap);
for (int i = 0; i < kNumIterations; i++) {
if (i == kWhiteIterations) {
local_heap.ExecuteWhileParked([this]() {
sema_white_->Signal();
sema_marking_started_->Wait();
});
}
Address address = local_heap.AllocateRawOrFail(
kSmallObjectSize, AllocationType::kOld, AllocationOrigin::kRuntime,
AllocationAlignment::kTaggedAligned);
objects_->push_back(address);
CreateFixedArray(heap_, address, kSmallObjectSize);
address = local_heap.AllocateRawOrFail(
kMediumObjectSize, AllocationType::kOld, AllocationOrigin::kRuntime,
AllocationAlignment::kTaggedAligned);
objects_->push_back(address);
CreateFixedArray(heap_, address, kMediumObjectSize);
}
}
Heap* heap_;
std::vector<Address>* objects_;
base::Semaphore* sema_white_;
base::Semaphore* sema_marking_started_;
};
UNINITIALIZED_TEST(ConcurrentBlackAllocation) {
if (!v8_flags.incremental_marking) return;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
Heap* heap = i_isolate->heap();
{
v8::Isolate::Scope isolate_scope(isolate);
std::vector<Address> objects;
base::Semaphore sema_white(0);
base::Semaphore sema_marking_started(0);
auto thread = std::make_unique<ConcurrentBlackAllocationThread>(
heap, &objects, &sema_white, &sema_marking_started);
CHECK(thread->Start());
sema_white.Wait();
heap->StartIncrementalMarking(i::GCFlag::kNoFlags,
i::GarbageCollectionReason::kTesting);
sema_marking_started.Signal();
thread->Join();
const int kObjectsAllocatedPerIteration = 2;
for (int i = 0; i < kNumIterations * kObjectsAllocatedPerIteration; i++) {
Address address = objects[i];
Tagged<HeapObject> object = HeapObject::FromAddress(address);
if (v8_flags.black_allocated_pages) {
CHECK(heap->marking_state()->IsUnmarked(object));
if (i < kWhiteIterations * kObjectsAllocatedPerIteration) {
CHECK(!PageMetadata::FromHeapObject(object)->Chunk()->IsFlagSet(
MemoryChunk::BLACK_ALLOCATED));
} else {
CHECK(PageMetadata::FromHeapObject(object)->Chunk()->IsFlagSet(
MemoryChunk::BLACK_ALLOCATED));
}
} else {
if (i < kWhiteIterations * kObjectsAllocatedPerIteration) {
CHECK(heap->marking_state()->IsUnmarked(object));
} else {
CHECK(heap->marking_state()->IsMarked(object));
}
}
}
}
isolate->Dispose();
}
class ConcurrentWriteBarrierThread final : public v8::base::Thread {
public:
ConcurrentWriteBarrierThread(Heap* heap, Tagged<FixedArray> fixed_array,
Tagged<HeapObject> value)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
fixed_array_(fixed_array),
value_(value) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&local_heap);
fixed_array_->set(0, value_);
}
Heap* heap_;
Tagged<FixedArray> fixed_array_;
Tagged<HeapObject> value_;
};
UNINITIALIZED_TEST(ConcurrentWriteBarrier) {
if (!v8_flags.incremental_marking) return;
if (!v8_flags.concurrent_marking) {
// The test requires concurrent marking barrier.
return;
}
ManualGCScope manual_gc_scope;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
Heap* heap = i_isolate->heap();
{
v8::Isolate::Scope isolate_scope(isolate);
PtrComprCageAccessScope ptr_compr_cage_access_scope(i_isolate);
Tagged<FixedArray> fixed_array;
Tagged<HeapObject> value;
{
HandleScope handle_scope(i_isolate);
DirectHandle<FixedArray> fixed_array_handle(
i_isolate->factory()->NewFixedArray(1));
DirectHandle<HeapNumber> value_handle(
i_isolate->factory()->NewHeapNumber<AllocationType::kOld>(1.1));
fixed_array = *fixed_array_handle;
value = *value_handle;
}
heap->StartIncrementalMarking(i::GCFlag::kNoFlags,
i::GarbageCollectionReason::kTesting);
CHECK(heap->marking_state()->IsUnmarked(value));
// Mark host |fixed_array| to trigger the barrier.
heap->marking_state()->TryMarkAndAccountLiveBytes(fixed_array);
auto thread = std::make_unique<ConcurrentWriteBarrierThread>(
heap, fixed_array, value);
CHECK(thread->Start());
thread->Join();
CHECK(heap->marking_state()->IsMarked(value));
heap::InvokeMajorGC(heap);
}
isolate->Dispose();
}
class ConcurrentRecordRelocSlotThread final : public v8::base::Thread {
public:
ConcurrentRecordRelocSlotThread(Heap* heap, Tagged<Code> code,
Tagged<HeapObject> value)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
code_(code),
value_(value) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground);
UnparkedScope unparked_scope(&local_heap);
DisallowGarbageCollection no_gc;
Tagged<InstructionStream> istream = code_->instruction_stream();
int mode_mask = RelocInfo::EmbeddedObjectModeMask();
WritableJitAllocation jit_allocation = ThreadIsolation::LookupJitAllocation(
istream->address(), istream->Size(),
ThreadIsolation::JitAllocationType::kInstructionStream, true);
for (WritableRelocIterator it(jit_allocation, istream,
code_->constant_pool(), mode_mask);
!it.done(); it.next()) {
DCHECK(RelocInfo::IsEmbeddedObjectMode(it.rinfo()->rmode()));
it.rinfo()->set_target_object(istream, value_);
}
}
Heap* heap_;
Tagged<Code> code_;
Tagged<HeapObject> value_;
};
UNINITIALIZED_TEST(ConcurrentRecordRelocSlot) {
if (!v8_flags.incremental_marking) return;
if (!v8_flags.concurrent_marking) {
// The test requires concurrent marking barrier.
return;
}
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
Heap* heap = i_isolate->heap();
{
v8::Isolate::Scope isolate_scope(isolate);
PtrComprCageAccessScope ptr_compr_cage_access_scope(i_isolate);
Tagged<Code> code;
Tagged<HeapObject> value;
{
HandleScope handle_scope(i_isolate);
uint8_t buffer[i::Assembler::kDefaultBufferSize];
MacroAssembler masm(i_isolate, v8::internal::CodeObjectRequired::kYes,
ExternalAssemblerBuffer(buffer, sizeof(buffer)));
#if V8_TARGET_ARCH_ARM64
// Arm64 requires stack alignment.
UseScratchRegisterScope temps(&masm);
Register tmp = temps.AcquireX();
masm.Mov(tmp, Operand(i_isolate->factory()->undefined_value()));
masm.Push(tmp, padreg);
#else
masm.Push(i_isolate->factory()->undefined_value());
#endif
CodeDesc desc;
masm.GetCode(i_isolate, &desc);
DirectHandle<Code> code_handle =
Factory::CodeBuilder(i_isolate, desc, CodeKind::FOR_TESTING).Build();
// Globalize the handle for |code| for the incremental marker to mark it.
i_isolate->global_handles()->Create(*code_handle);
heap::AbandonCurrentlyFreeMemory(heap->old_space());
DirectHandle<HeapNumber> value_handle(
i_isolate->factory()->NewHeapNumber<AllocationType::kOld>(1.1));
heap::ForceEvacuationCandidate(
PageMetadata::FromHeapObject(*value_handle));
code = *code_handle;
value = *value_handle;
}
heap->StartIncrementalMarking(i::GCFlag::kNoFlags,
i::GarbageCollectionReason::kTesting);
CHECK(heap->marking_state()->IsUnmarked(value));
// Advance marking to make sure |code| is marked.
heap->incremental_marking()->AdvanceForTesting(v8::base::TimeDelta::Max());
CHECK(heap->marking_state()->IsMarked(code));
CHECK(heap->marking_state()->IsUnmarked(value));
{
auto thread =
std::make_unique<ConcurrentRecordRelocSlotThread>(heap, code, value);
CHECK(thread->Start());
thread->Join();
}
CHECK(heap->marking_state()->IsMarked(value));
heap::InvokeMajorGC(heap);
}
isolate->Dispose();
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,65 @@
// Copyright 2017 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 <stdlib.h>
#include "src/heap/concurrent-marking.h"
#include "src/heap/heap.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8::internal::heap {
TEST(ConcurrentMarkingMarkedBytes) {
if (!v8_flags.incremental_marking) return;
if (!i::v8_flags.concurrent_marking) return;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = CcTest::heap();
HandleScope sc(isolate);
DirectHandle<FixedArray> root = isolate->factory()->NewFixedArray(1000000);
heap::InvokeMajorGC(heap);
if (!heap->incremental_marking()->IsStopped()) return;
// Store array in Global such that it is part of the root set when
// starting incremental marking.
v8::Global<Value> global_root(CcTest::isolate(),
Utils::ToLocal(Cast<Object>(root)));
heap::SimulateIncrementalMarking(heap, false);
// Ensure that objects are published to the global marking worklist such that
// the concurrent markers can pick it up.
heap->mark_compact_collector()->local_marking_worklists()->Publish();
heap->concurrent_marking()->JoinJobForTesting();
CHECK_GE(heap->concurrent_marking()->TotalMarkedBytes(), root->Size());
}
UNINITIALIZED_TEST(ConcurrentMarkingStoppedOnTeardown) {
if (!v8_flags.incremental_marking) return;
if (!i::v8_flags.concurrent_marking) return;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
Factory* factory = i_isolate->factory();
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Context::New(isolate)->Enter();
for (int i = 0; i < 10000; i++) {
factory->NewJSWeakMap();
}
Heap* heap = i_isolate->heap();
heap::SimulateIncrementalMarking(heap, false);
}
isolate->Dispose();
}
} // namespace v8::internal::heap

View File

@ -0,0 +1,230 @@
// Copyright 2018 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/api/api-inl.h"
#include "src/api/api.h"
#include "src/execution/isolate.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap-layout-inl.h"
#include "src/heap/spaces.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
#define TEST_STR "tests are great!"
namespace v8 {
namespace internal {
namespace heap {
// Adapted from cctest/test-api.cc
class TestOneByteResource : public v8::String::ExternalOneByteStringResource {
public:
explicit TestOneByteResource(const char* data, int* counter = nullptr,
size_t offset = 0)
: orig_data_(data),
data_(data + offset),
length_(strlen(data) - offset),
counter_(counter) {}
~TestOneByteResource() override {
i::DeleteArray(orig_data_);
if (counter_ != nullptr) ++*counter_;
}
const char* data() const override { return data_; }
size_t length() const override { return length_; }
private:
const char* orig_data_;
const char* data_;
size_t length_;
int* counter_;
};
TEST(ExternalString_ExternalBackingStoreSizeIncreases) {
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
ExternalBackingStoreType type = ExternalBackingStoreType::kExternalString;
const size_t backing_store_before =
heap->old_space()->ExternalBackingStoreBytes(type);
{
v8::HandleScope handle_scope(isolate);
v8::Local<v8::String> es = v8::String::NewExternalOneByte(
isolate, new TestOneByteResource(i::StrDup(TEST_STR))).ToLocalChecked();
USE(es);
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(es->Length(), backing_store_after - backing_store_before);
}
}
TEST(ExternalString_ExternalBackingStoreSizeDecreases) {
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
ExternalBackingStoreType type = ExternalBackingStoreType::kExternalString;
const size_t backing_store_before =
heap->old_space()->ExternalBackingStoreBytes(type);
{
v8::HandleScope handle_scope(isolate);
v8::Local<v8::String> es = v8::String::NewExternalOneByte(
isolate, new TestOneByteResource(i::StrDup(TEST_STR))).ToLocalChecked();
USE(es);
}
{
// We need to invoke GC without stack, otherwise some objects may not be
// reclaimed because of conservative stack scanning.
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap::InvokeAtomicMajorGC(heap);
}
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(0, backing_store_after - backing_store_before);
}
TEST(ExternalString_ExternalBackingStoreSizeIncreasesMarkCompact) {
if (!v8_flags.compact) return;
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
heap::AbandonCurrentlyFreeMemory(heap->old_space());
ExternalBackingStoreType type = ExternalBackingStoreType::kExternalString;
const size_t backing_store_before =
heap->old_space()->ExternalBackingStoreBytes(type);
{
v8::HandleScope handle_scope(isolate);
v8::Local<v8::String> es = v8::String::NewExternalOneByte(
isolate, new TestOneByteResource(i::StrDup(TEST_STR))).ToLocalChecked();
v8::internal::DirectHandle<v8::internal::String> esh =
v8::Utils::OpenDirectHandle(*es);
PageMetadata* page_before_gc = PageMetadata::FromHeapObject(*esh);
heap::ForceEvacuationCandidate(page_before_gc);
heap::InvokeMajorGC(heap);
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(es->Length(), backing_store_after - backing_store_before);
}
{
// We need to invoke GC without stack, otherwise some objects may not be
// reclaimed because of conservative stack scanning.
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap::InvokeAtomicMajorGC(heap);
}
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(0, backing_store_after - backing_store_before);
}
TEST(ExternalString_ExternalBackingStoreSizeIncreasesAfterExternalization) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
ExternalBackingStoreType type = ExternalBackingStoreType::kExternalString;
size_t old_backing_store_before = 0, new_backing_store_before = 0;
{
v8::HandleScope handle_scope(isolate);
new_backing_store_before =
heap->new_space()->ExternalBackingStoreBytes(type);
old_backing_store_before =
heap->old_space()->ExternalBackingStoreBytes(type);
// Allocate normal string in the new gen.
v8::Local<v8::String> str =
v8::String::NewFromUtf8Literal(isolate, TEST_STR);
CHECK_EQ(0, heap->new_space()->ExternalBackingStoreBytes(type) -
new_backing_store_before);
// Trigger full GC so that the newly allocated string moves to old gen.
heap::InvokeAtomicMajorGC(heap);
bool success = str->MakeExternal(
isolate, new TestOneByteResource(i::StrDup(TEST_STR)));
CHECK(success);
CHECK_EQ(str->Length(), heap->old_space()->ExternalBackingStoreBytes(type) -
old_backing_store_before);
}
{
// We need to invoke GC without stack, otherwise some objects may not be
// reclaimed because of conservative stack scanning.
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap::InvokeAtomicMajorGC(heap);
}
const size_t backing_store_after =
heap->old_space()->ExternalBackingStoreBytes(type);
CHECK_EQ(0, backing_store_after - old_backing_store_before);
}
TEST(ExternalString_PromotedThinString) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
i::Isolate* i_isolate = CcTest::i_isolate();
i::Factory* factory = i_isolate->factory();
Heap* heap = i_isolate->heap();
{
v8::HandleScope handle_scope(isolate);
// New external string in the old space.
v8::internal::Handle<v8::internal::String> string1 =
factory
->NewExternalStringFromOneByte(
new TestOneByteResource(i::StrDup(TEST_STR)))
.ToHandleChecked();
// Internalize external string.
i::Handle<i::String> isymbol1 = factory->InternalizeString(string1);
CHECK(IsInternalizedString(*isymbol1));
CHECK(IsExternalString(*string1));
CHECK(!HeapLayout::InYoungGeneration(*isymbol1));
// Collect thin string. References to the thin string will be updated to
// point to the actual external string in the old space.
heap::InvokeAtomicMinorGC(heap);
USE(isymbol1);
}
}
} // namespace heap
} // namespace internal
} // namespace v8
#undef TEST_STR

7676
deps/v8/test/cctest/heap/test-heap.cc vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
// Copyright 2015 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 <stdlib.h>
#include "src/heap/safepoint.h"
#ifdef __linux__
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <utility>
#include "src/handles/global-handles.h"
#include "src/heap/gc-tracer.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/spaces.h"
#include "src/init/v8.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
using v8::IdleTask;
using v8::Task;
using v8::Isolate;
namespace v8 {
namespace internal {
namespace heap {
class MockPlatform : public TestPlatform {
public:
MockPlatform() : taskrunner_(new MockTaskRunner()) {}
~MockPlatform() override {
for (auto& task : worker_tasks_) {
CcTest::default_platform()->PostTaskOnWorkerThread(
TaskPriority::kUserVisible, std::move(task));
}
worker_tasks_.clear();
}
std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate, v8::TaskPriority) override {
return taskrunner_;
}
void PostTaskOnWorkerThreadImpl(TaskPriority priority,
std::unique_ptr<Task> task,
const SourceLocation& location) override {
worker_tasks_.push_back(std::move(task));
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return false; }
bool PendingTask() { return taskrunner_->PendingTask(); }
void PerformTask() { taskrunner_->PerformTask(); }
private:
class MockTaskRunner : public v8::TaskRunner {
public:
void PostTaskImpl(std::unique_ptr<v8::Task> task,
const SourceLocation& location) override {
task_ = std::move(task);
}
void PostNonNestableTaskImpl(std::unique_ptr<Task> task,
const SourceLocation& location) override {
PostTask(std::move(task));
}
void PostDelayedTaskImpl(std::unique_ptr<Task> task,
double delay_in_seconds,
const SourceLocation& location) override {
PostTask(std::move(task));
}
void PostNonNestableDelayedTaskImpl(
std::unique_ptr<Task> task, double delay_in_seconds,
const SourceLocation& location) override {
PostTask(std::move(task));
}
void PostIdleTaskImpl(std::unique_ptr<IdleTask> task,
const SourceLocation& location) override {
UNREACHABLE();
}
bool IdleTasksEnabled() override { return false; }
bool NonNestableTasksEnabled() const override { return true; }
bool NonNestableDelayedTasksEnabled() const override { return true; }
bool PendingTask() { return task_ != nullptr; }
void PerformTask() {
std::unique_ptr<Task> task = std::move(task_);
task->Run();
}
private:
std::unique_ptr<Task> task_;
};
std::shared_ptr<MockTaskRunner> taskrunner_;
std::vector<std::unique_ptr<Task>> worker_tasks_;
};
TEST_WITH_PLATFORM(IncrementalMarkingUsingTasks, MockPlatform) {
if (!i::v8_flags.incremental_marking) return;
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
v8_flags.stress_incremental_marking = false;
v8::Isolate* isolate = CcTest::isolate();
{
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = CcTest::NewContext(isolate);
v8::Context::Scope context_scope(context);
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
Heap* heap = i_isolate->heap();
i::heap::SimulateFullSpace(heap->old_space());
i::IncrementalMarking* marking = heap->incremental_marking();
marking->Stop();
{
IsolateSafepointScope scope(heap);
heap->tracer()->StartCycle(
GarbageCollector::MARK_COMPACTOR, GarbageCollectionReason::kTesting,
"collector cctest", GCTracer::MarkingType::kIncremental);
marking->Start(GarbageCollector::MARK_COMPACTOR,
i::GarbageCollectionReason::kTesting);
}
CHECK(marking->IsMajorMarking());
while (marking->IsMajorMarking()) {
platform.PerformTask();
}
CHECK(marking->IsStopped());
}
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,379 @@
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include "src/common/globals.h"
#ifdef __linux__
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <utility>
#include "include/v8-locker.h"
#include "src/handles/global-handles.h"
#include "src/heap/live-object-range-inl.h"
#include "src/heap/mark-compact-inl.h"
#include "src/heap/mark-compact.h"
#include "src/heap/marking-inl.h"
#include "src/init/v8.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
TEST(Promotion) {
if (v8_flags.single_generation) return;
v8_flags.stress_concurrent_allocation = false; // For SealCurrentObjects.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
{
v8::HandleScope sc(CcTest::isolate());
Heap* heap = isolate->heap();
heap::SealCurrentObjects(heap);
int array_length = heap::FixedArrayLenFromSize(kMaxRegularHeapObjectSize);
DirectHandle<FixedArray> array =
isolate->factory()->NewFixedArray(array_length);
// Array should be in the new space.
CHECK(heap->InSpace(*array, NEW_SPACE));
heap::InvokeMajorGC(heap);
heap::InvokeMajorGC(heap);
CHECK(heap->InSpace(*array, OLD_SPACE));
}
}
// This is the same as Factory::NewContextfulMapForCurrentContext, except it
// doesn't retry on allocation failure.
AllocationResult HeapTester::AllocateMapForTest(Isolate* isolate) {
Heap* heap = isolate->heap();
Tagged<HeapObject> obj;
AllocationResult alloc = heap->AllocateRaw(Map::kSize, AllocationType::kMap);
if (!alloc.To(&obj)) return alloc;
ReadOnlyRoots roots(isolate);
obj->set_map_after_allocation(isolate, *isolate->meta_map());
return AllocationResult::FromObject(isolate->factory()->InitializeMap(
Cast<Map>(obj), JS_OBJECT_TYPE, JSObject::kHeaderSize,
TERMINAL_FAST_ELEMENTS_KIND, 0, roots));
}
// This is the same as Factory::NewFixedArray, except it doesn't retry
// on allocation failure.
AllocationResult HeapTester::AllocateFixedArrayForTest(
Heap* heap, int length, AllocationType allocation) {
DCHECK(length >= 0 && length <= FixedArray::kMaxLength);
int size = FixedArray::SizeFor(length);
Tagged<HeapObject> obj;
{
AllocationResult result = heap->AllocateRaw(size, allocation);
if (!result.To(&obj)) return result;
}
obj->set_map_after_allocation(heap->isolate(),
ReadOnlyRoots(heap).fixed_array_map(),
SKIP_WRITE_BARRIER);
Tagged<FixedArray> array = Cast<FixedArray>(obj);
array->set_length(length);
MemsetTagged(array->RawFieldOfFirstElement(),
ReadOnlyRoots(heap).undefined_value(), length);
return AllocationResult::FromObject(array);
}
HEAP_TEST(MarkCompactCollector) {
v8_flags.incremental_marking = false;
v8_flags.retain_maps_for_n_gc = 0;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = CcTest::heap();
Factory* factory = isolate->factory();
v8::HandleScope sc(CcTest::isolate());
DirectHandle<JSGlobalObject> global(isolate->context()->global_object(),
isolate);
// call mark-compact when heap is empty
heap::InvokeMajorGC(heap);
AllocationResult allocation;
if (!v8_flags.single_generation) {
// keep allocating garbage in new space until it fails
const int arraysize = 100;
do {
allocation =
AllocateFixedArrayForTest(heap, arraysize, AllocationType::kYoung);
} while (!allocation.IsFailure());
heap::InvokeMinorGC(heap);
AllocateFixedArrayForTest(heap, arraysize, AllocationType::kYoung)
.ToObjectChecked();
}
// keep allocating maps until it fails
do {
allocation = AllocateMapForTest(isolate);
} while (!allocation.IsFailure());
heap::InvokeMajorGC(heap);
AllocateMapForTest(isolate).ToObjectChecked();
{ HandleScope scope(isolate);
// allocate a garbage
DirectHandle<String> func_name =
factory->InternalizeUtf8String("theFunction");
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(func_name);
Object::SetProperty(isolate, global, func_name, function).Check();
factory->NewJSObject(function);
}
heap::InvokeMajorGC(heap);
{ HandleScope scope(isolate);
DirectHandle<String> func_name =
factory->InternalizeUtf8String("theFunction");
CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, global, func_name));
DirectHandle<Object> func_value =
Object::GetProperty(isolate, global, func_name).ToHandleChecked();
CHECK(IsJSFunction(*func_value));
DirectHandle<JSFunction> function = Cast<JSFunction>(func_value);
DirectHandle<JSObject> obj = factory->NewJSObject(function);
DirectHandle<String> obj_name = factory->InternalizeUtf8String("theObject");
Object::SetProperty(isolate, global, obj_name, obj).Check();
DirectHandle<String> prop_name = factory->InternalizeUtf8String("theSlot");
DirectHandle<Smi> twenty_three(Smi::FromInt(23), isolate);
Object::SetProperty(isolate, obj, prop_name, twenty_three).Check();
}
heap::InvokeMajorGC(heap);
{ HandleScope scope(isolate);
DirectHandle<String> obj_name = factory->InternalizeUtf8String("theObject");
CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, global, obj_name));
DirectHandle<Object> object =
Object::GetProperty(isolate, global, obj_name).ToHandleChecked();
CHECK(IsJSObject(*object));
DirectHandle<String> prop_name = factory->InternalizeUtf8String("theSlot");
CHECK_EQ(*Object::GetProperty(isolate, Cast<JSObject>(object), prop_name)
.ToHandleChecked(),
Smi::FromInt(23));
}
}
HEAP_TEST(DoNotEvacuatePinnedPages) {
if (!v8_flags.compact || !v8_flags.single_generation) return;
v8_flags.compact_on_every_full_gc = true;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
v8::HandleScope sc(CcTest::isolate());
Heap* heap = isolate->heap();
heap::SealCurrentObjects(heap);
DirectHandleVector<FixedArray> handles(isolate);
heap::CreatePadding(
heap, static_cast<int>(MemoryChunkLayout::AllocatableMemoryInDataPage()),
AllocationType::kOld, &handles);
MemoryChunk* chunk = MemoryChunk::FromHeapObject(*handles.front());
CHECK(heap->InSpace(*handles.front(), OLD_SPACE));
chunk->SetFlagNonExecutable(MemoryChunk::PINNED);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(Heap::SweepingForcedFinalizationMode::kV8Only);
// The pinned flag should prevent the page from moving.
for (DirectHandle<FixedArray> object : handles) {
CHECK_EQ(chunk, MemoryChunk::FromHeapObject(*object));
}
chunk->ClearFlagNonExecutable(MemoryChunk::PINNED);
heap::InvokeMajorGC(heap);
heap->EnsureSweepingCompleted(Heap::SweepingForcedFinalizationMode::kV8Only);
// `compact_on_every_full_gc` ensures that this page is an evacuation
// candidate, so with the pin flag cleared compaction should now move it.
for (DirectHandle<FixedArray> object : handles) {
CHECK_NE(chunk, MemoryChunk::FromHeapObject(*object));
}
}
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define V8_WITH_ASAN 1
#endif
#endif
// Here is a memory use test that uses /proc, and is therefore Linux-only. We
// do not care how much memory the simulator uses, since it is only there for
// debugging purposes. Testing with ASAN doesn't make sense, either.
#if defined(__linux__) && !defined(USE_SIMULATOR) && !defined(V8_WITH_ASAN)
static uintptr_t ReadLong(char* buffer, intptr_t* position, int base) {
char* end_address = buffer + *position;
uintptr_t result = strtoul(buffer + *position, &end_address, base);
CHECK(result != ULONG_MAX || errno != ERANGE);
CHECK(end_address > buffer + *position);
*position = end_address - buffer;
return result;
}
// The memory use computed this way is not entirely accurate and depends on
// the way malloc allocates memory. That's why the memory use may seem to
// increase even though the sum of the allocated object sizes decreases. It
// also means that the memory use depends on the kernel and stdlib.
static intptr_t MemoryInUse() {
intptr_t memory_use = 0;
int fd = open("/proc/self/maps", O_RDONLY);
if (fd < 0) return -1;
const int kBufSize = 20000;
char buffer[kBufSize];
ssize_t length = read(fd, buffer, kBufSize);
intptr_t line_start = 0;
CHECK_LT(length, kBufSize); // Make the buffer bigger.
CHECK_GT(length, 0); // We have to find some data in the file.
while (line_start < length) {
if (buffer[line_start] == '\n') {
line_start++;
continue;
}
intptr_t position = line_start;
uintptr_t start = ReadLong(buffer, &position, 16);
CHECK_EQ(buffer[position++], '-');
uintptr_t end = ReadLong(buffer, &position, 16);
CHECK_EQ(buffer[position++], ' ');
CHECK(buffer[position] == '-' || buffer[position] == 'r');
bool read_permission = (buffer[position++] == 'r');
CHECK(buffer[position] == '-' || buffer[position] == 'w');
bool write_permission = (buffer[position++] == 'w');
CHECK(buffer[position] == '-' || buffer[position] == 'x');
bool execute_permission = (buffer[position++] == 'x');
CHECK(buffer[position] == 's' || buffer[position] == 'p');
bool private_mapping = (buffer[position++] == 'p');
CHECK_EQ(buffer[position++], ' ');
uintptr_t offset = ReadLong(buffer, &position, 16);
USE(offset);
CHECK_EQ(buffer[position++], ' ');
uintptr_t major = ReadLong(buffer, &position, 16);
USE(major);
CHECK_EQ(buffer[position++], ':');
uintptr_t minor = ReadLong(buffer, &position, 16);
USE(minor);
CHECK_EQ(buffer[position++], ' ');
uintptr_t inode = ReadLong(buffer, &position, 10);
while (position < length && buffer[position] != '\n') position++;
if ((read_permission || write_permission || execute_permission) &&
private_mapping && inode == 0) {
memory_use += (end - start);
}
line_start = position;
}
close(fd);
return memory_use;
}
intptr_t ShortLivingIsolate() {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{ v8::Isolate::Scope isolate_scope(isolate);
v8::Locker lock(isolate);
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
CHECK(!context.IsEmpty());
}
isolate->Dispose();
return MemoryInUse();
}
UNINITIALIZED_TEST(RegressJoinThreadsOnIsolateDeinit) {
// Memory is measured, do not allocate in background thread.
v8_flags.stress_concurrent_allocation = false;
intptr_t size_limit = ShortLivingIsolate() * 2;
for (int i = 0; i < 10; i++) {
CHECK_GT(size_limit, ShortLivingIsolate());
}
}
TEST(Regress5829) {
if (!v8_flags.incremental_marking) return;
v8_flags.stress_concurrent_allocation = false; // For SealCurrentObjects.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
v8::HandleScope sc(CcTest::isolate());
Heap* heap = isolate->heap();
heap::SealCurrentObjects(heap);
i::IncrementalMarking* marking = heap->incremental_marking();
if (heap->sweeping_in_progress()) {
heap->EnsureSweepingCompleted(
Heap::SweepingForcedFinalizationMode::kV8Only);
}
CHECK(marking->IsMarking() || marking->IsStopped());
if (marking->IsStopped()) {
heap->StartIncrementalMarking(i::GCFlag::kNoFlags,
i::GarbageCollectionReason::kTesting);
}
CHECK(marking->IsMarking());
CHECK(marking->black_allocation());
DirectHandle<FixedArray> array =
isolate->factory()->NewFixedArray(10, AllocationType::kOld);
Address old_end = array->address() + array->Size();
// Right trim the array without clearing the mark bits.
array->set_length(9);
heap->CreateFillerObjectAt(old_end - kTaggedSize, kTaggedSize);
heap->FreeMainThreadLinearAllocationAreas();
PageMetadata* page = PageMetadata::FromAddress(array->address());
for (auto object_and_size : LiveObjectRange(page)) {
CHECK(!IsFreeSpaceOrFiller(object_and_size.first));
}
}
#endif // __linux__ and !USE_SIMULATOR
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,289 @@
// Copyright 2019 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/memory-measurement-inl.h"
#include "src/heap/memory-measurement.h"
#include "src/objects/smi.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
namespace {
DirectHandle<NativeContext> GetNativeContext(
Isolate* isolate, v8::Local<v8::Context> v8_context) {
DirectHandle<Context> context = v8::Utils::OpenDirectHandle(*v8_context);
return direct_handle(context->native_context(), isolate);
}
} // anonymous namespace
TEST(NativeContextInferrerGlobalObject) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope handle_scope(isolate);
DirectHandle<NativeContext> native_context =
GetNativeContext(isolate, env.local());
DirectHandle<JSGlobalObject> global(native_context->global_object(), isolate);
NativeContextInferrer inferrer;
Address inferred_context = 0;
CHECK(inferrer.Infer(isolate, global->map(), *global, &inferred_context));
CHECK_EQ(native_context->ptr(), inferred_context);
}
TEST(NativeContextInferrerJSFunction) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
DirectHandle<NativeContext> native_context =
GetNativeContext(isolate, env.local());
v8::Local<v8::Value> result = CompileRun("(function () { return 1; })");
DirectHandle<Object> object = Utils::OpenDirectHandle(*result);
DirectHandle<HeapObject> function = Cast<HeapObject>(object);
NativeContextInferrer inferrer;
Address inferred_context = 0;
CHECK(inferrer.Infer(isolate, function->map(), *function, &inferred_context));
CHECK_EQ(native_context->ptr(), inferred_context);
}
TEST(NativeContextInferrerJSObject) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
DirectHandle<NativeContext> native_context =
GetNativeContext(isolate, env.local());
v8::Local<v8::Value> result = CompileRun("({a : 10})");
DirectHandle<Object> object = Utils::OpenDirectHandle(*result);
DirectHandle<HeapObject> function = Cast<HeapObject>(object);
NativeContextInferrer inferrer;
Address inferred_context = 0;
CHECK(inferrer.Infer(isolate, function->map(), *function, &inferred_context));
CHECK_EQ(native_context->ptr(), inferred_context);
}
TEST(NativeContextStatsMerge) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
DirectHandle<NativeContext> native_context =
GetNativeContext(isolate, env.local());
v8::Local<v8::Value> result = CompileRun("({a : 10})");
DirectHandle<HeapObject> object =
Cast<HeapObject>(Utils::OpenDirectHandle(*result));
NativeContextStats stats1, stats2;
stats1.IncrementSize(native_context->ptr(), object->map(), *object, 10);
stats2.IncrementSize(native_context->ptr(), object->map(), *object, 20);
stats1.Merge(stats2);
CHECK_EQ(30, stats1.Get(native_context->ptr()));
}
TEST(NativeContextStatsArrayBuffers) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
DirectHandle<NativeContext> native_context =
GetNativeContext(isolate, env.local());
v8::Local<v8::ArrayBuffer> array_buffer =
v8::ArrayBuffer::New(CcTest::isolate(), 1000);
DirectHandle<JSArrayBuffer> i_array_buffer =
Utils::OpenDirectHandle(*array_buffer);
NativeContextStats stats;
stats.IncrementSize(native_context->ptr(), i_array_buffer->map(),
*i_array_buffer, 10);
CHECK_EQ(1010, stats.Get(native_context->ptr()));
}
namespace {
class TestResource : public v8::String::ExternalStringResource {
public:
explicit TestResource(uint16_t* data) : data_(data), length_(0) {
while (data[length_]) ++length_;
}
~TestResource() override { i::DeleteArray(data_); }
const uint16_t* data() const override { return data_; }
size_t length() const override { return length_; }
private:
uint16_t* data_;
size_t length_;
};
} // anonymous namespace
TEST(NativeContextStatsExternalString) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
DirectHandle<NativeContext> native_context =
GetNativeContext(isolate, env.local());
const char* c_source = "0123456789";
uint16_t* two_byte_source = AsciiToTwoByteString(c_source);
TestResource* resource = new TestResource(two_byte_source);
Local<v8::String> string =
v8::String::NewExternalTwoByte(CcTest::isolate(), resource)
.ToLocalChecked();
DirectHandle<String> i_string = Utils::OpenDirectHandle(*string);
NativeContextStats stats;
stats.IncrementSize(native_context->ptr(), i_string->map(), *i_string, 10);
CHECK_EQ(10 + 10 * 2, stats.Get(native_context->ptr()));
}
namespace {
class MockPlatform : public TestPlatform {
public:
MockPlatform() : mock_task_runner_(new MockTaskRunner()) {}
std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(
v8::Isolate*, v8::TaskPriority priority) override {
return mock_task_runner_;
}
double Delay() { return mock_task_runner_->Delay(); }
void PerformTask() { mock_task_runner_->PerformTask(); }
bool TaskPosted() { return mock_task_runner_->TaskPosted(); }
private:
class MockTaskRunner : public v8::TaskRunner {
public:
void PostTaskImpl(std::unique_ptr<v8::Task> task,
const SourceLocation&) override {}
void PostDelayedTaskImpl(std::unique_ptr<Task> task,
double delay_in_seconds,
const SourceLocation&) override {
task_ = std::move(task);
delay_ = delay_in_seconds;
}
void PostIdleTaskImpl(std::unique_ptr<IdleTask> task,
const SourceLocation&) override {
UNREACHABLE();
}
bool NonNestableTasksEnabled() const override { return true; }
bool NonNestableDelayedTasksEnabled() const override { return true; }
bool IdleTasksEnabled() override { return false; }
double Delay() { return delay_; }
void PerformTask() {
std::unique_ptr<Task> task = std::move(task_);
task->Run();
}
bool TaskPosted() { return task_.get(); }
private:
double delay_ = -1;
std::unique_ptr<Task> task_;
};
std::shared_ptr<MockTaskRunner> mock_task_runner_;
};
class MockMeasureMemoryDelegate : public v8::MeasureMemoryDelegate {
public:
bool ShouldMeasure(v8::Local<v8::Context> context) override { return true; }
void MeasurementComplete(Result result) override {
// Empty.
}
};
} // namespace
TEST_WITH_PLATFORM(RandomizedTimeout, MockPlatform) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = CcTest::isolate();
std::vector<double> delays;
for (int i = 0; i < 10; i++) {
isolate->MeasureMemory(std::make_unique<MockMeasureMemoryDelegate>());
delays.push_back(platform.Delay());
platform.PerformTask();
}
std::sort(delays.begin(), delays.end());
CHECK_LT(delays[0], delays.back());
}
TEST(LazyMemoryMeasurement) {
CcTest::InitializeVM();
MockPlatform platform;
CcTest::isolate()->MeasureMemory(
std::make_unique<MockMeasureMemoryDelegate>(),
v8::MeasureMemoryExecution::kLazy);
CHECK(!platform.TaskPosted());
}
TEST(PartiallyInitializedJSFunction) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
DirectHandle<JSFunction> js_function = factory->NewFunctionForTesting(
factory->NewStringFromAsciiChecked("test"));
DirectHandle<Context> context(js_function->context(), isolate);
// 1. Start simulating deserializaiton.
isolate->RegisterDeserializerStarted();
// 2. Set the context field to the uninitialized sentintel.
TaggedField<Object, JSFunction::kContextOffset>::store(
*js_function, Smi::uninitialized_deserialization_value());
// 3. Request memory measurement and run all tasks. GC that runs as part
// of the measurement should not crash.
CcTest::isolate()->MeasureMemory(
std::make_unique<MockMeasureMemoryDelegate>(),
v8::MeasureMemoryExecution::kEager);
while (v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(),
CcTest::isolate())) {
}
// 4. Restore the value and complete deserialization.
TaggedField<Object, JSFunction::kContextOffset>::store(*js_function,
*context);
isolate->RegisterDeserializerFinished();
}
TEST(PartiallyInitializedContext) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
DirectHandle<ScopeInfo> scope_info =
factory->global_this_binding_scope_info();
DirectHandle<Context> context = factory->NewScriptContext(
GetNativeContext(isolate, env.local()), scope_info);
DirectHandle<Map> map(context->map(), isolate);
DirectHandle<NativeContext> native_context(map->native_context(), isolate);
// 1. Start simulating deserializaiton.
isolate->RegisterDeserializerStarted();
// 2. Set the native context field to the uninitialized sentintel.
TaggedField<Object, Map::kConstructorOrBackPointerOrNativeContextOffset>::
store(*map, Smi::uninitialized_deserialization_value());
// 3. Request memory measurement and run all tasks. GC that runs as part
// of the measurement should not crash.
CcTest::isolate()->MeasureMemory(
std::make_unique<MockMeasureMemoryDelegate>(),
v8::MeasureMemoryExecution::kEager);
while (v8::platform::PumpMessageLoop(v8::internal::V8::GetCurrentPlatform(),
CcTest::isolate())) {
}
// 4. Restore the value and complete deserialization.
TaggedField<Object, Map::kConstructorOrBackPointerOrNativeContextOffset>::
store(*map, *native_context);
isolate->RegisterDeserializerFinished();
}
} // namespace heap
} // namespace internal
} // namespace v8

66
deps/v8/test/cctest/heap/test-pool.cc vendored Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2018 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 <vector>
#include "src/heap/heap.h"
#include "src/heap/memory-allocator.h"
#include "src/init/v8.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
using v8::IdleTask;
using v8::Task;
using v8::Isolate;
namespace v8 {
namespace internal {
namespace heap {
class MockPlatformForPool : public TestPlatform {
public:
~MockPlatformForPool() override {
for (auto& task : worker_tasks_) {
CcTest::default_platform()->PostTaskOnWorkerThread(
TaskPriority::kUserVisible, std::move(task));
}
worker_tasks_.clear();
}
void PostTaskOnWorkerThreadImpl(TaskPriority priority,
std::unique_ptr<Task> task,
const SourceLocation& location) override {
worker_tasks_.push_back(std::move(task));
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return false; }
private:
std::vector<std::unique_ptr<Task>> worker_tasks_;
};
UNINITIALIZED_TEST(EagerDiscardingInCollectAllAvailableGarbage) {
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
MockPlatformForPool platform;
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = CcTest::NewContext(isolate);
v8::Context::Scope context_scope(context);
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
Heap* heap = i_isolate->heap();
i::heap::SimulateFullSpace(heap->old_space());
i::heap::InvokeMemoryReducingMajorGCs(heap);
CHECK_EQ(0, heap->memory_allocator()->GetPooledChunksCount());
}
isolate->Dispose();
}
} // namespace heap
} // namespace internal
} // namespace v8

930
deps/v8/test/cctest/heap/test-spaces.cc vendored Normal file
View File

@ -0,0 +1,930 @@
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include <memory>
#include "include/v8-initialization.h"
#include "include/v8-platform.h"
#include "src/base/bounded-page-allocator.h"
#include "src/base/macros.h"
#include "src/base/platform/platform.h"
#include "src/common/globals.h"
#include "src/heap/allocation-result.h"
#include "src/heap/factory.h"
#include "src/heap/heap.h"
#include "src/heap/large-spaces.h"
#include "src/heap/main-allocator.h"
#include "src/heap/memory-allocator.h"
#include "src/heap/mutable-page-metadata.h"
#include "src/heap/page-pool.h"
#include "src/heap/spaces-inl.h"
#include "src/heap/spaces.h"
#include "src/objects/free-space.h"
#include "src/objects/objects-inl.h"
#include "src/snapshot/snapshot.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
// Temporarily sets a given allocator in an isolate.
class V8_NODISCARD TestMemoryAllocatorScope {
public:
TestMemoryAllocatorScope(Isolate* isolate, size_t max_capacity,
PageAllocator* page_allocator = nullptr)
: isolate_(isolate),
old_allocator_(std::move(isolate->heap()->memory_allocator_)) {
// Save the code pages for restoring them later on because the constructor
// of MemoryAllocator will change them.
isolate->GetCodePages()->swap(code_pages_);
isolate->heap()->memory_allocator_.reset(new MemoryAllocator(
isolate,
page_allocator != nullptr ? page_allocator : isolate->page_allocator(),
page_allocator != nullptr ? page_allocator : isolate->page_allocator(),
max_capacity));
if (page_allocator != nullptr) {
isolate->heap()->memory_allocator_->data_page_allocator_ = page_allocator;
}
}
MemoryAllocator* allocator() { return isolate_->heap()->memory_allocator(); }
~TestMemoryAllocatorScope() {
isolate_->heap()->memory_allocator()->ReleasePooledChunksImmediately();
isolate_->heap()->memory_allocator()->TearDown();
isolate_->heap()->memory_allocator_.swap(old_allocator_);
isolate_->GetCodePages()->swap(code_pages_);
}
TestMemoryAllocatorScope(const TestMemoryAllocatorScope&) = delete;
TestMemoryAllocatorScope& operator=(const TestMemoryAllocatorScope&) = delete;
private:
Isolate* isolate_;
std::unique_ptr<MemoryAllocator> old_allocator_;
std::vector<MemoryRange> code_pages_;
};
// Temporarily sets a given code page allocator in an isolate.
class V8_NODISCARD TestCodePageAllocatorScope {
public:
TestCodePageAllocatorScope(Isolate* isolate,
v8::PageAllocator* code_page_allocator)
: isolate_(isolate),
old_code_page_allocator_(
isolate->heap()->memory_allocator()->code_page_allocator()) {
isolate->heap()->memory_allocator()->code_page_allocator_ =
code_page_allocator;
}
~TestCodePageAllocatorScope() {
isolate_->heap()->memory_allocator()->code_page_allocator_ =
old_code_page_allocator_;
}
TestCodePageAllocatorScope(const TestCodePageAllocatorScope&) = delete;
TestCodePageAllocatorScope& operator=(const TestCodePageAllocatorScope&) =
delete;
private:
Isolate* isolate_;
v8::PageAllocator* old_code_page_allocator_;
};
static void VerifyMemoryChunk(Isolate* isolate, Heap* heap,
v8::PageAllocator* code_page_allocator,
size_t area_size, Executability executable,
PageSize page_size, LargeObjectSpace* space) {
TestMemoryAllocatorScope test_allocator_scope(isolate, heap->MaxReserved());
MemoryAllocator* memory_allocator = test_allocator_scope.allocator();
TestCodePageAllocatorScope test_code_page_allocator_scope(
isolate, code_page_allocator);
v8::PageAllocator* page_allocator =
memory_allocator->page_allocator(space->identity());
size_t allocatable_memory_area_offset =
MemoryChunkLayout::ObjectStartOffsetInMemoryChunk(space->identity());
MutablePageMetadata* memory_chunk =
memory_allocator->AllocateLargePage(space, area_size, executable);
size_t reserved_size =
((executable == EXECUTABLE))
? RoundUp(allocatable_memory_area_offset +
RoundUp(area_size, page_allocator->CommitPageSize()),
page_allocator->CommitPageSize())
: RoundUp(allocatable_memory_area_offset + area_size,
page_allocator->CommitPageSize());
CHECK(memory_chunk->size() == reserved_size);
CHECK(memory_chunk->area_start() <
memory_chunk->ChunkAddress() + memory_chunk->size());
CHECK(memory_chunk->area_end() <=
memory_chunk->ChunkAddress() + memory_chunk->size());
CHECK(static_cast<size_t>(memory_chunk->area_size()) == area_size);
memory_allocator->Free(MemoryAllocator::FreeMode::kImmediately, memory_chunk);
}
static unsigned int PseudorandomAreaSize() {
static uint32_t lo = 2345;
lo = 18273 * (lo & 0xFFFFF) + (lo >> 16);
return lo & 0xFFFFF;
}
TEST(MutablePageMetadata) {
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
IsolateSafepointScope safepoint(heap);
v8::PageAllocator* page_allocator = GetPlatformPageAllocator();
size_t area_size;
for (int i = 0; i < 100; i++) {
area_size =
RoundUp(PseudorandomAreaSize(), page_allocator->CommitPageSize());
const size_t code_range_size = 32 * MB;
#ifdef V8_ENABLE_SANDBOX
// When the sandbox is enabled, the code assumes that there's only a single
// code range for easy metadata lookup, so use the process wide code range
// in this case.
CodeRange* code_range =
IsolateGroup::current()->EnsureCodeRange(code_range_size);
base::BoundedPageAllocator* bounded_page_allocator =
code_range->page_allocator();
#else
// With CodeRange.
bool jitless = isolate->jitless();
VirtualMemory code_range_reservation(
page_allocator, code_range_size, nullptr,
MemoryChunk::GetAlignmentForAllocation(),
jitless ? PageAllocator::Permission::kNoAccess
: PageAllocator::Permission::kNoAccessWillJitLater);
base::PageInitializationMode page_initialization_mode =
base::PageInitializationMode::kAllocatedPagesCanBeUninitialized;
base::PageFreeingMode page_freeing_mode =
base::PageFreeingMode::kMakeInaccessible;
if (!jitless) {
page_initialization_mode = base::PageInitializationMode::kRecommitOnly;
page_freeing_mode = base::PageFreeingMode::kDiscard;
void* base = reinterpret_cast<void*>(code_range_reservation.address());
CHECK(page_allocator->SetPermissions(base, code_range_size,
PageAllocator::kReadWriteExecute));
CHECK(page_allocator->DiscardSystemPages(base, code_range_size));
}
CHECK(code_range_reservation.IsReserved());
base::BoundedPageAllocator code_page_allocator(
page_allocator, code_range_reservation.address(),
code_range_reservation.size(), MemoryChunk::GetAlignmentForAllocation(),
page_initialization_mode, page_freeing_mode);
base::BoundedPageAllocator* bounded_page_allocator = &code_page_allocator;
#endif
VerifyMemoryChunk(isolate, heap, bounded_page_allocator, area_size,
EXECUTABLE, PageSize::kLarge, heap->code_lo_space());
VerifyMemoryChunk(isolate, heap, bounded_page_allocator, area_size,
NOT_EXECUTABLE, PageSize::kLarge, heap->lo_space());
}
}
TEST(MemoryAllocator) {
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
TestMemoryAllocatorScope test_allocator_scope(isolate, heap->MaxReserved());
MemoryAllocator* memory_allocator = test_allocator_scope.allocator();
int total_pages = 0;
OldSpace faked_space(heap);
CHECK(!faked_space.first_page());
CHECK(!faked_space.last_page());
PageMetadata* first_page = memory_allocator->AllocatePage(
MemoryAllocator::AllocationMode::kRegular,
static_cast<PagedSpace*>(&faked_space), NOT_EXECUTABLE);
faked_space.memory_chunk_list().PushBack(first_page);
CHECK(first_page->next_page() == nullptr);
total_pages++;
for (PageMetadata* p = first_page; p != nullptr; p = p->next_page()) {
CHECK(p->owner() == &faked_space);
}
// Again, we should get n or n - 1 pages.
PageMetadata* other = memory_allocator->AllocatePage(
MemoryAllocator::AllocationMode::kRegular,
static_cast<PagedSpace*>(&faked_space), NOT_EXECUTABLE);
total_pages++;
faked_space.memory_chunk_list().PushBack(other);
int page_count = 0;
for (PageMetadata* p = first_page; p != nullptr; p = p->next_page()) {
CHECK(p->owner() == &faked_space);
page_count++;
}
CHECK(total_pages == page_count);
PageMetadata* second_page = first_page->next_page();
CHECK_NOT_NULL(second_page);
// OldSpace's destructor will tear down the space and free up all pages.
}
TEST(ComputeDiscardMemoryAreas) {
std::optional<base::AddressRegion> discard_area;
size_t page_size = MemoryAllocator::GetCommitPageSize();
discard_area = Sweeper::ComputeDiscardMemoryArea(0, 0);
CHECK(!discard_area);
discard_area = Sweeper::ComputeDiscardMemoryArea(0, page_size);
CHECK_EQ(discard_area->begin(), 0);
CHECK_EQ(discard_area->size(), page_size);
discard_area = Sweeper::ComputeDiscardMemoryArea(page_size, 2 * page_size);
CHECK_EQ(discard_area->begin(), page_size);
CHECK_EQ(discard_area->size(), page_size);
discard_area =
Sweeper::ComputeDiscardMemoryArea(page_size - kTaggedSize, 2 * page_size);
CHECK_EQ(discard_area->begin(), page_size);
CHECK_EQ(discard_area->size(), page_size);
discard_area =
Sweeper::ComputeDiscardMemoryArea(page_size, 2 * page_size + kTaggedSize);
CHECK_EQ(discard_area->begin(), page_size);
CHECK_EQ(discard_area->size(), page_size);
discard_area = Sweeper::ComputeDiscardMemoryArea(page_size, page_size);
CHECK(!discard_area);
discard_area = Sweeper::ComputeDiscardMemoryArea(page_size / 2,
page_size + page_size / 2);
CHECK(!discard_area);
discard_area = Sweeper::ComputeDiscardMemoryArea(page_size / 2,
page_size + page_size / 4);
CHECK(!discard_area);
discard_area =
Sweeper::ComputeDiscardMemoryArea(page_size / 2, page_size * 3);
CHECK_EQ(discard_area->begin(), page_size);
CHECK_EQ(discard_area->size(), page_size * 2);
}
TEST(SemiSpaceNewSpace) {
if (v8_flags.single_generation) return;
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
TestMemoryAllocatorScope test_allocator_scope(isolate, heap->MaxReserved());
MemoryAllocator* memory_allocator = test_allocator_scope.allocator();
LinearAllocationArea allocation_info;
auto new_space = std::make_unique<SemiSpaceNewSpace>(
heap, heap->InitialSemiSpaceSize(), heap->InitialSemiSpaceSize(),
heap->InitialSemiSpaceSize());
MainAllocator allocator(heap->main_thread_local_heap(), new_space.get(),
MainAllocator::IsNewGeneration::kYes,
&allocation_info);
CHECK(new_space->MaximumCapacity());
size_t successful_allocations = 0;
while (new_space->Available() >= kMaxRegularHeapObjectSize) {
AllocationResult allocation = allocator.AllocateRaw(
kMaxRegularHeapObjectSize, kTaggedAligned, AllocationOrigin::kRuntime);
if (allocation.IsFailure()) break;
successful_allocations++;
Tagged<Object> obj = allocation.ToObjectChecked();
Tagged<HeapObject> ho = Cast<HeapObject>(obj);
CHECK(new_space->Contains(ho));
}
CHECK_LT(0, successful_allocations);
new_space.reset();
memory_allocator->ReleasePooledChunksImmediately();
}
TEST(PagedNewSpace) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
TestMemoryAllocatorScope test_allocator_scope(isolate, heap->MaxReserved());
MemoryAllocator* memory_allocator = test_allocator_scope.allocator();
LinearAllocationArea allocation_info;
auto new_space = std::make_unique<PagedNewSpace>(
heap, heap->InitialSemiSpaceSize(), heap->InitialSemiSpaceSize(),
heap->InitialSemiSpaceSize());
MainAllocator allocator(heap->main_thread_local_heap(), new_space.get(),
MainAllocator::IsNewGeneration::kYes,
&allocation_info);
CHECK(new_space->MaximumCapacity());
CHECK(new_space->EnsureCurrentCapacity());
CHECK_LT(0, new_space->TotalCapacity());
size_t successful_allocations = 0;
while (true) {
AllocationResult allocation = allocator.AllocateRaw(
kMaxRegularHeapObjectSize, kTaggedAligned, AllocationOrigin::kRuntime);
if (allocation.IsFailure()) break;
successful_allocations++;
Tagged<Object> obj = allocation.ToObjectChecked();
Tagged<HeapObject> ho = Cast<HeapObject>(obj);
CHECK(new_space->Contains(ho));
}
CHECK_LT(0, successful_allocations);
new_space.reset();
memory_allocator->ReleasePooledChunksImmediately();
}
TEST(OldSpace) {
v8_flags.max_heap_size = 20;
// This test uses its own old space, which confuses the incremental marker.
v8_flags.incremental_marking = false;
// This test doesn't expect GCs caused by concurrent allocations in the
// background thread.
v8_flags.stress_concurrent_allocation = false;
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
TestMemoryAllocatorScope test_allocator_scope(isolate, heap->MaxReserved());
LinearAllocationArea allocation_info;
auto old_space = std::make_unique<OldSpace>(heap);
MainAllocator allocator(heap->main_thread_local_heap(), old_space.get(),
MainAllocator::IsNewGeneration::kNo,
&allocation_info);
const int obj_size = kMaxRegularHeapObjectSize;
size_t successful_allocations = 0;
while (true) {
AllocationResult allocation = allocator.AllocateRaw(
obj_size, kTaggedAligned, AllocationOrigin::kRuntime);
if (allocation.IsFailure()) break;
successful_allocations++;
Tagged<Object> obj = allocation.ToObjectChecked();
Tagged<HeapObject> ho = Cast<HeapObject>(obj);
CHECK(old_space->Contains(ho));
}
CHECK_LT(0, successful_allocations);
}
TEST(OldLargeObjectSpace) {
v8_flags.max_heap_size = 20;
// This test uses its own old large object space, which confuses the
// incremental marker.
v8_flags.incremental_marking = false;
// This test doesn't expect GCs caused by concurrent allocations in the
// background thread.
v8_flags.stress_concurrent_allocation = false;
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
auto lo = std::make_unique<OldLargeObjectSpace>(heap);
const int lo_size = PageMetadata::kPageSize;
HandleScope handle_scope(isolate);
Tagged<Map> map = ReadOnlyRoots(isolate).fixed_double_array_map();
size_t successful_allocations = 0;
while (true) {
AllocationResult allocation =
lo->AllocateRaw(heap->main_thread_local_heap(), lo_size);
if (allocation.IsFailure()) break;
successful_allocations++;
Tagged<Object> obj = allocation.ToObjectChecked();
CHECK(IsHeapObject(obj));
Tagged<HeapObject> ho = Cast<HeapObject>(obj);
CHECK(lo->Contains(ho));
CHECK_EQ(0, Heap::GetFillToAlign(ho.address(), kTaggedAligned));
// All large objects have the same alignment because they start at the
// same offset within a page. Fixed double arrays have the most strict
// alignment requirements.
CHECK_EQ(0, Heap::GetFillToAlign(ho.address(),
HeapObject::RequiredAlignment(map)));
DirectHandle<HeapObject> keep_alive(ho, isolate);
}
CHECK_LT(0, successful_allocations);
CHECK(!lo->IsEmpty());
CHECK(lo->AllocateRaw(heap->main_thread_local_heap(), lo_size).IsFailure());
}
#ifndef DEBUG
// The test verifies that committed size of a space is less then some threshold.
// Debug builds pull in all sorts of additional instrumentation that increases
// heap sizes. E.g. CSA_DCHECK creates on-heap strings for error messages. These
// messages are also not stable if files are moved and modified during the build
// process (jumbo builds).
TEST(SizeOfInitialHeap) {
ManualGCScope manual_gc_scope;
if (i::v8_flags.always_turbofan) return;
// Bootstrapping without a snapshot causes more allocations.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
if (!isolate->snapshot_available()) return;
HandleScope scope(isolate);
v8::Local<v8::Context> context = CcTest::isolate()->GetCurrentContext();
// Skip this test on the custom snapshot builder.
if (!CcTest::global()
->Get(context, v8_str("assertEquals"))
.ToLocalChecked()
->IsUndefined()) {
return;
}
// Initial size of LO_SPACE
size_t initial_lo_space = isolate->heap()->lo_space()->Size();
// The limit for each space for an empty isolate containing just the
// snapshot.
// In PPC the page size is 64K, causing more internal fragmentation
// hence requiring a larger limit.
#if V8_OS_LINUX && V8_HOST_ARCH_PPC64
const size_t kMaxInitialSizePerSpace = 3 * MB;
#else
const size_t kMaxInitialSizePerSpace = 2 * MB;
#endif
// Freshly initialized VM gets by with the snapshot size (which is below
// kMaxInitialSizePerSpace per space).
Heap* heap = isolate->heap();
for (int i = FIRST_GROWABLE_PAGED_SPACE; i <= LAST_GROWABLE_PAGED_SPACE;
i++) {
if (!heap->paged_space(i)) continue;
// Debug code can be very large, so skip CODE_SPACE if we are generating it.
if (i == CODE_SPACE && i::v8_flags.debug_code) continue;
// Check that the initial heap is also below the limit.
CHECK_LE(heap->paged_space(i)->CommittedMemory(), kMaxInitialSizePerSpace);
}
CompileRun("/*empty*/");
// No large objects required to perform the above steps.
CHECK_EQ(initial_lo_space,
static_cast<size_t>(isolate->heap()->lo_space()->Size()));
}
#endif // DEBUG
class Observer : public AllocationObserver {
public:
explicit Observer(intptr_t step_size)
: AllocationObserver(step_size), count_(0) {}
void Step(int bytes_allocated, Address addr, size_t) override { count_++; }
int count() const { return count_; }
private:
int count_;
};
HEAP_TEST(Regress777177) {
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
HandleScope scope(isolate);
OldSpace* old_space = heap->old_space();
MainAllocator* old_space_allocator = heap->allocator()->old_space_allocator();
Observer observer(128);
old_space_allocator->FreeLinearAllocationArea();
old_space_allocator->AddAllocationObserver(&observer);
int area_size = old_space->AreaSize();
int max_object_size = kMaxRegularHeapObjectSize;
int filler_size = area_size - max_object_size;
{
// Ensure a new linear allocation area on a fresh page.
AlwaysAllocateScopeForTesting always_allocate(heap);
heap::SimulateFullSpace(old_space);
AllocationResult result = old_space_allocator->AllocateRaw(
filler_size, kTaggedAligned, AllocationOrigin::kRuntime);
Tagged<HeapObject> obj = result.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), filler_size);
}
{
// Allocate all bytes of the linear allocation area. This moves top_ and
// top_on_previous_step_ to the next page.
AllocationResult result = old_space_allocator->AllocateRaw(
max_object_size, kTaggedAligned, AllocationOrigin::kRuntime);
Tagged<HeapObject> obj = result.ToObjectChecked();
// Simulate allocation folding moving the top pointer back.
old_space_allocator->ResetLab(
obj.address(), heap->allocator()->old_space_allocator()->limit(),
heap->allocator()->old_space_allocator()->limit());
}
{
// This triggers assert in crbug.com/777177.
AllocationResult result = old_space_allocator->AllocateRaw(
filler_size, kTaggedAligned, AllocationOrigin::kRuntime);
Tagged<HeapObject> obj = result.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), filler_size);
}
old_space_allocator->RemoveAllocationObserver(&observer);
}
HEAP_TEST(Regress791582) {
if (v8_flags.single_generation) return;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
HandleScope scope(isolate);
MainAllocator* new_space_allocator = heap->allocator()->new_space_allocator();
GrowNewSpace(heap);
int until_page_end =
static_cast<int>(heap->NewSpaceLimit() - heap->NewSpaceTop());
if (!IsAligned(until_page_end, kTaggedSize)) {
// The test works if the size of allocation area size is a multiple of
// pointer size. This is usually the case unless some allocation observer
// is already active (e.g. incremental marking observer).
return;
}
Observer observer(128);
new_space_allocator->FreeLinearAllocationArea();
new_space_allocator->AddAllocationObserver(&observer);
{
AllocationResult result = new_space_allocator->AllocateRaw(
until_page_end, kTaggedAligned, AllocationOrigin::kRuntime);
Tagged<HeapObject> obj = result.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), until_page_end);
// Simulate allocation folding moving the top pointer back.
*heap->NewSpaceAllocationTopAddress() = obj.address();
}
{
// This triggers assert in crbug.com/791582
AllocationResult result = new_space_allocator->AllocateRaw(
256, kTaggedAligned, AllocationOrigin::kRuntime);
Tagged<HeapObject> obj = result.ToObjectChecked();
heap->CreateFillerObjectAt(obj.address(), 256);
}
new_space_allocator->RemoveAllocationObserver(&observer);
}
TEST(ShrinkPageToHighWaterMarkFreeSpaceEnd) {
v8_flags.stress_incremental_marking = false;
v8_flags.stress_concurrent_allocation = false; // For SealCurrentObjects.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
heap::SealCurrentObjects(CcTest::heap());
// Prepare page that only contains a single object and a trailing FreeSpace
// filler.
DirectHandle<FixedArray> array =
isolate->factory()->NewFixedArray(128, AllocationType::kOld);
PageMetadata* page = PageMetadata::FromHeapObject(*array);
// Reset space so high water mark is consistent.
PagedSpace* old_space = CcTest::heap()->old_space();
CcTest::heap()->FreeMainThreadLinearAllocationAreas();
old_space->ResetFreeList();
Tagged<HeapObject> filler =
HeapObject::FromAddress(array->address() + array->Size());
CHECK(IsFreeSpace(filler));
size_t shrunk = old_space->ShrinkPageToHighWaterMark(page);
size_t should_have_shrunk = RoundDown(
static_cast<size_t>(MemoryChunkLayout::AllocatableMemoryInDataPage() -
array->Size()),
CommitPageSize());
CHECK_EQ(should_have_shrunk, shrunk);
}
TEST(ShrinkPageToHighWaterMarkNoFiller) {
v8_flags.stress_concurrent_allocation = false; // For SealCurrentObjects.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
heap::SealCurrentObjects(CcTest::heap());
const int kFillerSize = 0;
DirectHandleVector<FixedArray> arrays(isolate);
heap::FillOldSpacePageWithFixedArrays(CcTest::heap(), kFillerSize, &arrays);
DirectHandle<FixedArray> array = arrays.back();
PageMetadata* page = PageMetadata::FromHeapObject(*array);
CHECK_EQ(page->area_end(), array->address() + array->Size() + kFillerSize);
// Reset space so high water mark and fillers are consistent.
PagedSpace* old_space = CcTest::heap()->old_space();
CcTest::heap()->FreeMainThreadLinearAllocationAreas();
old_space->ResetFreeList();
size_t shrunk = old_space->ShrinkPageToHighWaterMark(page);
CHECK_EQ(0u, shrunk);
}
TEST(ShrinkPageToHighWaterMarkOneWordFiller) {
v8_flags.stress_concurrent_allocation = false; // For SealCurrentObjects.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
heap::SealCurrentObjects(CcTest::heap());
const int kFillerSize = kTaggedSize;
DirectHandleVector<FixedArray> arrays(isolate);
heap::FillOldSpacePageWithFixedArrays(CcTest::heap(), kFillerSize, &arrays);
DirectHandle<FixedArray> array = arrays.back();
PageMetadata* page = PageMetadata::FromHeapObject(*array);
CHECK_EQ(page->area_end(), array->address() + array->Size() + kFillerSize);
// Reset space so high water mark and fillers are consistent.
PagedSpace* old_space = CcTest::heap()->old_space();
CcTest::heap()->FreeMainThreadLinearAllocationAreas();
old_space->ResetFreeList();
Tagged<HeapObject> filler =
HeapObject::FromAddress(array->address() + array->Size());
CHECK_EQ(filler->map(),
ReadOnlyRoots(CcTest::heap()).one_pointer_filler_map());
size_t shrunk = old_space->ShrinkPageToHighWaterMark(page);
CHECK_EQ(0u, shrunk);
}
TEST(ShrinkPageToHighWaterMarkTwoWordFiller) {
v8_flags.stress_concurrent_allocation = false; // For SealCurrentObjects.
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
heap::SealCurrentObjects(CcTest::heap());
const int kFillerSize = 2 * kTaggedSize;
DirectHandleVector<FixedArray> arrays(isolate);
heap::FillOldSpacePageWithFixedArrays(CcTest::heap(), kFillerSize, &arrays);
DirectHandle<FixedArray> array = arrays.back();
PageMetadata* page = PageMetadata::FromHeapObject(*array);
CHECK_EQ(page->area_end(), array->address() + array->Size() + kFillerSize);
// Reset space so high water mark and fillers are consistent.
PagedSpace* old_space = CcTest::heap()->old_space();
CcTest::heap()->FreeMainThreadLinearAllocationAreas();
old_space->ResetFreeList();
Tagged<HeapObject> filler =
HeapObject::FromAddress(array->address() + array->Size());
CHECK_EQ(filler->map(),
ReadOnlyRoots(CcTest::heap()).two_pointer_filler_map());
size_t shrunk = old_space->ShrinkPageToHighWaterMark(page);
CHECK_EQ(0u, shrunk);
}
namespace {
// PageAllocator that always fails.
class FailingPageAllocator : public v8::PageAllocator {
public:
size_t AllocatePageSize() override { return 1024; }
size_t CommitPageSize() override { return 1024; }
void SetRandomMmapSeed(int64_t seed) override {}
void* GetRandomMmapAddr() override { return nullptr; }
void* AllocatePages(void* address, size_t length, size_t alignment,
Permission permissions) override {
return nullptr;
}
bool FreePages(void* address, size_t length) override { return false; }
bool ReleasePages(void* address, size_t length, size_t new_length) override {
return false;
}
bool SetPermissions(void* address, size_t length,
Permission permissions) override {
return false;
}
bool RecommitPages(void* address, size_t length,
Permission permissions) override {
return false;
}
bool DecommitPages(void* address, size_t length) override { return false; }
bool SealPages(void* address, size_t length) override { return false; }
};
} // namespace
TEST(NoMemoryForNewPage) {
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
// Memory allocator that will fail to allocate any pages.
FailingPageAllocator failing_allocator;
TestMemoryAllocatorScope test_allocator_scope(isolate, 0, &failing_allocator);
MemoryAllocator* memory_allocator = test_allocator_scope.allocator();
OldSpace faked_space(heap);
PageMetadata* page = memory_allocator->AllocatePage(
MemoryAllocator::AllocationMode::kRegular,
static_cast<PagedSpace*>(&faked_space), NOT_EXECUTABLE);
CHECK_NULL(page);
}
namespace {
// ReadOnlySpace cannot be torn down by a destructor because the destructor
// cannot take an argument. Since these tests create ReadOnlySpaces not attached
// to the Heap directly, they need to be destroyed to ensure the
// MemoryAllocator's stats are all 0 at exit.
class V8_NODISCARD ReadOnlySpaceScope {
public:
explicit ReadOnlySpaceScope(Heap* heap) : ro_space_(heap) {}
~ReadOnlySpaceScope() {
ro_space_.TearDown(CcTest::heap()->memory_allocator());
}
ReadOnlySpace* space() { return &ro_space_; }
private:
ReadOnlySpace ro_space_;
};
} // namespace
TEST(ReadOnlySpaceMetrics_OnePage) {
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
// Create a read-only space and allocate some memory, shrink the pages and
// check the allocated object size is as expected.
ReadOnlySpaceScope scope(heap);
ReadOnlySpace* faked_space = scope.space();
// Initially no memory.
CHECK_EQ(faked_space->Size(), 0);
CHECK_EQ(faked_space->Capacity(), 0);
CHECK_EQ(faked_space->CommittedMemory(), 0);
CHECK_EQ(faked_space->CommittedPhysicalMemory(), 0);
faked_space->AllocateRaw(16, kTaggedAligned);
faked_space->ShrinkPages();
faked_space->Seal(ReadOnlySpace::SealMode::kDoNotDetachFromHeap);
// Allocated objects size.
CHECK_EQ(faked_space->Size(), 16);
size_t committed_memory = RoundUp(
MemoryChunkLayout::ObjectStartOffsetInDataPage() + faked_space->Size(),
MemoryAllocator::GetCommitPageSize());
// Amount of OS allocated memory.
CHECK_EQ(faked_space->CommittedMemory(), committed_memory);
CHECK_EQ(faked_space->CommittedPhysicalMemory(), committed_memory);
// Capacity will be one OS page minus the page header.
CHECK_EQ(faked_space->Capacity(),
committed_memory - MemoryChunkLayout::ObjectStartOffsetInDataPage());
}
TEST(ReadOnlySpaceMetrics_AlignedAllocations) {
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
// Create a read-only space and allocate some memory, shrink the pages and
// check the allocated object size is as expected.
ReadOnlySpaceScope scope(heap);
ReadOnlySpace* faked_space = scope.space();
// Initially no memory.
CHECK_EQ(faked_space->Size(), 0);
CHECK_EQ(faked_space->Capacity(), 0);
CHECK_EQ(faked_space->CommittedMemory(), 0);
CHECK_EQ(faked_space->CommittedPhysicalMemory(), 0);
// Allocate an object just under an OS page in size.
int object_size =
static_cast<int>(MemoryAllocator::GetCommitPageSize() - kApiTaggedSize);
int alignment = USE_ALLOCATION_ALIGNMENT_BOOL ? kDoubleSize : kTaggedSize;
Tagged<HeapObject> object =
faked_space->AllocateRaw(object_size, kDoubleAligned).ToObjectChecked();
CHECK_EQ(object.address() % alignment, 0);
object =
faked_space->AllocateRaw(object_size, kDoubleAligned).ToObjectChecked();
CHECK_EQ(object.address() % alignment, 0);
// Calculate size of allocations based on area_start.
Address area_start = faked_space->pages().back()->GetAreaStart();
Address top = RoundUp(area_start, alignment) + object_size;
top = RoundUp(top, alignment) + object_size;
size_t expected_size = top - area_start;
faked_space->ShrinkPages();
faked_space->Seal(ReadOnlySpace::SealMode::kDoNotDetachFromHeap);
// Allocated objects size may will contain 4 bytes of padding on 32-bit or
// with pointer compression.
CHECK_EQ(faked_space->Size(), expected_size);
size_t committed_memory = RoundUp(
MemoryChunkLayout::ObjectStartOffsetInDataPage() + faked_space->Size(),
MemoryAllocator::GetCommitPageSize());
CHECK_EQ(faked_space->CommittedMemory(), committed_memory);
CHECK_EQ(faked_space->CommittedPhysicalMemory(), committed_memory);
// Capacity will be 3 OS pages minus the page header.
CHECK_EQ(faked_space->Capacity(),
committed_memory - MemoryChunkLayout::ObjectStartOffsetInDataPage());
}
TEST(ReadOnlySpaceMetrics_TwoPages) {
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
// Create a read-only space and allocate some memory, shrink the pages and
// check the allocated object size is as expected.
ReadOnlySpaceScope scope(heap);
ReadOnlySpace* faked_space = scope.space();
// Initially no memory.
CHECK_EQ(faked_space->Size(), 0);
CHECK_EQ(faked_space->Capacity(), 0);
CHECK_EQ(faked_space->CommittedMemory(), 0);
CHECK_EQ(faked_space->CommittedPhysicalMemory(), 0);
// Allocate an object that's too big to have more than one on a page.
int object_size = RoundUp(
static_cast<int>(
MemoryChunkLayout::AllocatableMemoryInMemoryChunk(RO_SPACE) / 2 + 16),
kTaggedSize);
CHECK_GT(object_size * 2,
MemoryChunkLayout::AllocatableMemoryInMemoryChunk(RO_SPACE));
faked_space->AllocateRaw(object_size, kTaggedAligned);
// Then allocate another so it expands the space to two pages.
faked_space->AllocateRaw(object_size, kTaggedAligned);
faked_space->ShrinkPages();
faked_space->Seal(ReadOnlySpace::SealMode::kDoNotDetachFromHeap);
// Allocated objects size.
CHECK_EQ(faked_space->Size(), object_size * 2);
// Amount of OS allocated memory.
size_t committed_memory_per_page =
RoundUp(MemoryChunkLayout::ObjectStartOffsetInDataPage() + object_size,
MemoryAllocator::GetCommitPageSize());
CHECK_EQ(faked_space->CommittedMemory(), 2 * committed_memory_per_page);
CHECK_EQ(faked_space->CommittedPhysicalMemory(),
2 * committed_memory_per_page);
// Capacity will be the space up to the amount of committed memory minus the
// page headers.
size_t capacity_per_page =
RoundUp(MemoryChunkLayout::ObjectStartOffsetInDataPage() + object_size,
MemoryAllocator::GetCommitPageSize()) -
MemoryChunkLayout::ObjectStartOffsetInDataPage();
CHECK_EQ(faked_space->Capacity(), 2 * capacity_per_page);
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,826 @@
// Copyright 2018 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/api/api-inl.h"
#include "src/codegen/assembler-inl.h"
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"
#include "src/ic/handler-configuration.h"
#include "src/objects/data-handler-inl.h"
#include "src/objects/smi.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
Handle<LoadHandler> CreateLoadHandlerForTest(
Factory* factory, AllocationType allocation = AllocationType::kYoung) {
Handle<LoadHandler> result = factory->NewLoadHandler(1, allocation);
result->set_smi_handler(Smi::zero());
result->set_validity_cell(Smi::zero());
result->set_data1(Smi::zero());
return result;
}
TEST(WeakReferencesBasic) {
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(
CcTest::heap());
HandleScope outer_scope(isolate);
IndirectHandle<LoadHandler> lh = CreateLoadHandlerForTest(factory);
if (!v8_flags.single_generation) CHECK(HeapLayout::InYoungGeneration(*lh));
Tagged<MaybeObject> code_object = lh->data1();
CHECK(IsSmi(code_object));
heap::InvokeMajorGC(CcTest::heap());
CHECK(!HeapLayout::InYoungGeneration(*lh));
CHECK_EQ(code_object, lh->data1());
{
HandleScope inner_scope(isolate);
// Create a new Code.
Assembler assm(isolate->allocator(), AssemblerOptions{});
assm.nop(); // supported on all architectures
CodeDesc desc;
assm.GetCode(isolate, &desc);
IndirectHandle<Code> code =
Factory::CodeBuilder(isolate, desc, CodeKind::FOR_TESTING).Build();
CHECK(IsCode(*code));
// We cannot store the Code object itself into the tagged field as it will
// be located outside of the main pointer compression cage when the sandbox
// is enabled. So instead we use the Code's wrapper object.
lh->set_data1(MakeWeak(code->wrapper()));
Tagged<HeapObject> code_wrapper_heap_object;
CHECK(lh->data1().GetHeapObjectIfWeak(&code_wrapper_heap_object));
CHECK_EQ(code->wrapper(), code_wrapper_heap_object);
heap::InvokeMajorGC(CcTest::heap());
CHECK(lh->data1().GetHeapObjectIfWeak(&code_wrapper_heap_object));
CHECK_EQ(code->wrapper(), code_wrapper_heap_object);
} // code will go out of scope.
heap::InvokeMajorGC(CcTest::heap());
CHECK(lh->data1().IsCleared());
}
TEST(WeakReferencesOldToOld) {
// Like WeakReferencesBasic, but the updated weak slot is in the old space,
// and referring to an old space object.
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh =
CreateLoadHandlerForTest(factory, AllocationType::kOld);
CHECK(heap->InOldSpace(*lh));
// Create a new FixedArray which the LoadHandler will point to.
DirectHandle<FixedArray> fixed_array =
factory->NewFixedArray(1, AllocationType::kOld);
CHECK(heap->InOldSpace(*fixed_array));
lh->set_data1(MakeWeak(*fixed_array));
PageMetadata* page_before_gc = PageMetadata::FromHeapObject(*fixed_array);
heap::ForceEvacuationCandidate(page_before_gc);
heap::InvokeMajorGC(heap);
CHECK(heap->InOldSpace(*fixed_array));
Tagged<HeapObject> heap_object;
CHECK(lh->data1().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(heap_object, *fixed_array);
}
TEST(WeakReferencesOldToNew) {
// Like WeakReferencesBasic, but the updated weak slot is in the old space,
// and referring to an new space object.
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh =
CreateLoadHandlerForTest(factory, AllocationType::kOld);
CHECK(heap->InOldSpace(*lh));
// Create a new FixedArray which the LoadHandler will point to.
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
CHECK(HeapLayout::InYoungGeneration(*fixed_array));
lh->set_data1(MakeWeak(*fixed_array));
heap::InvokeMajorGC(heap);
Tagged<HeapObject> heap_object;
CHECK(lh->data1().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(heap_object, *fixed_array);
}
TEST(WeakReferencesOldToNewScavenged) {
if (v8_flags.single_generation) return;
// Like WeakReferencesBasic, but the updated weak slot is in the old space,
// and referring to an new space object, which is then scavenged.
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh =
CreateLoadHandlerForTest(factory, AllocationType::kOld);
CHECK(heap->InOldSpace(*lh));
// Create a new FixedArray which the LoadHandler will point to.
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
CHECK(HeapLayout::InYoungGeneration(*fixed_array));
lh->set_data1(MakeWeak(*fixed_array));
heap::InvokeMinorGC(heap);
Tagged<HeapObject> heap_object;
CHECK(lh->data1().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(heap_object, *fixed_array);
}
TEST(WeakReferencesOldToCleared) {
// Like WeakReferencesBasic, but the updated weak slot is in the old space,
// and is cleared.
ManualGCScope manual_gc_scope;
heap::ManualEvacuationCandidatesSelectionScope
manual_evacuation_candidate_selection_scope(manual_gc_scope);
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh =
CreateLoadHandlerForTest(factory, AllocationType::kOld);
CHECK(heap->InOldSpace(*lh));
lh->set_data1(ClearedValue(isolate));
heap::InvokeMajorGC(heap);
CHECK(lh->data1().IsCleared());
}
TEST(ObjectMovesBeforeClearingWeakField) {
if (!v8_flags.incremental_marking || v8_flags.single_generation ||
v8_flags.separate_gc_phases) {
return;
}
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
HandleScope outer_scope(isolate);
IndirectHandle<LoadHandler> lh = CreateLoadHandlerForTest(factory);
CHECK(InCorrectGeneration(*lh));
Address lh_object_location = lh->address();
{
HandleScope inner_scope(isolate);
// Create a new FixedArray which the LoadHandler will point to.
IndirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
CHECK(HeapLayout::InYoungGeneration(*fixed_array));
lh->set_data1(MakeWeak(*fixed_array));
// inner_scope will go out of scope, so when marking the next time,
// *fixed_array will stay white.
}
// Do marking steps; this will store *lh into the list for later processing
// (since it points to a white object).
SimulateIncrementalMarking(heap, true);
// Scavenger will move *lh.
heap::InvokeMinorGC(heap);
CHECK_NE(lh_object_location, lh.address());
CHECK(lh->data1().IsWeak());
// Now we try to clear *lh.
heap::InvokeMajorGC(heap);
CHECK(lh->data1().IsCleared());
}
TEST(ObjectWithWeakFieldDies) {
if (!v8_flags.incremental_marking) {
return;
}
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
{
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh = CreateLoadHandlerForTest(factory);
CHECK(InCorrectGeneration(*lh));
{
HandleScope inner_scope(isolate);
// Create a new FixedArray which the LoadHandler will point to.
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
CHECK(InCorrectGeneration(*fixed_array));
lh->set_data1(MakeWeak(*fixed_array));
// inner_scope will go out of scope, so when marking the next time,
// *fixed_array will stay white.
}
// Do marking steps; this will store *lh into the list for later processing
// (since it points to a white object).
SimulateIncrementalMarking(heap, true);
} // outer_scope goes out of scope
// lh will die
heap::InvokeMinorGC(heap);
// This used to crash when processing the dead weak reference.
heap::InvokeMajorGC(heap);
}
TEST(ObjectWithWeakReferencePromoted) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh = CreateLoadHandlerForTest(factory);
CHECK(HeapLayout::InYoungGeneration(*lh));
// Create a new FixedArray which the LoadHandler will point to.
DirectHandle<FixedArray> fixed_array = factory->NewFixedArray(1);
CHECK(HeapLayout::InYoungGeneration(*fixed_array));
lh->set_data1(MakeWeak(*fixed_array));
heap::EmptyNewSpaceUsingGC(heap);
CHECK(heap->InOldSpace(*lh));
CHECK(heap->InOldSpace(*fixed_array));
Tagged<HeapObject> heap_object;
CHECK(lh->data1().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(heap_object, *fixed_array);
}
TEST(ObjectWithClearedWeakReferencePromoted) {
if (v8_flags.single_generation || v8_flags.stress_incremental_marking) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh = CreateLoadHandlerForTest(factory);
CHECK(HeapLayout::InYoungGeneration(*lh));
lh->set_data1(ClearedValue(isolate));
heap::EmptyNewSpaceUsingGC(heap);
CHECK(heap->InOldSpace(*lh));
CHECK(lh->data1().IsCleared());
heap::InvokeMajorGC(heap);
CHECK(lh->data1().IsCleared());
}
TEST(WeakReferenceWriteBarrier) {
if (!v8_flags.incremental_marking) {
return;
}
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
DirectHandle<LoadHandler> lh = CreateLoadHandlerForTest(factory);
CHECK(InCorrectGeneration(*lh));
v8::Global<Value> global_lh(CcTest::isolate(), Utils::ToLocal(lh));
{
HandleScope inner_scope(isolate);
// Create a new FixedArray which the LoadHandler will point to.
DirectHandle<FixedArray> fixed_array1 = factory->NewFixedArray(1);
CHECK(InCorrectGeneration(*fixed_array1));
lh->set_data1(MakeWeak(*fixed_array1));
SimulateIncrementalMarking(heap, true);
DirectHandle<FixedArray> fixed_array2 = factory->NewFixedArray(1);
CHECK(InCorrectGeneration(*fixed_array2));
// This write will trigger the write barrier.
lh->set_data1(MakeWeak(*fixed_array2));
}
heap::InvokeMajorGC(heap);
// Check that the write barrier treated the weak reference as strong.
CHECK(lh->data1().IsWeak());
}
TEST(EmptyWeakArray) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope outer_scope(isolate);
DirectHandle<WeakFixedArray> array = factory->empty_weak_fixed_array();
CHECK(IsWeakFixedArray(*array));
CHECK(!IsFixedArray(*array));
CHECK_EQ(array->length(), 0);
}
TEST(WeakArraysBasic) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
HandleScope outer_scope(isolate);
const int length = 4;
IndirectHandle<WeakFixedArray> array = factory->NewWeakFixedArray(length);
CHECK(IsWeakFixedArray(*array));
CHECK(!IsFixedArray(*array));
CHECK_EQ(array->length(), length);
CHECK(HeapLayout::InYoungGeneration(*array));
for (int i = 0; i < length; ++i) {
Tagged<HeapObject> heap_object;
CHECK(array->get(i).GetHeapObjectIfStrong(&heap_object));
CHECK_EQ(heap_object, ReadOnlyRoots(heap).undefined_value());
}
IndirectHandle<HeapObject> saved;
{
HandleScope inner_scope(isolate);
IndirectHandle<FixedArray> index0 = factory->NewFixedArray(1);
index0->set(0, Smi::FromInt(2016));
IndirectHandle<FixedArray> index1 = factory->NewFixedArray(1);
index1->set(0, Smi::FromInt(2017));
IndirectHandle<FixedArray> index2 = factory->NewFixedArray(1);
index2->set(0, Smi::FromInt(2018));
IndirectHandle<FixedArray> index3 = factory->NewFixedArray(1);
index3->set(0, Smi::FromInt(2019));
array->set(0, MakeWeak(*index0));
array->set(1, MakeWeak(*index1));
array->set(2, *index2);
array->set(3, MakeWeak(*index3));
saved = inner_scope.CloseAndEscape(index1);
} // inner_scope goes out of scope.
// The references are only cleared by the mark-compact (scavenger treats weak
// references as strong). Thus we need to GC until the array reaches old
// space.
// TODO(marja): update this when/if we do handle weak references in the new
// space.
heap::InvokeMinorGC(heap);
Tagged<HeapObject> heap_object;
CHECK(array->get(0).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2016);
CHECK(array->get(1).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2017);
CHECK(array->get(2).GetHeapObjectIfStrong(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2018);
CHECK(array->get(3).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2019);
heap::InvokeMajorGC(heap);
CHECK(heap->InOldSpace(*array));
CHECK(array->get(0).IsCleared());
CHECK(array->get(1).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2017);
CHECK(array->get(2).GetHeapObjectIfStrong(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2018);
CHECK(array->get(3).IsCleared());
}
TEST(WeakArrayListBasic) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
HandleScope outer_scope(isolate);
Handle<WeakArrayList> array(ReadOnlyRoots(heap).empty_weak_array_list(),
isolate);
CHECK(IsWeakArrayList(*array));
CHECK(!IsFixedArray(*array));
CHECK(!IsWeakFixedArray(*array));
CHECK_EQ(array->length(), 0);
DirectHandle<FixedArray> index2 = factory->NewFixedArray(1);
index2->set(0, Smi::FromInt(2017));
{
HandleScope inner_scope(isolate);
DirectHandle<FixedArray> index0 = factory->NewFixedArray(1);
index0->set(0, Smi::FromInt(2016));
DirectHandle<FixedArray> index4 = factory->NewFixedArray(1);
index4->set(0, Smi::FromInt(2018));
DirectHandle<FixedArray> index6 = factory->NewFixedArray(1);
index6->set(0, Smi::FromInt(2019));
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(index0));
array = WeakArrayList::AddToEnd(
isolate, array, MaybeObjectDirectHandle(Smi::FromInt(1), isolate));
CHECK_EQ(array->length(), 2);
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(index2));
array = WeakArrayList::AddToEnd(
isolate, array, MaybeObjectDirectHandle(Smi::FromInt(3), isolate));
CHECK_EQ(array->length(), 4);
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(index4));
array = WeakArrayList::AddToEnd(
isolate, array, MaybeObjectDirectHandle(Smi::FromInt(5), isolate));
CHECK_EQ(array->length(), 6);
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(index6));
array = WeakArrayList::AddToEnd(
isolate, array, MaybeObjectDirectHandle(Smi::FromInt(7), isolate));
CHECK_EQ(array->length(), 8);
CHECK(InCorrectGeneration(*array));
CHECK_EQ(array->get(0), MakeWeak(*index0));
CHECK_EQ(array->get(1).ToSmi().value(), 1);
CHECK_EQ(array->get(2), MakeWeak(*index2));
CHECK_EQ(array->get(3).ToSmi().value(), 3);
CHECK_EQ(array->get(4), MakeWeak(*index4));
CHECK_EQ(array->get(5).ToSmi().value(), 5);
CHECK_EQ(array->get(6), MakeWeak(*index6));
array = inner_scope.CloseAndEscape(array);
} // inner_scope goes out of scope.
// The references are only cleared by the mark-compact (scavenger treats weak
// references as strong). Thus we need to GC until the array reaches old
// space.
// TODO(marja): update this when/if we do handle weak references in the new
// space.
heap::InvokeMinorGC(heap);
Tagged<HeapObject> heap_object;
CHECK_EQ(array->length(), 8);
CHECK(array->get(0).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2016);
CHECK_EQ(array->get(1).ToSmi().value(), 1);
CHECK(array->get(2).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2017);
CHECK_EQ(array->get(3).ToSmi().value(), 3);
CHECK(array->get(4).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2018);
CHECK_EQ(array->get(5).ToSmi().value(), 5);
CHECK(array->get(6).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2019);
CHECK_EQ(array->get(7).ToSmi().value(), 7);
heap::InvokeMajorGC(heap);
CHECK(heap->InOldSpace(*array));
CHECK_EQ(array->length(), 8);
CHECK(array->get(0).IsCleared());
CHECK_EQ(array->get(1).ToSmi().value(), 1);
CHECK(array->get(2).GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(Cast<Smi>(Cast<FixedArray>(heap_object)->get(0)).value(), 2017);
CHECK_EQ(array->get(3).ToSmi().value(), 3);
CHECK(array->get(4).IsCleared());
CHECK_EQ(array->get(5).ToSmi().value(), 5);
CHECK(array->get(6).IsCleared());
CHECK_EQ(array->get(7).ToSmi().value(), 7);
}
TEST(WeakArrayListRemove) {
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
Handle<WeakArrayList> array(ReadOnlyRoots(heap).empty_weak_array_list(),
isolate);
DirectHandle<FixedArray> elem0 = factory->NewFixedArray(1);
DirectHandle<FixedArray> elem1 = factory->NewFixedArray(1);
DirectHandle<FixedArray> elem2 = factory->NewFixedArray(1);
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(elem0));
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(elem1));
array = WeakArrayList::AddToEnd(isolate, array,
MaybeObjectDirectHandle::Weak(elem2));
CHECK_EQ(array->length(), 3);
CHECK_EQ(array->get(0), MakeWeak(*elem0));
CHECK_EQ(array->get(1), MakeWeak(*elem1));
CHECK_EQ(array->get(2), MakeWeak(*elem2));
CHECK(array->RemoveOne(MaybeObjectDirectHandle::Weak(elem1)));
CHECK_EQ(array->length(), 2);
CHECK_EQ(array->get(0), MakeWeak(*elem0));
CHECK_EQ(array->get(1), MakeWeak(*elem2));
CHECK(!array->RemoveOne(MaybeObjectDirectHandle::Weak(elem1)));
CHECK_EQ(array->length(), 2);
CHECK_EQ(array->get(0), MakeWeak(*elem0));
CHECK_EQ(array->get(1), MakeWeak(*elem2));
CHECK(array->RemoveOne(MaybeObjectDirectHandle::Weak(elem0)));
CHECK_EQ(array->length(), 1);
CHECK_EQ(array->get(0), MakeWeak(*elem2));
CHECK(array->RemoveOne(MaybeObjectDirectHandle::Weak(elem2)));
CHECK_EQ(array->length(), 0);
}
TEST(ProtectedWeakFixedArray) {
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope handle_scope(isolate);
IndirectHandle<ProtectedWeakFixedArray> array =
factory->NewProtectedWeakFixedArray(5);
IndirectHandle<TrustedFixedArray> elem1 = factory->NewTrustedFixedArray(1);
IndirectHandle<TrustedFixedArray> elem3 = factory->NewTrustedFixedArray(1);
array->set(1, MakeWeak(*elem1));
array->set(3, MakeWeak(*elem3));
{
HandleScope inner_scope(isolate);
DirectHandle<TrustedFixedArray> elem0 = factory->NewTrustedFixedArray(1);
DirectHandle<TrustedFixedArray> elem2 = factory->NewTrustedFixedArray(1);
DirectHandle<TrustedFixedArray> elem4 = factory->NewTrustedFixedArray(1);
array->set(0, MakeWeak(*elem0));
array->set(2, MakeWeak(*elem2));
array->set(4, MakeWeak(*elem4));
heap::InvokeMajorGC(heap);
CHECK_EQ(array->get(0).GetHeapObjectAssumeWeak(), *elem0);
CHECK_EQ(array->get(1).GetHeapObjectAssumeWeak(), *elem1);
CHECK_EQ(array->get(2).GetHeapObjectAssumeWeak(), *elem2);
CHECK_EQ(array->get(3).GetHeapObjectAssumeWeak(), *elem3);
CHECK_EQ(array->get(4).GetHeapObjectAssumeWeak(), *elem4);
}
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
heap::InvokeMajorGC(heap);
CHECK(array->get(0).IsCleared());
CHECK_EQ(array->get(1).GetHeapObjectAssumeWeak(), *elem1);
CHECK(array->get(2).IsCleared());
CHECK_EQ(array->get(3).GetHeapObjectAssumeWeak(), *elem3);
CHECK(array->get(4).IsCleared());
}
TEST(Regress7768) {
i::v8_flags.allow_natives_syntax = true;
i::v8_flags.turbo_inlining = false;
if (!v8_flags.incremental_marking) {
return;
}
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext context;
Isolate* isolate = CcTest::i_isolate();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
// Create an optimized code which will contain a weak reference to another
// function ("f"). The weak reference is the only reference to the function.
CompileRun(
"function myfunc(f) { f(); } "
"%PrepareFunctionForOptimization(myfunc); "
"(function wrapper() { "
" function f() {}; myfunc(f); myfunc(f); "
" %OptimizeFunctionOnNextCall(myfunc); myfunc(f); "
" %ClearFunctionFeedback(wrapper);"
"})(); "
"%ClearFunctionFeedback(myfunc);");
// Do marking steps; this will store the objects pointed by myfunc for later
// processing.
SimulateIncrementalMarking(heap, true);
// Deoptimize the code; now the pointers inside it will be replaced with
// undefined, and the weak_objects_in_code is the only place pointing to the
// function f.
CompileRun("%DeoptimizeFunction(myfunc);");
// The object pointed to by the weak reference won't be scavenged.
heap::InvokeMinorGC(heap);
// Make sure the memory where it's stored is invalidated, so that we'll crash
// if we try to access it.
HeapTester::UncommitUnusedMemory(heap);
// This used to crash when processing the dead weak reference.
heap::InvokeMajorGC(heap);
}
TEST(PrototypeUsersBasic) {
CcTest::InitializeVM();
LocalContext context;
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer_scope(isolate);
Handle<WeakArrayList> array(ReadOnlyRoots(heap).empty_weak_array_list(),
isolate);
// Add some objects into the array.
int index = -1;
{
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
CHECK_EQ(array->length(), index + 1);
}
CHECK_EQ(index, 1);
int empty_index = index;
PrototypeUsers::MarkSlotEmpty(*array, empty_index);
// Even though we have an empty slot, we still add to the end.
int last_index = index;
int old_capacity = array->capacity();
while (!array->IsFull()) {
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
CHECK_EQ(index, last_index + 1);
CHECK_EQ(array->length(), index + 1);
last_index = index;
}
// The next addition will fill the empty slot.
{
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
}
CHECK_EQ(index, empty_index);
// The next addition will make the arrow grow again.
{
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
CHECK_EQ(array->length(), index + 1);
last_index = index;
}
CHECK_GT(array->capacity(), old_capacity);
// Make multiple slots empty.
int empty_index1 = 1;
int empty_index2 = 2;
PrototypeUsers::MarkSlotEmpty(*array, empty_index1);
PrototypeUsers::MarkSlotEmpty(*array, empty_index2);
// Fill the array (still adding to the end)
old_capacity = array->capacity();
while (!array->IsFull()) {
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
CHECK_EQ(index, last_index + 1);
CHECK_EQ(array->length(), index + 1);
last_index = index;
}
// Make sure we use the empty slots in (reverse) order.
{
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
}
CHECK_EQ(index, empty_index2);
{
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map, &index);
}
CHECK_EQ(index, empty_index1);
}
namespace {
Tagged<HeapObject> saved_heap_object;
static void TestCompactCallback(Tagged<HeapObject> value, int old_index,
int new_index) {
saved_heap_object = value;
CHECK_EQ(old_index, 2);
CHECK_EQ(new_index, 1);
}
} // namespace
TEST(PrototypeUsersCompacted) {
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
LocalContext context;
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
i::DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
HandleScope outer_scope(isolate);
Handle<WeakArrayList> array(ReadOnlyRoots(heap).empty_weak_array_list(),
isolate);
// Add some objects into the array.
int index = -1;
DirectHandle<Map> map_cleared_by_user =
factory->NewContextfulMapForCurrentContext(JS_OBJECT_TYPE,
JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, map_cleared_by_user, &index);
CHECK_EQ(index, 1);
DirectHandle<Map> live_map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, live_map, &index);
CHECK_EQ(index, 2);
{
HandleScope inner_scope(isolate);
DirectHandle<Map> soon_dead_map =
factory->NewContextfulMapForCurrentContext(JS_OBJECT_TYPE,
JSObject::kHeaderSize);
array = PrototypeUsers::Add(isolate, array, soon_dead_map, &index);
CHECK_EQ(index, 3);
array = inner_scope.CloseAndEscape(array);
}
PrototypeUsers::MarkSlotEmpty(*array, 1);
heap::InvokeMajorGC(heap);
CHECK(array->get(3).IsCleared());
CHECK_EQ(array->length(), 3 + PrototypeUsers::kFirstIndex);
Tagged<WeakArrayList> new_array =
PrototypeUsers::Compact(array, heap, TestCompactCallback);
CHECK_EQ(new_array->length(), 1 + PrototypeUsers::kFirstIndex);
CHECK_EQ(saved_heap_object, *live_map);
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,94 @@
// Copyright 2015 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/incremental-marking.h"
#include "src/heap/mark-compact.h"
#include "src/heap/marking-state-inl.h"
#include "src/heap/spaces.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-tester.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace heap {
HEAP_TEST(WriteBarrier_Marking) {
if (!v8_flags.incremental_marking) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer(isolate);
DirectHandle<FixedArray> objects = factory->NewFixedArray(3);
v8::Global<Value> global_objects(CcTest::isolate(), Utils::ToLocal(objects));
{
// Make sure that these objects are not immediately reachable from
// the roots to prevent them being marked grey at the start of marking.
HandleScope inner(isolate);
DirectHandle<FixedArray> host = factory->NewFixedArray(1);
DirectHandle<HeapNumber> value1 = factory->NewHeapNumber(1.1);
DirectHandle<HeapNumber> value2 = factory->NewHeapNumber(1.2);
objects->set(0, *host);
objects->set(1, *value1);
objects->set(2, *value2);
}
heap::SimulateIncrementalMarking(CcTest::heap(), false);
Tagged<FixedArray> host = Cast<FixedArray>(objects->get(0));
Tagged<HeapObject> value1 = Cast<HeapObject>(objects->get(1));
Tagged<HeapObject> value2 = Cast<HeapObject>(objects->get(2));
CHECK(heap->marking_state()->IsUnmarked(host));
CHECK(heap->marking_state()->IsUnmarked(value1));
// Trigger the barrier for the unmarked host and expect the bail out.
WriteBarrier::MarkingForTesting(host, host->RawFieldOfElementAt(0), value1);
CHECK(heap->marking_state()->IsMarked(value1));
CHECK(heap->marking_state()->IsUnmarked(value2));
WriteBarrier::MarkingForTesting(host, host->RawFieldOfElementAt(0), value2);
CHECK(heap->marking_state()->IsMarked(value2));
heap::SimulateIncrementalMarking(CcTest::heap(), true);
CHECK(heap->marking_state()->IsMarked(host));
CHECK(heap->marking_state()->IsMarked(value1));
CHECK(heap->marking_state()->IsMarked(value2));
}
HEAP_TEST(WriteBarrier_MarkingExtension) {
if (!v8_flags.incremental_marking) return;
ManualGCScope manual_gc_scope;
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope outer(isolate);
DirectHandle<FixedArray> objects = factory->NewFixedArray(1);
ArrayBufferExtension* extension;
{
HandleScope inner(isolate);
Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(CcTest::isolate(), 100);
DirectHandle<JSArrayBuffer> host = v8::Utils::OpenDirectHandle(*ab);
extension = host->extension();
objects->set(0, *host);
}
heap::SimulateIncrementalMarking(CcTest::heap(), false);
Tagged<JSArrayBuffer> host = Cast<JSArrayBuffer>(objects->get(0));
CHECK(heap->marking_state()->IsUnmarked(host));
CHECK(!extension->IsMarked());
WriteBarrier::ForArrayBufferExtension(host, extension);
CHECK(extension->IsMarked());
// Concurrent marking barrier should mark the value now.
CHECK(extension->IsMarked());
// Keep object alive using the global handle.
v8::Global<ArrayBuffer> global_host(
CcTest::isolate(), Utils::ToLocal(direct_handle(host, isolate)));
heap::SimulateIncrementalMarking(CcTest::heap(), true);
CHECK(heap->marking_state()->IsMarked(host));
CHECK(extension->IsMarked());
}
} // namespace heap
} // namespace internal
} // namespace v8