| // 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/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); |
| } |
| |
| 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*>(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::Handle<v8::internal::String> esh = v8::Utils::OpenHandle(*es); |
| |
| Page* page_before_gc = Page::FromHeapObject(*esh); |
| 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) { |
| 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 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) { |
| if (FLAG_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(isymbol1->IsInternalizedString()); |
| CHECK(string1->IsExternalString()); |
| CHECK(!heap->InYoungGeneration(*isymbol1)); |
| |
| // New external string in the young space. This string has the same content |
| // as the previous one (that was already internalized). |
| v8::Local<v8::String> string2 = |
| v8::String::NewFromUtf8Literal(isolate, TEST_STR); |
| 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<i::String> istring = v8::Utils::OpenHandle(*string2); |
| i::Handle<i::String> isymbol2 = factory->InternalizeString(istring); |
| CHECK(isymbol2->IsInternalizedString()); |
| CHECK(istring->IsThinString()); |
| CHECK(heap->InYoungGeneration(*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 |