| // 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/init/v8.h" |
| |
| #include "src/heap/heap-inl.h" |
| #include "src/heap/heap.h" |
| #include "src/heap/invalidated-slots-inl.h" |
| #include "src/heap/invalidated-slots.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 { |
| |
| Page* HeapTester::AllocateByteArraysOnPage( |
| Heap* heap, std::vector<ByteArray>* byte_arrays) { |
| PauseAllocationObserversScope pause_observers(heap); |
| const int kLength = 256 - ByteArray::kHeaderSize; |
| const int kSize = ByteArray::SizeFor(kLength); |
| CHECK_EQ(kSize, 256); |
| Isolate* isolate = heap->isolate(); |
| PagedSpace* old_space = heap->old_space(); |
| Page* page; |
| // Fill a page with byte arrays. |
| { |
| AlwaysAllocateScope always_allocate(isolate); |
| heap::SimulateFullSpace(old_space); |
| ByteArray byte_array; |
| CHECK(AllocateByteArrayForTest(heap, kLength, AllocationType::kOld) |
| .To(&byte_array)); |
| byte_arrays->push_back(byte_array); |
| page = Page::FromHeapObject(byte_array); |
| size_t n = page->area_size() / kSize; |
| for (size_t i = 1; i < n; i++) { |
| CHECK(AllocateByteArrayForTest(heap, kLength, AllocationType::kOld) |
| .To(&byte_array)); |
| byte_arrays->push_back(byte_array); |
| CHECK_EQ(page, Page::FromHeapObject(byte_array)); |
| } |
| } |
| CHECK_NULL(page->invalidated_slots()); |
| return page; |
| } |
| |
| HEAP_TEST(InvalidatedSlotsNoInvalidatedRanges) { |
| CcTest::InitializeVM(); |
| Heap* heap = CcTest::heap(); |
| std::vector<ByteArray> byte_arrays; |
| Page* page = AllocateByteArraysOnPage(heap, &byte_arrays); |
| InvalidatedSlotsFilter filter(page); |
| for (ByteArray byte_array : byte_arrays) { |
| Address start = byte_array.address() + ByteArray::kHeaderSize; |
| Address end = byte_array.address() + byte_array.Size(); |
| for (Address addr = start; addr < end; addr += kTaggedSize) { |
| CHECK(filter.IsValid(addr)); |
| } |
| } |
| } |
| |
| HEAP_TEST(InvalidatedSlotsSomeInvalidatedRanges) { |
| CcTest::InitializeVM(); |
| Heap* heap = CcTest::heap(); |
| std::vector<ByteArray> byte_arrays; |
| Page* page = AllocateByteArraysOnPage(heap, &byte_arrays); |
| // Register every second byte arrays as invalidated. |
| for (size_t i = 0; i < byte_arrays.size(); i += 2) { |
| page->RegisterObjectWithInvalidatedSlots(byte_arrays[i], |
| byte_arrays[i].Size()); |
| } |
| InvalidatedSlotsFilter filter(page); |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| ByteArray byte_array = byte_arrays[i]; |
| Address start = byte_array.address() + ByteArray::kHeaderSize; |
| Address end = byte_array.address() + byte_array.Size(); |
| for (Address addr = start; addr < end; addr += kTaggedSize) { |
| if (i % 2 == 0) { |
| CHECK(!filter.IsValid(addr)); |
| } else { |
| CHECK(filter.IsValid(addr)); |
| } |
| } |
| } |
| } |
| |
| HEAP_TEST(InvalidatedSlotsAllInvalidatedRanges) { |
| CcTest::InitializeVM(); |
| Heap* heap = CcTest::heap(); |
| std::vector<ByteArray> byte_arrays; |
| Page* page = AllocateByteArraysOnPage(heap, &byte_arrays); |
| // Register the all byte arrays as invalidated. |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| page->RegisterObjectWithInvalidatedSlots(byte_arrays[i], |
| byte_arrays[i].Size()); |
| } |
| InvalidatedSlotsFilter filter(page); |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| ByteArray byte_array = byte_arrays[i]; |
| Address start = byte_array.address() + ByteArray::kHeaderSize; |
| Address end = byte_array.address() + byte_array.Size(); |
| for (Address addr = start; addr < end; addr += kTaggedSize) { |
| CHECK(!filter.IsValid(addr)); |
| } |
| } |
| } |
| |
| HEAP_TEST(InvalidatedSlotsAfterTrimming) { |
| ManualGCScope manual_gc_scope; |
| CcTest::InitializeVM(); |
| Heap* heap = CcTest::heap(); |
| std::vector<ByteArray> byte_arrays; |
| Page* page = AllocateByteArraysOnPage(heap, &byte_arrays); |
| // Register the all byte arrays as invalidated. |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| page->RegisterObjectWithInvalidatedSlots(byte_arrays[i], |
| byte_arrays[i].Size()); |
| } |
| // Trim byte arrays and check that the slots outside the byte arrays are |
| // considered invalid if the old space page was swept. |
| InvalidatedSlotsFilter filter(page); |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| ByteArray byte_array = byte_arrays[i]; |
| Address start = byte_array.address() + ByteArray::kHeaderSize; |
| Address end = byte_array.address() + byte_array.Size(); |
| heap->RightTrimFixedArray(byte_array, byte_array.length()); |
| for (Address addr = start; addr < end; addr += kTaggedSize) { |
| CHECK_EQ(filter.IsValid(addr), page->SweepingDone()); |
| } |
| } |
| } |
| |
| HEAP_TEST(InvalidatedSlotsEvacuationCandidate) { |
| ManualGCScope manual_gc_scope; |
| CcTest::InitializeVM(); |
| Heap* heap = CcTest::heap(); |
| std::vector<ByteArray> byte_arrays; |
| Page* page = AllocateByteArraysOnPage(heap, &byte_arrays); |
| page->MarkEvacuationCandidate(); |
| // Register the all byte arrays as invalidated. |
| // This should be no-op because the page is marked as evacuation |
| // candidate. |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| page->RegisterObjectWithInvalidatedSlots(byte_arrays[i], |
| byte_arrays[i].Size()); |
| } |
| // All slots must still be valid. |
| InvalidatedSlotsFilter filter(page); |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| ByteArray byte_array = byte_arrays[i]; |
| Address start = byte_array.address() + ByteArray::kHeaderSize; |
| Address end = byte_array.address() + byte_array.Size(); |
| for (Address addr = start; addr < end; addr += kTaggedSize) { |
| CHECK(filter.IsValid(addr)); |
| } |
| } |
| } |
| |
| HEAP_TEST(InvalidatedSlotsResetObjectRegression) { |
| CcTest::InitializeVM(); |
| Heap* heap = CcTest::heap(); |
| std::vector<ByteArray> byte_arrays; |
| Page* page = AllocateByteArraysOnPage(heap, &byte_arrays); |
| // Ensure that the first array has smaller size then the rest. |
| heap->RightTrimFixedArray(byte_arrays[0], byte_arrays[0].length() - 8); |
| // Register the all byte arrays as invalidated. |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| page->RegisterObjectWithInvalidatedSlots(byte_arrays[i], |
| byte_arrays[i].Size()); |
| } |
| // All slots must still be invalid. |
| InvalidatedSlotsFilter filter(page); |
| for (size_t i = 0; i < byte_arrays.size(); i++) { |
| ByteArray byte_array = byte_arrays[i]; |
| Address start = byte_array.address() + ByteArray::kHeaderSize; |
| Address end = byte_array.address() + byte_array.Size(); |
| for (Address addr = start; addr < end; addr += kTaggedSize) { |
| CHECK(!filter.IsValid(addr)); |
| } |
| } |
| } |
| |
| Handle<FixedArray> AllocateArrayOnFreshPage(Isolate* isolate, |
| PagedSpace* old_space, int length) { |
| AlwaysAllocateScope always_allocate(isolate); |
| heap::SimulateFullSpace(old_space); |
| return isolate->factory()->NewFixedArray(length, AllocationType::kOld); |
| } |
| |
| Handle<FixedArray> AllocateArrayOnEvacuationCandidate(Isolate* isolate, |
| PagedSpace* old_space, |
| int length) { |
| Handle<FixedArray> object = |
| AllocateArrayOnFreshPage(isolate, old_space, length); |
| heap::ForceEvacuationCandidate(Page::FromHeapObject(*object)); |
| return object; |
| } |
| |
| HEAP_TEST(InvalidatedSlotsRightTrimFixedArray) { |
| FLAG_manual_evacuation_candidates_selection = true; |
| FLAG_parallel_compaction = false; |
| ManualGCScope manual_gc_scope; |
| CcTest::InitializeVM(); |
| Isolate* isolate = CcTest::i_isolate(); |
| Factory* factory = isolate->factory(); |
| Heap* heap = CcTest::heap(); |
| HandleScope scope(isolate); |
| PagedSpace* old_space = heap->old_space(); |
| // Allocate a dummy page to be swept be the sweeper during evacuation. |
| AllocateArrayOnFreshPage(isolate, old_space, 1); |
| Handle<FixedArray> evacuated = |
| AllocateArrayOnEvacuationCandidate(isolate, old_space, 1); |
| Handle<FixedArray> trimmed = AllocateArrayOnFreshPage(isolate, old_space, 10); |
| heap::SimulateIncrementalMarking(heap); |
| for (int i = 1; i < trimmed->length(); i++) { |
| trimmed->set(i, *evacuated); |
| } |
| { |
| HandleScope scope(isolate); |
| Handle<HeapObject> dead = factory->NewFixedArray(1); |
| for (int i = 1; i < trimmed->length(); i++) { |
| trimmed->set(i, *dead); |
| } |
| heap->RightTrimFixedArray(*trimmed, trimmed->length() - 1); |
| } |
| CcTest::CollectGarbage(i::NEW_SPACE); |
| CcTest::CollectGarbage(i::OLD_SPACE); |
| } |
| |
| HEAP_TEST(InvalidatedSlotsRightTrimLargeFixedArray) { |
| FLAG_manual_evacuation_candidates_selection = true; |
| FLAG_parallel_compaction = false; |
| ManualGCScope manual_gc_scope; |
| CcTest::InitializeVM(); |
| Isolate* isolate = CcTest::i_isolate(); |
| Factory* factory = isolate->factory(); |
| Heap* heap = CcTest::heap(); |
| HandleScope scope(isolate); |
| PagedSpace* old_space = heap->old_space(); |
| // Allocate a dummy page to be swept be the sweeper during evacuation. |
| AllocateArrayOnFreshPage(isolate, old_space, 1); |
| Handle<FixedArray> evacuated = |
| AllocateArrayOnEvacuationCandidate(isolate, old_space, 1); |
| Handle<FixedArray> trimmed; |
| { |
| AlwaysAllocateScope always_allocate(isolate); |
| trimmed = factory->NewFixedArray( |
| kMaxRegularHeapObjectSize / kTaggedSize + 100, AllocationType::kOld); |
| DCHECK(MemoryChunk::FromHeapObject(*trimmed)->InLargeObjectSpace()); |
| } |
| heap::SimulateIncrementalMarking(heap); |
| for (int i = 1; i < trimmed->length(); i++) { |
| trimmed->set(i, *evacuated); |
| } |
| { |
| HandleScope scope(isolate); |
| Handle<HeapObject> dead = factory->NewFixedArray(1); |
| for (int i = 1; i < trimmed->length(); i++) { |
| trimmed->set(i, *dead); |
| } |
| heap->RightTrimFixedArray(*trimmed, trimmed->length() - 1); |
| } |
| CcTest::CollectGarbage(i::NEW_SPACE); |
| CcTest::CollectGarbage(i::OLD_SPACE); |
| } |
| |
| HEAP_TEST(InvalidatedSlotsLeftTrimFixedArray) { |
| FLAG_manual_evacuation_candidates_selection = true; |
| FLAG_parallel_compaction = false; |
| ManualGCScope manual_gc_scope; |
| CcTest::InitializeVM(); |
| Isolate* isolate = CcTest::i_isolate(); |
| Factory* factory = isolate->factory(); |
| Heap* heap = CcTest::heap(); |
| HandleScope scope(isolate); |
| PagedSpace* old_space = heap->old_space(); |
| // Allocate a dummy page to be swept be the sweeper during evacuation. |
| AllocateArrayOnFreshPage(isolate, old_space, 1); |
| Handle<FixedArray> evacuated = |
| AllocateArrayOnEvacuationCandidate(isolate, old_space, 1); |
| Handle<FixedArray> trimmed = AllocateArrayOnFreshPage(isolate, old_space, 10); |
| heap::SimulateIncrementalMarking(heap); |
| for (int i = 0; i + 1 < trimmed->length(); i++) { |
| trimmed->set(i, *evacuated); |
| } |
| { |
| HandleScope scope(isolate); |
| Handle<HeapObject> dead = factory->NewFixedArray(1); |
| for (int i = 1; i < trimmed->length(); i++) { |
| trimmed->set(i, *dead); |
| } |
| heap->LeftTrimFixedArray(*trimmed, trimmed->length() - 1); |
| } |
| CcTest::CollectGarbage(i::NEW_SPACE); |
| CcTest::CollectGarbage(i::OLD_SPACE); |
| } |
| |
| HEAP_TEST(InvalidatedSlotsFastToSlow) { |
| FLAG_manual_evacuation_candidates_selection = true; |
| FLAG_parallel_compaction = false; |
| ManualGCScope manual_gc_scope; |
| CcTest::InitializeVM(); |
| Isolate* isolate = CcTest::i_isolate(); |
| Factory* factory = isolate->factory(); |
| Heap* heap = CcTest::heap(); |
| PagedSpace* old_space = heap->old_space(); |
| |
| HandleScope scope(isolate); |
| |
| Handle<String> name = factory->InternalizeUtf8String("TestObject"); |
| Handle<String> prop_name1 = factory->InternalizeUtf8String("prop1"); |
| Handle<String> prop_name2 = factory->InternalizeUtf8String("prop2"); |
| Handle<String> prop_name3 = factory->InternalizeUtf8String("prop3"); |
| // Allocate a dummy page to be swept be the sweeper during evacuation. |
| AllocateArrayOnFreshPage(isolate, old_space, 1); |
| Handle<FixedArray> evacuated = |
| AllocateArrayOnEvacuationCandidate(isolate, old_space, 1); |
| // Allocate a dummy page to ensure that the JSObject is allocated on |
| // a fresh page. |
| AllocateArrayOnFreshPage(isolate, old_space, 1); |
| Handle<JSObject> obj; |
| { |
| AlwaysAllocateScope always_allocate(isolate); |
| Handle<JSFunction> function = factory->NewFunctionForTest(name); |
| function->shared().set_expected_nof_properties(3); |
| obj = factory->NewJSObject(function, AllocationType::kOld); |
| } |
| // Start incremental marking. |
| heap::SimulateIncrementalMarking(heap); |
| // Set properties to point to the evacuation candidate. |
| Object::SetProperty(isolate, obj, prop_name1, evacuated).Check(); |
| Object::SetProperty(isolate, obj, prop_name2, evacuated).Check(); |
| Object::SetProperty(isolate, obj, prop_name3, evacuated).Check(); |
| |
| { |
| HandleScope scope(isolate); |
| Handle<HeapObject> dead = factory->NewFixedArray(1); |
| Object::SetProperty(isolate, obj, prop_name1, dead).Check(); |
| Object::SetProperty(isolate, obj, prop_name2, dead).Check(); |
| Object::SetProperty(isolate, obj, prop_name3, dead).Check(); |
| Handle<Map> map(obj->map(), isolate); |
| Handle<Map> normalized_map = |
| Map::Normalize(isolate, map, CLEAR_INOBJECT_PROPERTIES, "testing"); |
| JSObject::MigrateToMap(isolate, obj, normalized_map); |
| } |
| CcTest::CollectGarbage(i::NEW_SPACE); |
| CcTest::CollectGarbage(i::OLD_SPACE); |
| } |
| |
| } // namespace heap |
| } // namespace internal |
| } // namespace v8 |