|  | // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  |  | 
|  | #include "platform/globals.h" | 
|  |  | 
|  | #include "platform/assert.h" | 
|  | #include "vm/class_finalizer.h" | 
|  | #include "vm/dart_api_impl.h" | 
|  | #include "vm/globals.h" | 
|  | #include "vm/heap/become.h" | 
|  | #include "vm/heap/heap.h" | 
|  | #include "vm/message_handler.h" | 
|  | #include "vm/message_snapshot.h" | 
|  | #include "vm/object_graph.h" | 
|  | #include "vm/port.h" | 
|  | #include "vm/symbols.h" | 
|  | #include "vm/unit_test.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | TEST_CASE(OldGC) { | 
|  | const char* kScriptChars = | 
|  | "main() {\n" | 
|  | "  return [1, 2, 3];\n" | 
|  | "}\n"; | 
|  | NOT_IN_PRODUCT(FLAG_verbose_gc = true); | 
|  | Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); | 
|  | Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); | 
|  |  | 
|  | EXPECT_VALID(result); | 
|  | EXPECT(!Dart_IsNull(result)); | 
|  | EXPECT(Dart_IsList(result)); | 
|  | TransitionNativeToVM transition(thread); | 
|  | GCTestHelper::CollectOldSpace(); | 
|  | } | 
|  |  | 
|  | #if !defined(PRODUCT) | 
|  | TEST_CASE(OldGC_Unsync) { | 
|  | // Finalize any GC in progress as it is unsafe to change FLAG_marker_tasks | 
|  | // when incremental marking is in progress. | 
|  | { | 
|  | TransitionNativeToVM transition(thread); | 
|  | GCTestHelper::CollectAllGarbage(); | 
|  | } | 
|  | FLAG_marker_tasks = 0; | 
|  |  | 
|  | const char* kScriptChars = | 
|  | "main() {\n" | 
|  | "  return [1, 2, 3];\n" | 
|  | "}\n"; | 
|  | FLAG_verbose_gc = true; | 
|  | Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); | 
|  | Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); | 
|  |  | 
|  | EXPECT_VALID(result); | 
|  | EXPECT(!Dart_IsNull(result)); | 
|  | EXPECT(Dart_IsList(result)); | 
|  | TransitionNativeToVM transition(thread); | 
|  | GCTestHelper::CollectOldSpace(); | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | TEST_CASE(LargeSweep) { | 
|  | const char* kScriptChars = | 
|  | "main() {\n" | 
|  | "  return List.filled(8 * 1024 * 1024, null);\n" | 
|  | "}\n"; | 
|  | NOT_IN_PRODUCT(FLAG_verbose_gc = true); | 
|  | Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); | 
|  | Dart_EnterScope(); | 
|  | Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); | 
|  |  | 
|  | EXPECT_VALID(result); | 
|  | EXPECT(!Dart_IsNull(result)); | 
|  | EXPECT(Dart_IsList(result)); | 
|  | { | 
|  | TransitionNativeToVM transition(thread); | 
|  | GCTestHelper::CollectOldSpace(); | 
|  | } | 
|  | Dart_ExitScope(); | 
|  | { | 
|  | TransitionNativeToVM transition(thread); | 
|  | GCTestHelper::CollectOldSpace(); | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | static ClassPtr GetClass(const Library& lib, const char* name) { | 
|  | const Class& cls = Class::Handle( | 
|  | lib.LookupClass(String::Handle(Symbols::New(Thread::Current(), name)))); | 
|  | EXPECT(!cls.IsNull());  // No ambiguity error expected. | 
|  | return cls.ptr(); | 
|  | } | 
|  |  | 
|  | TEST_CASE(ClassHeapStats) { | 
|  | const char* kScriptChars = | 
|  | "class A {\n" | 
|  | "  var a;\n" | 
|  | "  var b;\n" | 
|  | "}\n" | 
|  | "" | 
|  | "main() {\n" | 
|  | "  var x = new A();\n" | 
|  | "  return new A();\n" | 
|  | "}\n"; | 
|  | Dart_Handle h_lib = TestCase::LoadTestScript(kScriptChars, NULL); | 
|  | auto isolate_group = IsolateGroup::Current(); | 
|  | ClassTable* class_table = isolate_group->class_table(); | 
|  | { | 
|  | // GC before main so allocations during the tests don't cause unexpected GC. | 
|  | TransitionNativeToVM transition(thread); | 
|  | GCTestHelper::CollectAllGarbage(); | 
|  | } | 
|  | Dart_EnterScope(); | 
|  | Dart_Handle result = Dart_Invoke(h_lib, NewString("main"), 0, NULL); | 
|  | EXPECT_VALID(result); | 
|  | EXPECT(!Dart_IsNull(result)); | 
|  | intptr_t cid; | 
|  | { | 
|  | TransitionNativeToVM transition(thread); | 
|  | Library& lib = Library::Handle(); | 
|  | lib ^= Api::UnwrapHandle(h_lib); | 
|  | EXPECT(!lib.IsNull()); | 
|  | const Class& cls = Class::Handle(GetClass(lib, "A")); | 
|  | ASSERT(!cls.IsNull()); | 
|  | cid = cls.id(); | 
|  |  | 
|  | { | 
|  | // Verify preconditions: allocated twice in new space. | 
|  | CountObjectsVisitor visitor(thread, class_table->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_EQ(2, visitor.new_count_[cid]); | 
|  | EXPECT_EQ(0, visitor.old_count_[cid]); | 
|  | } | 
|  |  | 
|  | // Perform GC. | 
|  | GCTestHelper::CollectNewSpace(); | 
|  |  | 
|  | { | 
|  | // Verify postconditions: Only one survived. | 
|  | CountObjectsVisitor visitor(thread, class_table->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_EQ(1, visitor.new_count_[cid]); | 
|  | EXPECT_EQ(0, visitor.old_count_[cid]); | 
|  | } | 
|  |  | 
|  | // Perform GC. The following is heavily dependent on the behaviour | 
|  | // of the GC: Retained instance of A will be promoted. | 
|  | GCTestHelper::CollectNewSpace(); | 
|  |  | 
|  | { | 
|  | // Verify postconditions: One promoted instance. | 
|  | CountObjectsVisitor visitor(thread, class_table->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_EQ(0, visitor.new_count_[cid]); | 
|  | EXPECT_EQ(1, visitor.old_count_[cid]); | 
|  | } | 
|  |  | 
|  | // Perform a GC on new space. | 
|  | GCTestHelper::CollectNewSpace(); | 
|  |  | 
|  | { | 
|  | // Verify postconditions: | 
|  | CountObjectsVisitor visitor(thread, class_table->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_EQ(0, visitor.new_count_[cid]); | 
|  | EXPECT_EQ(1, visitor.old_count_[cid]); | 
|  | } | 
|  |  | 
|  | GCTestHelper::CollectOldSpace(); | 
|  |  | 
|  | { | 
|  | // Verify postconditions: | 
|  | CountObjectsVisitor visitor(thread, class_table->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_EQ(0, visitor.new_count_[cid]); | 
|  | EXPECT_EQ(1, visitor.old_count_[cid]); | 
|  | } | 
|  | } | 
|  | // Exit scope, freeing instance. | 
|  | Dart_ExitScope(); | 
|  | { | 
|  | TransitionNativeToVM transition(thread); | 
|  | // Perform GC. | 
|  | GCTestHelper::CollectOldSpace(); | 
|  | { | 
|  | // Verify postconditions: | 
|  | CountObjectsVisitor visitor(thread, class_table->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_EQ(0, visitor.new_count_[cid]); | 
|  | EXPECT_EQ(0, visitor.old_count_[cid]); | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif  // !PRODUCT | 
|  |  | 
|  | class FindOnly : public FindObjectVisitor { | 
|  | public: | 
|  | explicit FindOnly(ObjectPtr target) : target_(target) { | 
|  | #if defined(DEBUG) | 
|  | EXPECT_GT(Thread::Current()->no_safepoint_scope_depth(), 0); | 
|  | #endif | 
|  | } | 
|  | virtual ~FindOnly() {} | 
|  |  | 
|  | virtual bool FindObject(ObjectPtr obj) const { return obj == target_; } | 
|  |  | 
|  | private: | 
|  | ObjectPtr target_; | 
|  | }; | 
|  |  | 
|  | class FindNothing : public FindObjectVisitor { | 
|  | public: | 
|  | FindNothing() {} | 
|  | virtual ~FindNothing() {} | 
|  | virtual bool FindObject(ObjectPtr obj) const { return false; } | 
|  | }; | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(FindObject) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  | Heap::Space spaces[2] = {Heap::kOld, Heap::kNew}; | 
|  | for (size_t space = 0; space < ARRAY_SIZE(spaces); ++space) { | 
|  | const String& obj = String::Handle(String::New("x", spaces[space])); | 
|  | { | 
|  | HeapIterationScope iteration(thread); | 
|  | NoSafepointScope no_safepoint; | 
|  | FindOnly find_only(obj.ptr()); | 
|  | EXPECT(obj.ptr() == heap->FindObject(&find_only)); | 
|  | } | 
|  | } | 
|  | { | 
|  | HeapIterationScope iteration(thread); | 
|  | NoSafepointScope no_safepoint; | 
|  | FindNothing find_nothing; | 
|  | EXPECT(Object::null() == heap->FindObject(&find_nothing)); | 
|  | } | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(IterateReadOnly) { | 
|  | const String& obj = String::Handle(String::New("x", Heap::kOld)); | 
|  |  | 
|  | // It is not safe to make the heap read-only if marking or sweeping is in | 
|  | // progress. | 
|  | GCTestHelper::WaitForGCTasks(); | 
|  |  | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  | EXPECT(heap->Contains(UntaggedObject::ToAddr(obj.ptr()))); | 
|  | heap->WriteProtect(true); | 
|  | EXPECT(heap->Contains(UntaggedObject::ToAddr(obj.ptr()))); | 
|  | heap->WriteProtect(false); | 
|  | EXPECT(heap->Contains(UntaggedObject::ToAddr(obj.ptr()))); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_DeadOldToNew) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | old.SetAt(0, neu); | 
|  | old = Array::null(); | 
|  | neu = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT_EQ(size_before, size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_DeadNewToOld) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | neu.SetAt(0, old); | 
|  | old = Array::null(); | 
|  | neu = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT_EQ(size_before, size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_DeadGenCycle) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | neu.SetAt(0, old); | 
|  | old.SetAt(0, neu); | 
|  | old = Array::null(); | 
|  | neu = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT_EQ(size_before, size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveNewToOld) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | neu.SetAt(0, old); | 
|  | old = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT(size_before < size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveOldToNew) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | old.SetAt(0, neu); | 
|  | neu = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT(size_before < size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveOldDeadNew) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | neu = Array::null(); | 
|  | old.SetAt(0, old); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT(size_before < size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveNewDeadOld) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | old = Array::null(); | 
|  | neu.SetAt(0, neu); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | heap->WaitForMarkerTasks(thread);  // Finalize marking to get live size. | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT(size_before < size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveNewToOldChain) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& old2 = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | old.SetAt(0, old2); | 
|  | neu.SetAt(0, old); | 
|  | old = Array::null(); | 
|  | old2 = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT(size_before < size_after); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(CollectAllGarbage_LiveOldToNewChain) { | 
|  | Heap* heap = IsolateGroup::Current()->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | intptr_t size_before = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | Array& neu = Array::Handle(Array::New(1, Heap::kNew)); | 
|  | Array& neu2 = Array::Handle(Array::New(1, Heap::kOld)); | 
|  | neu.SetAt(0, neu2); | 
|  | old.SetAt(0, neu); | 
|  | neu = Array::null(); | 
|  | neu2 = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  |  | 
|  | intptr_t size_after = | 
|  | heap->new_space()->UsedInWords() + heap->old_space()->UsedInWords(); | 
|  |  | 
|  | EXPECT(size_before < size_after); | 
|  | } | 
|  |  | 
|  | static void NoopFinalizer(void* isolate_callback_data, void* peer) {} | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(ExternalPromotion) { | 
|  | auto isolate_group = IsolateGroup::Current(); | 
|  | Heap* heap = isolate_group->heap(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  | intptr_t size_before = kWordSize * (heap->new_space()->ExternalInWords() + | 
|  | heap->old_space()->ExternalInWords()); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(100, Heap::kOld)); | 
|  | Array& neu = Array::Handle(); | 
|  | for (intptr_t i = 0; i < 100; i++) { | 
|  | neu = Array::New(1, Heap::kNew); | 
|  | FinalizablePersistentHandle::New(isolate_group, neu, NULL, NoopFinalizer, | 
|  | 1 * MB, | 
|  | /*auto_delete=*/true); | 
|  | old.SetAt(i, neu); | 
|  | } | 
|  |  | 
|  | intptr_t size_middle = kWordSize * (heap->new_space()->ExternalInWords() + | 
|  | heap->old_space()->ExternalInWords()); | 
|  | EXPECT_EQ(size_before + 100 * MB, size_middle); | 
|  |  | 
|  | old = Array::null(); | 
|  | neu = Array::null(); | 
|  |  | 
|  | heap->CollectAllGarbage(); | 
|  |  | 
|  | intptr_t size_after = kWordSize * (heap->new_space()->ExternalInWords() + | 
|  | heap->old_space()->ExternalInWords()); | 
|  |  | 
|  | EXPECT_EQ(size_before, size_after); | 
|  | } | 
|  |  | 
|  | #if !defined(PRODUCT) | 
|  | class HeapTestHelper { | 
|  | public: | 
|  | static void Scavenge(Thread* thread) { | 
|  | thread->heap()->CollectNewSpaceGarbage(thread, Heap::kDebugging); | 
|  | } | 
|  | static void MarkSweep(Thread* thread) { | 
|  | thread->heap()->CollectOldSpaceGarbage(thread, Heap::kMarkSweep, | 
|  | Heap::kDebugging); | 
|  | thread->heap()->WaitForMarkerTasks(thread); | 
|  | thread->heap()->WaitForSweeperTasks(thread); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class SendAndExitMessagesHandler : public MessageHandler { | 
|  | public: | 
|  | explicit SendAndExitMessagesHandler(Isolate* owner) | 
|  | : msg_(Utils::CreateCStringUniquePtr(nullptr)), owner_(owner) {} | 
|  |  | 
|  | const char* name() const { return "merge-isolates-heaps-handler"; } | 
|  |  | 
|  | ~SendAndExitMessagesHandler() { PortMap::ClosePorts(this); } | 
|  |  | 
|  | MessageStatus HandleMessage(std::unique_ptr<Message> message) { | 
|  | // Parse the message. | 
|  | Object& response_obj = Object::Handle(); | 
|  | if (message->IsRaw()) { | 
|  | response_obj = message->raw_obj(); | 
|  | } else if (message->IsPersistentHandle()) { | 
|  | PersistentHandle* handle = message->persistent_handle(); | 
|  | // Object is in the receiving isolate's heap. | 
|  | EXPECT(isolate()->group()->heap()->Contains( | 
|  | UntaggedObject::ToAddr(handle->ptr()))); | 
|  | response_obj = handle->ptr(); | 
|  | isolate()->group()->api_state()->FreePersistentHandle(handle); | 
|  | } else { | 
|  | Thread* thread = Thread::Current(); | 
|  | response_obj = ReadMessage(thread, message.get()); | 
|  | } | 
|  | if (response_obj.IsString()) { | 
|  | String& response = String::Handle(); | 
|  | response ^= response_obj.ptr(); | 
|  | msg_.reset(Utils::StrDup(response.ToCString())); | 
|  | } else { | 
|  | ASSERT(response_obj.IsArray()); | 
|  | Array& response_array = Array::Handle(); | 
|  | response_array ^= response_obj.ptr(); | 
|  | ASSERT(response_array.Length() == 1); | 
|  | ExternalTypedData& response = ExternalTypedData::Handle(); | 
|  | response ^= response_array.At(0); | 
|  | msg_.reset(Utils::StrDup(reinterpret_cast<char*>(response.DataAddr(0)))); | 
|  | } | 
|  |  | 
|  | return kOK; | 
|  | } | 
|  |  | 
|  | const char* msg() const { return msg_.get(); } | 
|  |  | 
|  | virtual Isolate* isolate() const { return owner_; } | 
|  |  | 
|  | private: | 
|  | Utils::CStringUniquePtr msg_; | 
|  | Isolate* owner_; | 
|  | }; | 
|  |  | 
|  | VM_UNIT_TEST_CASE(CleanupBequestNeverReceived) { | 
|  | // This test uses features from isolate groups | 
|  | FLAG_enable_isolate_groups = true; | 
|  |  | 
|  | const char* TEST_MESSAGE = "hello, world"; | 
|  | Dart_Isolate parent = TestCase::CreateTestIsolate("parent"); | 
|  | EXPECT_EQ(parent, Dart_CurrentIsolate()); | 
|  | { | 
|  | SendAndExitMessagesHandler handler(Isolate::Current()); | 
|  | Dart_Port port_id = PortMap::CreatePort(&handler); | 
|  | EXPECT_EQ(PortMap::GetIsolate(port_id), Isolate::Current()); | 
|  | Dart_ExitIsolate(); | 
|  |  | 
|  | Dart_Isolate worker = TestCase::CreateTestIsolateInGroup("worker", parent); | 
|  | EXPECT_EQ(worker, Dart_CurrentIsolate()); | 
|  | { | 
|  | Thread* thread = Thread::Current(); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone zone(thread); | 
|  | HANDLESCOPE(thread); | 
|  |  | 
|  | String& string = String::Handle(String::New(TEST_MESSAGE)); | 
|  | PersistentHandle* handle = | 
|  | Isolate::Current()->group()->api_state()->AllocatePersistentHandle(); | 
|  | handle->set_ptr(string.ptr()); | 
|  |  | 
|  | reinterpret_cast<Isolate*>(worker)->bequeath( | 
|  | std::unique_ptr<Bequest>(new Bequest(handle, port_id))); | 
|  | } | 
|  | } | 
|  | Dart_ShutdownIsolate(); | 
|  | Dart_EnterIsolate(parent); | 
|  | Dart_ShutdownIsolate(); | 
|  | } | 
|  |  | 
|  | VM_UNIT_TEST_CASE(ReceivesSendAndExitMessage) { | 
|  | // This test uses features from isolate groups | 
|  | FLAG_enable_isolate_groups = true; | 
|  |  | 
|  | const char* TEST_MESSAGE = "hello, world"; | 
|  | Dart_Isolate parent = TestCase::CreateTestIsolate("parent"); | 
|  | EXPECT_EQ(parent, Dart_CurrentIsolate()); | 
|  | SendAndExitMessagesHandler handler(Isolate::Current()); | 
|  | Dart_Port port_id = PortMap::CreatePort(&handler); | 
|  | EXPECT_EQ(PortMap::GetIsolate(port_id), Isolate::Current()); | 
|  | Dart_ExitIsolate(); | 
|  |  | 
|  | Dart_Isolate worker = TestCase::CreateTestIsolateInGroup("worker", parent); | 
|  | EXPECT_EQ(worker, Dart_CurrentIsolate()); | 
|  | { | 
|  | Thread* thread = Thread::Current(); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone zone(thread); | 
|  | HANDLESCOPE(thread); | 
|  |  | 
|  | String& string = String::Handle(String::New(TEST_MESSAGE)); | 
|  |  | 
|  | PersistentHandle* handle = | 
|  | Isolate::Current()->group()->api_state()->AllocatePersistentHandle(); | 
|  | handle->set_ptr(string.ptr()); | 
|  |  | 
|  | reinterpret_cast<Isolate*>(worker)->bequeath( | 
|  | std::unique_ptr<Bequest>(new Bequest(handle, port_id))); | 
|  | } | 
|  |  | 
|  | Dart_ShutdownIsolate(); | 
|  | Dart_EnterIsolate(parent); | 
|  | { | 
|  | Thread* thread = Thread::Current(); | 
|  | TransitionNativeToVM transition(thread); | 
|  | StackZone zone(thread); | 
|  | HANDLESCOPE(thread); | 
|  |  | 
|  | EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage()); | 
|  | } | 
|  | EXPECT_STREQ(handler.msg(), TEST_MESSAGE); | 
|  | Dart_ShutdownIsolate(); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(ExternalAllocationStats) { | 
|  | auto isolate_group = thread->isolate_group(); | 
|  | Heap* heap = isolate_group->heap(); | 
|  |  | 
|  | Array& old = Array::Handle(Array::New(100, Heap::kOld)); | 
|  | Array& neu = Array::Handle(); | 
|  | for (intptr_t i = 0; i < 100; i++) { | 
|  | neu = Array::New(1, Heap::kNew); | 
|  | FinalizablePersistentHandle::New(isolate_group, neu, NULL, NoopFinalizer, | 
|  | 1 * MB, | 
|  | /*auto_delete=*/true); | 
|  | old.SetAt(i, neu); | 
|  |  | 
|  | if ((i % 4) == 0) { | 
|  | HeapTestHelper::MarkSweep(thread); | 
|  | } else { | 
|  | HeapTestHelper::Scavenge(thread); | 
|  | } | 
|  |  | 
|  | CountObjectsVisitor visitor(thread, | 
|  | isolate_group->class_table()->NumCids()); | 
|  | HeapIterationScope iter(thread); | 
|  | iter.IterateObjects(&visitor); | 
|  | isolate_group->VisitWeakPersistentHandles(&visitor); | 
|  | EXPECT_LE(visitor.old_external_size_[kArrayCid], | 
|  | heap->old_space()->ExternalInWords() * kWordSize); | 
|  | EXPECT_LE(visitor.new_external_size_[kArrayCid], | 
|  | heap->new_space()->ExternalInWords() * kWordSize); | 
|  | } | 
|  | } | 
|  | #endif  // !defined(PRODUCT) | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(ArrayTruncationRaces) { | 
|  | // Alternate between allocating new lists and truncating. | 
|  | // For each list, the life cycle is | 
|  | // 1) the list is allocated and filled with some elements | 
|  | // 2) kNumLists other lists are allocated | 
|  | // 3) the list's backing store is truncated; the list becomes unreachable | 
|  | // 4) kNumLists other lists are allocated | 
|  | // 5) the backing store becomes unreachable | 
|  | // The goal is to cause truncation *during* concurrent mark or sweep, by | 
|  | // truncating an array that had been alive for a while and will be visited by | 
|  | // a GC triggering by the allocations in step 2. | 
|  |  | 
|  | intptr_t kMaxListLength = 100; | 
|  | intptr_t kNumLists = 1000; | 
|  | Array& lists = Array::Handle(Array::New(kNumLists)); | 
|  | Array& arrays = Array::Handle(Array::New(kNumLists)); | 
|  |  | 
|  | GrowableObjectArray& list = GrowableObjectArray::Handle(); | 
|  | Array& array = Array::Handle(); | 
|  | Object& element = Object::Handle(); | 
|  |  | 
|  | for (intptr_t i = 0; i < kNumLists; i++) { | 
|  | list = GrowableObjectArray::New(Heap::kNew); | 
|  | intptr_t length = i % kMaxListLength; | 
|  | for (intptr_t j = 0; j < length; j++) { | 
|  | list.Add(element, Heap::kNew); | 
|  | } | 
|  | lists.SetAt(i, list); | 
|  | } | 
|  |  | 
|  | intptr_t kTruncations = 100000; | 
|  | for (intptr_t i = 0; i < kTruncations; i++) { | 
|  | list ^= lists.At(i % kNumLists); | 
|  | array = Array::MakeFixedLength(list); | 
|  | arrays.SetAt(i % kNumLists, array); | 
|  |  | 
|  | list = GrowableObjectArray::New(Heap::kOld); | 
|  | intptr_t length = i % kMaxListLength; | 
|  | for (intptr_t j = 0; j < length; j++) { | 
|  | list.Add(element, Heap::kOld); | 
|  | } | 
|  | lists.SetAt(i % kNumLists, list); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace dart |