// 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-inl.h" #include "src/api.h" #include "src/heap/spaces.h" #include "src/isolate.h" #include "src/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)->heap(); ExternalBackingStoreType type = ExternalBackingStoreType::kExternalString; const size_t backing_store_before = heap->old_space()->ExternalBackingStoreBytes(type); { v8::HandleScope handle_scope(isolate); v8::Local 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)->heap(); ExternalBackingStoreType type = ExternalBackingStoreType::kExternalString; const size_t backing_store_before = heap->old_space()->ExternalBackingStoreBytes(type); { v8::HandleScope handle_scope(isolate); v8::Local es = v8::String::NewExternalOneByte( isolate, new TestOneByteResource(i::StrDup(TEST_STR))).ToLocalChecked(); USE(es); } heap::GcAndSweep(heap, OLD_SPACE); const size_t backing_store_after = heap->old_space()->ExternalBackingStoreBytes(type); CHECK_EQ(0, backing_store_after - backing_store_before); } TEST(ExternalString_ExternalBackingStoreSizeIncreasesMarkCompact) { if (FLAG_never_compact) return; ManualGCScope manual_gc_scope; FLAG_manual_evacuation_candidates_selection = true; CcTest::InitializeVM(); LocalContext env; v8::Isolate* isolate = env->GetIsolate(); Heap* heap = reinterpret_cast(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 es = v8::String::NewExternalOneByte( isolate, new TestOneByteResource(i::StrDup(TEST_STR))).ToLocalChecked(); v8::internal::Handle esh = v8::Utils::OpenHandle(*es); Page* page_before_gc = Page::FromAddress(esh->address()); heap::ForceEvacuationCandidate(page_before_gc); CcTest::CollectAllGarbage(); const size_t backing_store_after = heap->old_space()->ExternalBackingStoreBytes(type); CHECK_EQ(es->Length(), backing_store_after - backing_store_before); } heap::GcAndSweep(heap, OLD_SPACE); const size_t backing_store_after = heap->old_space()->ExternalBackingStoreBytes(type); CHECK_EQ(0, backing_store_after - backing_store_before); } TEST(ExternalString_ExternalBackingStoreSizeIncreasesAfterExternalization) { CcTest::InitializeVM(); LocalContext env; v8::Isolate* isolate = env->GetIsolate(); Heap* heap = reinterpret_cast(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 str = v8::String::NewFromUtf8(isolate, TEST_STR, v8::NewStringType::kNormal) .ToLocalChecked(); CHECK_EQ(0, heap->new_space()->ExternalBackingStoreBytes(type) - new_backing_store_before); // Trigger GCs so that the newly allocated string moves to old gen. heap::GcAndSweep(heap, NEW_SPACE); // in survivor space now heap::GcAndSweep(heap, NEW_SPACE); // in old gen now bool success = str->MakeExternal(new TestOneByteResource(i::StrDup(TEST_STR))); CHECK(success); CHECK_EQ(str->Length(), heap->old_space()->ExternalBackingStoreBytes(type) - old_backing_store_before); } heap::GcAndSweep(heap, OLD_SPACE); CHECK_EQ(0, heap->old_space()->ExternalBackingStoreBytes(type) - old_backing_store_before); } TEST(ExternalString_PromotedThinString) { 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 string1 = factory ->NewExternalStringFromOneByte( new TestOneByteResource(i::StrDup(TEST_STR))) .ToHandleChecked(); // Internalize external string. i::Handle isymbol1 = factory->InternalizeString(string1); CHECK(isymbol1->IsInternalizedString()); CHECK(string1->IsExternalString()); CHECK(!heap->InNewSpace(*isymbol1)); // New external string in the young space. This string has the same content // as the previous one (that was already internalized). v8::Local string2 = v8::String::NewFromUtf8(isolate, TEST_STR, v8::NewStringType::kNormal) .ToLocalChecked(); bool success = string2->MakeExternal(new TestOneByteResource(i::StrDup(TEST_STR))); CHECK(success); // Internalize (it will create a thin string in the new space). i::Handle istring = v8::Utils::OpenHandle(*string2); i::Handle isymbol2 = factory->InternalizeString(istring); CHECK(isymbol2->IsInternalizedString()); CHECK(istring->IsThinString()); CHECK(heap->InNewSpace(*istring)); // Collect thin string. References to the thin string will be updated to // point to the actual external string in the old space. heap::GcAndSweep(heap, NEW_SPACE); USE(isymbol1); USE(isymbol2); } } } // namespace heap } // namespace internal } // namespace v8 #undef TEST_STR