7b704c4f9b
Expose the steps for incremental marking and idle scavenge more directly in NewSpace. Adjust the NewSpace and Heap interfaces to allow callers to be more clear about how they are interacting with inline allocation steps. This refactor prepares the ground for more consumers of inline allocation steps (e.g. sampling heap profiler.) R=hpayer@chromium.org BUG= Review URL: https://codereview.chromium.org/1404523002 Cr-Commit-Position: refs/heads/master@{#31814}
891 lines
31 KiB
C++
891 lines
31 KiB
C++
// 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.
|
|
|
|
// TODO(mythria): Remove this define after this flag is turned on globally
|
|
#define V8_IMMINENT_DEPRECATION_WARNINGS
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "src/base/platform/platform.h"
|
|
#include "src/snapshot/snapshot.h"
|
|
#include "src/v8.h"
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/cctest/heap-tester.h"
|
|
|
|
using namespace v8::internal;
|
|
|
|
#if 0
|
|
static void VerifyRegionMarking(Address page_start) {
|
|
#ifdef ENABLE_CARDMARKING_WRITE_BARRIER
|
|
Page* p = Page::FromAddress(page_start);
|
|
|
|
p->SetRegionMarks(Page::kAllRegionsCleanMarks);
|
|
|
|
for (Address addr = p->ObjectAreaStart();
|
|
addr < p->ObjectAreaEnd();
|
|
addr += kPointerSize) {
|
|
CHECK(!Page::FromAddress(addr)->IsRegionDirty(addr));
|
|
}
|
|
|
|
for (Address addr = p->ObjectAreaStart();
|
|
addr < p->ObjectAreaEnd();
|
|
addr += kPointerSize) {
|
|
Page::FromAddress(addr)->MarkRegionDirty(addr);
|
|
}
|
|
|
|
for (Address addr = p->ObjectAreaStart();
|
|
addr < p->ObjectAreaEnd();
|
|
addr += kPointerSize) {
|
|
CHECK(Page::FromAddress(addr)->IsRegionDirty(addr));
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
// TODO(gc) you can no longer allocate pages like this. Details are hidden.
|
|
#if 0
|
|
TEST(Page) {
|
|
byte* mem = NewArray<byte>(2*Page::kPageSize);
|
|
CHECK(mem != NULL);
|
|
|
|
Address start = reinterpret_cast<Address>(mem);
|
|
Address page_start = RoundUp(start, Page::kPageSize);
|
|
|
|
Page* p = Page::FromAddress(page_start);
|
|
// Initialized Page has heap pointer, normally set by memory_allocator.
|
|
p->heap_ = CcTest::heap();
|
|
CHECK(p->address() == page_start);
|
|
CHECK(p->is_valid());
|
|
|
|
p->opaque_header = 0;
|
|
p->SetIsLargeObjectPage(false);
|
|
CHECK(!p->next_page()->is_valid());
|
|
|
|
CHECK(p->ObjectAreaStart() == page_start + Page::kObjectStartOffset);
|
|
CHECK(p->ObjectAreaEnd() == page_start + Page::kPageSize);
|
|
|
|
CHECK(p->Offset(page_start + Page::kObjectStartOffset) ==
|
|
Page::kObjectStartOffset);
|
|
CHECK(p->Offset(page_start + Page::kPageSize) == Page::kPageSize);
|
|
|
|
CHECK(p->OffsetToAddress(Page::kObjectStartOffset) == p->ObjectAreaStart());
|
|
CHECK(p->OffsetToAddress(Page::kPageSize) == p->ObjectAreaEnd());
|
|
|
|
// test region marking
|
|
VerifyRegionMarking(page_start);
|
|
|
|
DeleteArray(mem);
|
|
}
|
|
#endif
|
|
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
// Temporarily sets a given allocator in an isolate.
|
|
class TestMemoryAllocatorScope {
|
|
public:
|
|
TestMemoryAllocatorScope(Isolate* isolate, MemoryAllocator* allocator)
|
|
: isolate_(isolate),
|
|
old_allocator_(isolate->memory_allocator_) {
|
|
isolate->memory_allocator_ = allocator;
|
|
}
|
|
|
|
~TestMemoryAllocatorScope() {
|
|
isolate_->memory_allocator_ = old_allocator_;
|
|
}
|
|
|
|
private:
|
|
Isolate* isolate_;
|
|
MemoryAllocator* old_allocator_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestMemoryAllocatorScope);
|
|
};
|
|
|
|
|
|
// Temporarily sets a given code range in an isolate.
|
|
class TestCodeRangeScope {
|
|
public:
|
|
TestCodeRangeScope(Isolate* isolate, CodeRange* code_range)
|
|
: isolate_(isolate),
|
|
old_code_range_(isolate->code_range_) {
|
|
isolate->code_range_ = code_range;
|
|
}
|
|
|
|
~TestCodeRangeScope() {
|
|
isolate_->code_range_ = old_code_range_;
|
|
}
|
|
|
|
private:
|
|
Isolate* isolate_;
|
|
CodeRange* old_code_range_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestCodeRangeScope);
|
|
};
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|
|
|
|
|
|
static void VerifyMemoryChunk(Isolate* isolate,
|
|
Heap* heap,
|
|
CodeRange* code_range,
|
|
size_t reserve_area_size,
|
|
size_t commit_area_size,
|
|
size_t second_commit_area_size,
|
|
Executability executable) {
|
|
MemoryAllocator* memory_allocator = new MemoryAllocator(isolate);
|
|
CHECK(memory_allocator->SetUp(heap->MaxReserved(),
|
|
heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_allocator_scope(isolate, memory_allocator);
|
|
TestCodeRangeScope test_code_range_scope(isolate, code_range);
|
|
|
|
size_t header_size = (executable == EXECUTABLE)
|
|
? MemoryAllocator::CodePageGuardStartOffset()
|
|
: MemoryChunk::kObjectStartOffset;
|
|
size_t guard_size = (executable == EXECUTABLE)
|
|
? MemoryAllocator::CodePageGuardSize()
|
|
: 0;
|
|
|
|
MemoryChunk* memory_chunk = memory_allocator->AllocateChunk(reserve_area_size,
|
|
commit_area_size,
|
|
executable,
|
|
NULL);
|
|
size_t alignment = code_range != NULL && code_range->valid() ?
|
|
MemoryChunk::kAlignment : v8::base::OS::CommitPageSize();
|
|
size_t reserved_size =
|
|
((executable == EXECUTABLE))
|
|
? RoundUp(header_size + guard_size + reserve_area_size + guard_size,
|
|
alignment)
|
|
: RoundUp(header_size + reserve_area_size,
|
|
v8::base::OS::CommitPageSize());
|
|
CHECK(memory_chunk->size() == reserved_size);
|
|
CHECK(memory_chunk->area_start() < memory_chunk->address() +
|
|
memory_chunk->size());
|
|
CHECK(memory_chunk->area_end() <= memory_chunk->address() +
|
|
memory_chunk->size());
|
|
CHECK(static_cast<size_t>(memory_chunk->area_size()) == commit_area_size);
|
|
|
|
Address area_start = memory_chunk->area_start();
|
|
|
|
memory_chunk->CommitArea(second_commit_area_size);
|
|
CHECK(area_start == memory_chunk->area_start());
|
|
CHECK(memory_chunk->area_start() < memory_chunk->address() +
|
|
memory_chunk->size());
|
|
CHECK(memory_chunk->area_end() <= memory_chunk->address() +
|
|
memory_chunk->size());
|
|
CHECK(static_cast<size_t>(memory_chunk->area_size()) ==
|
|
second_commit_area_size);
|
|
|
|
memory_allocator->Free(memory_chunk);
|
|
memory_allocator->TearDown();
|
|
delete memory_allocator;
|
|
}
|
|
|
|
|
|
TEST(Regress3540) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
const int pageSize = Page::kPageSize;
|
|
MemoryAllocator* memory_allocator = new MemoryAllocator(isolate);
|
|
CHECK(
|
|
memory_allocator->SetUp(heap->MaxReserved(), heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_allocator_scope(isolate, memory_allocator);
|
|
CodeRange* code_range = new CodeRange(isolate);
|
|
const size_t code_range_size = 4 * pageSize;
|
|
if (!code_range->SetUp(
|
|
code_range_size +
|
|
RoundUp(v8::base::OS::CommitPageSize() * kReservedCodeRangePages,
|
|
MemoryChunk::kAlignment) +
|
|
v8::internal::MemoryAllocator::CodePageAreaSize())) {
|
|
return;
|
|
}
|
|
|
|
Address address;
|
|
size_t size;
|
|
size_t request_size = code_range_size - 2 * pageSize;
|
|
address = code_range->AllocateRawMemory(
|
|
request_size, request_size - (2 * MemoryAllocator::CodePageGuardSize()),
|
|
&size);
|
|
CHECK(address != NULL);
|
|
|
|
Address null_address;
|
|
size_t null_size;
|
|
request_size = code_range_size - pageSize;
|
|
null_address = code_range->AllocateRawMemory(
|
|
request_size, request_size - (2 * MemoryAllocator::CodePageGuardSize()),
|
|
&null_size);
|
|
CHECK(null_address == NULL);
|
|
|
|
code_range->FreeRawMemory(address, size);
|
|
delete code_range;
|
|
memory_allocator->TearDown();
|
|
delete memory_allocator;
|
|
}
|
|
|
|
|
|
static unsigned int Pseudorandom() {
|
|
static uint32_t lo = 2345;
|
|
lo = 18273 * (lo & 0xFFFFF) + (lo >> 16);
|
|
return lo & 0xFFFFF;
|
|
}
|
|
|
|
|
|
TEST(MemoryChunk) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
|
|
size_t reserve_area_size = 1 * MB;
|
|
size_t initial_commit_area_size, second_commit_area_size;
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
initial_commit_area_size = Pseudorandom();
|
|
second_commit_area_size = Pseudorandom();
|
|
|
|
// With CodeRange.
|
|
CodeRange* code_range = new CodeRange(isolate);
|
|
const size_t code_range_size = 32 * MB;
|
|
if (!code_range->SetUp(code_range_size)) return;
|
|
|
|
VerifyMemoryChunk(isolate,
|
|
heap,
|
|
code_range,
|
|
reserve_area_size,
|
|
initial_commit_area_size,
|
|
second_commit_area_size,
|
|
EXECUTABLE);
|
|
|
|
VerifyMemoryChunk(isolate,
|
|
heap,
|
|
code_range,
|
|
reserve_area_size,
|
|
initial_commit_area_size,
|
|
second_commit_area_size,
|
|
NOT_EXECUTABLE);
|
|
delete code_range;
|
|
|
|
// Without CodeRange.
|
|
code_range = NULL;
|
|
VerifyMemoryChunk(isolate,
|
|
heap,
|
|
code_range,
|
|
reserve_area_size,
|
|
initial_commit_area_size,
|
|
second_commit_area_size,
|
|
EXECUTABLE);
|
|
|
|
VerifyMemoryChunk(isolate,
|
|
heap,
|
|
code_range,
|
|
reserve_area_size,
|
|
initial_commit_area_size,
|
|
second_commit_area_size,
|
|
NOT_EXECUTABLE);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(MemoryAllocator) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
|
|
MemoryAllocator* memory_allocator = new MemoryAllocator(isolate);
|
|
CHECK(memory_allocator != nullptr);
|
|
CHECK(memory_allocator->SetUp(heap->MaxReserved(),
|
|
heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_scope(isolate, memory_allocator);
|
|
|
|
{
|
|
int total_pages = 0;
|
|
OldSpace faked_space(heap, OLD_SPACE, NOT_EXECUTABLE);
|
|
Page* first_page = memory_allocator->AllocatePage(
|
|
faked_space.AreaSize(), &faked_space, NOT_EXECUTABLE);
|
|
|
|
first_page->InsertAfter(faked_space.anchor()->prev_page());
|
|
CHECK(first_page->is_valid());
|
|
CHECK(first_page->next_page() == faked_space.anchor());
|
|
total_pages++;
|
|
|
|
for (Page* p = first_page; p != faked_space.anchor(); p = p->next_page()) {
|
|
CHECK(p->owner() == &faked_space);
|
|
}
|
|
|
|
// Again, we should get n or n - 1 pages.
|
|
Page* other = memory_allocator->AllocatePage(faked_space.AreaSize(),
|
|
&faked_space, NOT_EXECUTABLE);
|
|
CHECK(other->is_valid());
|
|
total_pages++;
|
|
other->InsertAfter(first_page);
|
|
int page_count = 0;
|
|
for (Page* p = first_page; p != faked_space.anchor(); p = p->next_page()) {
|
|
CHECK(p->owner() == &faked_space);
|
|
page_count++;
|
|
}
|
|
CHECK(total_pages == page_count);
|
|
|
|
Page* second_page = first_page->next_page();
|
|
CHECK(second_page->is_valid());
|
|
|
|
// OldSpace's destructor will tear down the space and free up all pages.
|
|
}
|
|
memory_allocator->TearDown();
|
|
delete memory_allocator;
|
|
}
|
|
|
|
|
|
TEST(NewSpace) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
MemoryAllocator* memory_allocator = new MemoryAllocator(isolate);
|
|
CHECK(memory_allocator->SetUp(heap->MaxReserved(),
|
|
heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_scope(isolate, memory_allocator);
|
|
|
|
NewSpace new_space(heap);
|
|
|
|
CHECK(new_space.SetUp(CcTest::heap()->ReservedSemiSpaceSize(),
|
|
CcTest::heap()->ReservedSemiSpaceSize()));
|
|
CHECK(new_space.HasBeenSetUp());
|
|
|
|
while (new_space.Available() >= Page::kMaxRegularHeapObjectSize) {
|
|
Object* obj =
|
|
new_space.AllocateRawUnaligned(Page::kMaxRegularHeapObjectSize)
|
|
.ToObjectChecked();
|
|
CHECK(new_space.Contains(HeapObject::cast(obj)));
|
|
}
|
|
|
|
new_space.TearDown();
|
|
memory_allocator->TearDown();
|
|
delete memory_allocator;
|
|
}
|
|
|
|
|
|
TEST(OldSpace) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
MemoryAllocator* memory_allocator = new MemoryAllocator(isolate);
|
|
CHECK(memory_allocator->SetUp(heap->MaxReserved(),
|
|
heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_scope(isolate, memory_allocator);
|
|
|
|
OldSpace* s = new OldSpace(heap, OLD_SPACE, NOT_EXECUTABLE);
|
|
CHECK(s != NULL);
|
|
|
|
CHECK(s->SetUp());
|
|
|
|
while (s->Available() > 0) {
|
|
s->AllocateRawUnaligned(Page::kMaxRegularHeapObjectSize).ToObjectChecked();
|
|
}
|
|
|
|
delete s;
|
|
memory_allocator->TearDown();
|
|
delete memory_allocator;
|
|
}
|
|
|
|
|
|
TEST(CompactionSpace) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
MemoryAllocator* memory_allocator = new MemoryAllocator(isolate);
|
|
CHECK(memory_allocator != nullptr);
|
|
CHECK(
|
|
memory_allocator->SetUp(heap->MaxReserved(), heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_scope(isolate, memory_allocator);
|
|
|
|
CompactionSpace* compaction_space =
|
|
new CompactionSpace(heap, OLD_SPACE, NOT_EXECUTABLE);
|
|
CHECK(compaction_space != NULL);
|
|
CHECK(compaction_space->SetUp());
|
|
|
|
OldSpace* old_space = new OldSpace(heap, OLD_SPACE, NOT_EXECUTABLE);
|
|
CHECK(old_space != NULL);
|
|
CHECK(old_space->SetUp());
|
|
|
|
// Cannot loop until "Available()" since we initially have 0 bytes available
|
|
// and would thus neither grow, nor be able to allocate an object.
|
|
const int kNumObjects = 100;
|
|
const int kNumObjectsPerPage =
|
|
compaction_space->AreaSize() / Page::kMaxRegularHeapObjectSize;
|
|
const int kExpectedPages =
|
|
(kNumObjects + kNumObjectsPerPage - 1) / kNumObjectsPerPage;
|
|
for (int i = 0; i < kNumObjects; i++) {
|
|
compaction_space->AllocateRawUnaligned(Page::kMaxRegularHeapObjectSize)
|
|
.ToObjectChecked();
|
|
}
|
|
int pages_in_old_space = old_space->CountTotalPages();
|
|
int pages_in_compaction_space = compaction_space->CountTotalPages();
|
|
CHECK_EQ(pages_in_compaction_space, kExpectedPages);
|
|
CHECK_LE(pages_in_old_space, 1);
|
|
|
|
old_space->MergeCompactionSpace(compaction_space);
|
|
CHECK_EQ(old_space->CountTotalPages(),
|
|
pages_in_old_space + pages_in_compaction_space);
|
|
|
|
delete compaction_space;
|
|
delete old_space;
|
|
|
|
memory_allocator->TearDown();
|
|
delete memory_allocator;
|
|
}
|
|
|
|
|
|
TEST(CompactionSpaceUsingExternalMemory) {
|
|
const int kObjectSize = 512;
|
|
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
MemoryAllocator* allocator = new MemoryAllocator(isolate);
|
|
CHECK(allocator != nullptr);
|
|
CHECK(allocator->SetUp(heap->MaxReserved(), heap->MaxExecutableSize()));
|
|
TestMemoryAllocatorScope test_scope(isolate, allocator);
|
|
|
|
CompactionSpaceCollection* collection = new CompactionSpaceCollection(heap);
|
|
CompactionSpace* compaction_space = collection->Get(OLD_SPACE);
|
|
CHECK(compaction_space != NULL);
|
|
CHECK(compaction_space->SetUp());
|
|
|
|
OldSpace* old_space = new OldSpace(heap, OLD_SPACE, NOT_EXECUTABLE);
|
|
CHECK(old_space != NULL);
|
|
CHECK(old_space->SetUp());
|
|
|
|
// The linear allocation area already counts as used bytes, making
|
|
// exact testing impossible.
|
|
heap->DisableInlineAllocation();
|
|
|
|
// Test:
|
|
// * Allocate a backing store in old_space.
|
|
// * Compute the number num_rest_objects of kObjectSize objects that fit into
|
|
// of available memory.
|
|
// kNumRestObjects.
|
|
// * Add the rest of available memory to the compaction space.
|
|
// * Allocate kNumRestObjects in the compaction space.
|
|
// * Allocate one object more.
|
|
// * Merge the compaction space and compare the expected number of pages.
|
|
|
|
// Allocate a single object in old_space to initialize a backing page.
|
|
old_space->AllocateRawUnaligned(kObjectSize).ToObjectChecked();
|
|
// Compute the number of objects that fit into the rest in old_space.
|
|
intptr_t rest = static_cast<int>(old_space->Available());
|
|
CHECK_GT(rest, 0);
|
|
intptr_t num_rest_objects = rest / kObjectSize;
|
|
// After allocating num_rest_objects in compaction_space we allocate a bit
|
|
// more.
|
|
const intptr_t kAdditionalCompactionMemory = kObjectSize;
|
|
// We expect a single old_space page.
|
|
const intptr_t kExpectedInitialOldSpacePages = 1;
|
|
// We expect a single additional page in compaction space because we mostly
|
|
// use external memory.
|
|
const intptr_t kExpectedCompactionPages = 1;
|
|
// We expect two pages to be reachable from old_space in the end.
|
|
const intptr_t kExpectedOldSpacePagesAfterMerge = 2;
|
|
|
|
CHECK_EQ(old_space->CountTotalPages(), kExpectedInitialOldSpacePages);
|
|
CHECK_EQ(compaction_space->CountTotalPages(), 0);
|
|
CHECK_EQ(compaction_space->Capacity(), 0);
|
|
// Make the rest of memory available for compaction.
|
|
old_space->DivideUponCompactionSpaces(&collection, 1, rest);
|
|
CHECK_EQ(compaction_space->CountTotalPages(), 0);
|
|
CHECK_EQ(compaction_space->Capacity(), rest);
|
|
while (num_rest_objects-- > 0) {
|
|
compaction_space->AllocateRawUnaligned(kObjectSize).ToObjectChecked();
|
|
}
|
|
// We only used external memory so far.
|
|
CHECK_EQ(compaction_space->CountTotalPages(), 0);
|
|
// Additional allocation.
|
|
compaction_space->AllocateRawUnaligned(kAdditionalCompactionMemory)
|
|
.ToObjectChecked();
|
|
// Now the compaction space shouldve also acquired a page.
|
|
CHECK_EQ(compaction_space->CountTotalPages(), kExpectedCompactionPages);
|
|
|
|
old_space->MergeCompactionSpace(compaction_space);
|
|
CHECK_EQ(old_space->CountTotalPages(), kExpectedOldSpacePagesAfterMerge);
|
|
|
|
delete collection;
|
|
delete old_space;
|
|
|
|
allocator->TearDown();
|
|
delete allocator;
|
|
}
|
|
|
|
|
|
CompactionSpaceCollection** HeapTester::InitializeCompactionSpaces(
|
|
Heap* heap, int num_spaces) {
|
|
CompactionSpaceCollection** spaces =
|
|
new CompactionSpaceCollection*[num_spaces];
|
|
for (int i = 0; i < num_spaces; i++) {
|
|
spaces[i] = new CompactionSpaceCollection(heap);
|
|
}
|
|
return spaces;
|
|
}
|
|
|
|
|
|
void HeapTester::DestroyCompactionSpaces(CompactionSpaceCollection** spaces,
|
|
int num_spaces) {
|
|
for (int i = 0; i < num_spaces; i++) {
|
|
delete spaces[i];
|
|
}
|
|
delete[] spaces;
|
|
}
|
|
|
|
|
|
void HeapTester::MergeCompactionSpaces(PagedSpace* space,
|
|
CompactionSpaceCollection** spaces,
|
|
int num_spaces) {
|
|
AllocationSpace id = space->identity();
|
|
for (int i = 0; i < num_spaces; i++) {
|
|
space->MergeCompactionSpace(spaces[i]->Get(id));
|
|
CHECK_EQ(spaces[i]->Get(id)->accounting_stats_.Size(), 0);
|
|
CHECK_EQ(spaces[i]->Get(id)->accounting_stats_.Capacity(), 0);
|
|
CHECK_EQ(spaces[i]->Get(id)->Waste(), 0);
|
|
}
|
|
}
|
|
|
|
|
|
void HeapTester::AllocateInCompactionSpaces(CompactionSpaceCollection** spaces,
|
|
AllocationSpace id, int num_spaces,
|
|
int num_objects, int object_size) {
|
|
for (int i = 0; i < num_spaces; i++) {
|
|
for (int j = 0; j < num_objects; j++) {
|
|
spaces[i]->Get(id)->AllocateRawUnaligned(object_size).ToObjectChecked();
|
|
}
|
|
spaces[i]->Get(id)->EmptyAllocationInfo();
|
|
CHECK_EQ(spaces[i]->Get(id)->accounting_stats_.Size(),
|
|
num_objects * object_size);
|
|
CHECK_GE(spaces[i]->Get(id)->accounting_stats_.Capacity(),
|
|
spaces[i]->Get(id)->accounting_stats_.Size());
|
|
}
|
|
}
|
|
|
|
|
|
void HeapTester::CompactionStats(CompactionSpaceCollection** spaces,
|
|
AllocationSpace id, int num_spaces,
|
|
intptr_t* capacity, intptr_t* size) {
|
|
*capacity = 0;
|
|
*size = 0;
|
|
for (int i = 0; i < num_spaces; i++) {
|
|
*capacity += spaces[i]->Get(id)->accounting_stats_.Capacity();
|
|
*size += spaces[i]->Get(id)->accounting_stats_.Size();
|
|
}
|
|
}
|
|
|
|
|
|
void HeapTester::TestCompactionSpaceDivide(int num_additional_objects,
|
|
int object_size,
|
|
int num_compaction_spaces,
|
|
int additional_capacity_in_bytes) {
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
Heap* heap = isolate->heap();
|
|
OldSpace* old_space = new OldSpace(heap, OLD_SPACE, NOT_EXECUTABLE);
|
|
CHECK(old_space != nullptr);
|
|
CHECK(old_space->SetUp());
|
|
old_space->AllocateRawUnaligned(object_size).ToObjectChecked();
|
|
old_space->EmptyAllocationInfo();
|
|
|
|
intptr_t rest_capacity = old_space->accounting_stats_.Capacity() -
|
|
old_space->accounting_stats_.Size();
|
|
intptr_t capacity_for_compaction_space =
|
|
rest_capacity / num_compaction_spaces;
|
|
int num_objects_in_compaction_space =
|
|
static_cast<int>(capacity_for_compaction_space) / object_size +
|
|
num_additional_objects;
|
|
CHECK_GT(num_objects_in_compaction_space, 0);
|
|
intptr_t initial_old_space_capacity = old_space->accounting_stats_.Capacity();
|
|
|
|
CompactionSpaceCollection** spaces =
|
|
InitializeCompactionSpaces(heap, num_compaction_spaces);
|
|
old_space->DivideUponCompactionSpaces(spaces, num_compaction_spaces,
|
|
capacity_for_compaction_space);
|
|
|
|
intptr_t compaction_capacity = 0;
|
|
intptr_t compaction_size = 0;
|
|
CompactionStats(spaces, OLD_SPACE, num_compaction_spaces,
|
|
&compaction_capacity, &compaction_size);
|
|
|
|
intptr_t old_space_capacity = old_space->accounting_stats_.Capacity();
|
|
intptr_t old_space_size = old_space->accounting_stats_.Size();
|
|
// Compaction space memory is subtracted from the original space's capacity.
|
|
CHECK_EQ(old_space_capacity,
|
|
initial_old_space_capacity - compaction_capacity);
|
|
CHECK_EQ(compaction_size, 0);
|
|
|
|
AllocateInCompactionSpaces(spaces, OLD_SPACE, num_compaction_spaces,
|
|
num_objects_in_compaction_space, object_size);
|
|
|
|
// Old space size and capacity should be the same as after dividing.
|
|
CHECK_EQ(old_space->accounting_stats_.Size(), old_space_size);
|
|
CHECK_EQ(old_space->accounting_stats_.Capacity(), old_space_capacity);
|
|
|
|
CompactionStats(spaces, OLD_SPACE, num_compaction_spaces,
|
|
&compaction_capacity, &compaction_size);
|
|
MergeCompactionSpaces(old_space, spaces, num_compaction_spaces);
|
|
|
|
CHECK_EQ(old_space->accounting_stats_.Capacity(),
|
|
old_space_capacity + compaction_capacity);
|
|
CHECK_EQ(old_space->accounting_stats_.Size(),
|
|
old_space_size + compaction_size);
|
|
// We check against the expected end capacity.
|
|
CHECK_EQ(old_space->accounting_stats_.Capacity(),
|
|
initial_old_space_capacity + additional_capacity_in_bytes);
|
|
|
|
DestroyCompactionSpaces(spaces, num_compaction_spaces);
|
|
delete old_space;
|
|
}
|
|
|
|
|
|
HEAP_TEST(CompactionSpaceDivideSinglePage) {
|
|
const int kObjectSize = KB;
|
|
const int kCompactionSpaces = 4;
|
|
// Since the bound for objects is tight and the dividing is best effort, we
|
|
// subtract some objects to make sure we still fit in the initial page.
|
|
// A CHECK makes sure that the overall number of allocated objects stays
|
|
// > 0.
|
|
const int kAdditionalObjects = -10;
|
|
const int kAdditionalCapacityRequired = 0;
|
|
TestCompactionSpaceDivide(kAdditionalObjects, kObjectSize, kCompactionSpaces,
|
|
kAdditionalCapacityRequired);
|
|
}
|
|
|
|
|
|
HEAP_TEST(CompactionSpaceDivideMultiplePages) {
|
|
const int kObjectSize = KB;
|
|
const int kCompactionSpaces = 4;
|
|
// Allocate half a page of objects to ensure that we need one more page per
|
|
// compaction space.
|
|
const int kAdditionalObjects = (Page::kPageSize / kObjectSize / 2);
|
|
const int kAdditionalCapacityRequired =
|
|
Page::kAllocatableMemory * kCompactionSpaces;
|
|
TestCompactionSpaceDivide(kAdditionalObjects, kObjectSize, kCompactionSpaces,
|
|
kAdditionalCapacityRequired);
|
|
}
|
|
|
|
|
|
TEST(LargeObjectSpace) {
|
|
v8::V8::Initialize();
|
|
|
|
LargeObjectSpace* lo = CcTest::heap()->lo_space();
|
|
CHECK(lo != NULL);
|
|
|
|
int lo_size = Page::kPageSize;
|
|
|
|
Object* obj = lo->AllocateRaw(lo_size, NOT_EXECUTABLE).ToObjectChecked();
|
|
CHECK(obj->IsHeapObject());
|
|
|
|
HeapObject* ho = HeapObject::cast(obj);
|
|
|
|
CHECK(lo->Contains(HeapObject::cast(obj)));
|
|
|
|
CHECK(lo->FindObject(ho->address()) == obj);
|
|
|
|
CHECK(lo->Contains(ho));
|
|
|
|
while (true) {
|
|
intptr_t available = lo->Available();
|
|
{ AllocationResult allocation = lo->AllocateRaw(lo_size, NOT_EXECUTABLE);
|
|
if (allocation.IsRetry()) break;
|
|
}
|
|
// The available value is conservative such that it may report
|
|
// zero prior to heap exhaustion.
|
|
CHECK(lo->Available() < available || available == 0);
|
|
}
|
|
|
|
CHECK(!lo->IsEmpty());
|
|
|
|
CHECK(lo->AllocateRaw(lo_size, NOT_EXECUTABLE).IsRetry());
|
|
}
|
|
|
|
|
|
TEST(SizeOfFirstPageIsLargeEnough) {
|
|
if (i::FLAG_always_opt) return;
|
|
// Bootstrapping without a snapshot causes more allocations.
|
|
CcTest::InitializeVM();
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
if (!isolate->snapshot_available()) return;
|
|
if (Snapshot::EmbedsScript(isolate)) return;
|
|
|
|
// If this test fails due to enabling experimental natives that are not part
|
|
// of the snapshot, we may need to adjust CalculateFirstPageSizes.
|
|
|
|
// Freshly initialized VM gets by with one page per space.
|
|
for (int i = FIRST_PAGED_SPACE; i <= LAST_PAGED_SPACE; i++) {
|
|
// Debug code can be very large, so skip CODE_SPACE if we are generating it.
|
|
if (i == CODE_SPACE && i::FLAG_debug_code) continue;
|
|
CHECK_EQ(1, isolate->heap()->paged_space(i)->CountTotalPages());
|
|
}
|
|
|
|
// Executing the empty script gets by with one page per space.
|
|
HandleScope scope(isolate);
|
|
CompileRun("/*empty*/");
|
|
for (int i = FIRST_PAGED_SPACE; i <= LAST_PAGED_SPACE; i++) {
|
|
// Debug code can be very large, so skip CODE_SPACE if we are generating it.
|
|
if (i == CODE_SPACE && i::FLAG_debug_code) continue;
|
|
CHECK_EQ(1, isolate->heap()->paged_space(i)->CountTotalPages());
|
|
}
|
|
|
|
// No large objects required to perform the above steps.
|
|
CHECK(isolate->heap()->lo_space()->IsEmpty());
|
|
}
|
|
|
|
|
|
UNINITIALIZED_TEST(NewSpaceGrowsToTargetCapacity) {
|
|
FLAG_target_semi_space_size = 2 * (Page::kPageSize / MB);
|
|
if (FLAG_optimize_for_size) return;
|
|
|
|
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::Context::New(isolate)->Enter();
|
|
|
|
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
|
|
|
NewSpace* new_space = i_isolate->heap()->new_space();
|
|
|
|
// This test doesn't work if we start with a non-default new space
|
|
// configuration.
|
|
if (new_space->InitialTotalCapacity() == Page::kPageSize) {
|
|
CHECK_EQ(new_space->CommittedMemory(), new_space->InitialTotalCapacity());
|
|
|
|
// Fill up the first (and only) page of the semi space.
|
|
FillCurrentPage(new_space);
|
|
|
|
// Try to allocate out of the new space. A new page should be added and
|
|
// the
|
|
// allocation should succeed.
|
|
v8::internal::AllocationResult allocation =
|
|
new_space->AllocateRawUnaligned(80);
|
|
CHECK(!allocation.IsRetry());
|
|
CHECK_EQ(new_space->CommittedMemory(), 2 * Page::kPageSize);
|
|
|
|
// Turn the allocation into a proper object so isolate teardown won't
|
|
// crash.
|
|
HeapObject* free_space = NULL;
|
|
CHECK(allocation.To(&free_space));
|
|
new_space->heap()->CreateFillerObjectAt(free_space->address(), 80);
|
|
}
|
|
}
|
|
isolate->Dispose();
|
|
}
|
|
|
|
|
|
static HeapObject* AllocateUnaligned(NewSpace* space, int size) {
|
|
AllocationResult allocation = space->AllocateRawUnaligned(size);
|
|
CHECK(!allocation.IsRetry());
|
|
HeapObject* filler = NULL;
|
|
CHECK(allocation.To(&filler));
|
|
space->heap()->CreateFillerObjectAt(filler->address(), size);
|
|
return filler;
|
|
}
|
|
|
|
class Observer : public InlineAllocationObserver {
|
|
public:
|
|
explicit Observer(intptr_t step_size)
|
|
: InlineAllocationObserver(step_size), count_(0) {}
|
|
|
|
virtual void Step(int bytes_allocated) { count_++; }
|
|
|
|
int count() const { return count_; }
|
|
|
|
private:
|
|
int count_;
|
|
};
|
|
|
|
|
|
UNINITIALIZED_TEST(InlineAllocationObserver) {
|
|
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::Context::New(isolate)->Enter();
|
|
|
|
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
|
|
|
NewSpace* new_space = i_isolate->heap()->new_space();
|
|
|
|
Observer observer1(128);
|
|
new_space->AddInlineAllocationObserver(&observer1);
|
|
|
|
// The observer should not get notified if we have only allocated less than
|
|
// 128 bytes.
|
|
AllocateUnaligned(new_space, 64);
|
|
CHECK_EQ(observer1.count(), 0);
|
|
|
|
// The observer should not get called even when we have allocated exactly
|
|
// 128 bytes.
|
|
AllocateUnaligned(new_space, 64);
|
|
CHECK_EQ(observer1.count(), 0);
|
|
|
|
// The next allocation gets the notification.
|
|
AllocateUnaligned(new_space, 8);
|
|
CHECK_EQ(observer1.count(), 1);
|
|
|
|
// Another >128 bytes should get another notification.
|
|
AllocateUnaligned(new_space, 136);
|
|
CHECK_EQ(observer1.count(), 2);
|
|
|
|
// Allocating a large object should get only one notification.
|
|
AllocateUnaligned(new_space, 1024);
|
|
CHECK_EQ(observer1.count(), 3);
|
|
|
|
// Allocating another 2048 bytes in small objects should get 12
|
|
// notifications.
|
|
for (int i = 0; i < 64; ++i) {
|
|
AllocateUnaligned(new_space, 32);
|
|
}
|
|
CHECK_EQ(observer1.count(), 15);
|
|
|
|
// Multiple observers should work.
|
|
Observer observer2(96);
|
|
new_space->AddInlineAllocationObserver(&observer2);
|
|
|
|
AllocateUnaligned(new_space, 2048);
|
|
CHECK_EQ(observer1.count(), 16);
|
|
CHECK_EQ(observer2.count(), 1);
|
|
|
|
AllocateUnaligned(new_space, 104);
|
|
CHECK_EQ(observer1.count(), 16);
|
|
CHECK_EQ(observer2.count(), 2);
|
|
|
|
// Callback should stop getting called after an observer is removed.
|
|
new_space->RemoveInlineAllocationObserver(&observer1);
|
|
|
|
AllocateUnaligned(new_space, 384);
|
|
CHECK_EQ(observer1.count(), 16); // no more notifications.
|
|
CHECK_EQ(observer2.count(), 3); // this one is still active.
|
|
|
|
new_space->RemoveInlineAllocationObserver(&observer2);
|
|
AllocateUnaligned(new_space, 384);
|
|
CHECK_EQ(observer1.count(), 16);
|
|
CHECK_EQ(observer2.count(), 3);
|
|
}
|
|
isolate->Dispose();
|
|
}
|