| // Copyright 2016 The Chromium 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 "platform/heap/HeapCompact.h" | 
 |  | 
 | #include "platform/heap/Handle.h" | 
 | #include "platform/heap/HeapTestUtilities.h" | 
 | #include "platform/heap/SparseHeapBitmap.h" | 
 | #include "platform/wtf/Deque.h" | 
 | #include "platform/wtf/HashMap.h" | 
 | #include "platform/wtf/LinkedHashSet.h" | 
 | #include "platform/wtf/Vector.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | #include <memory> | 
 |  | 
 | namespace { | 
 |  | 
 | enum VerifyArenaCompaction { | 
 |   NoVerify, | 
 |   VectorsAreCompacted, | 
 |   HashTablesAreCompacted, | 
 | }; | 
 |  | 
 | class IntWrapper : public blink::GarbageCollectedFinalized<IntWrapper> { | 
 |  public: | 
 |   static IntWrapper* Create(int x, VerifyArenaCompaction verify = NoVerify) { | 
 |     return new IntWrapper(x, verify); | 
 |   } | 
 |  | 
 |   virtual ~IntWrapper() = default; | 
 |  | 
 |   void Trace(blink::Visitor* visitor) { | 
 |     // Verify if compaction is indeed activated. | 
 |     // | 
 |     // What arenas end up being compacted is dependent on residency, | 
 |     // so approximate the arena checks to fit. | 
 |     blink::HeapCompact* compaction = visitor->Heap().Compaction(); | 
 |     switch (verify_) { | 
 |       case NoVerify: | 
 |         return; | 
 |       case HashTablesAreCompacted: | 
 |         CHECK(compaction->IsCompactingArena( | 
 |             blink::BlinkGC::kHashTableArenaIndex)); | 
 |         return; | 
 |       case VectorsAreCompacted: | 
 |         CHECK(compaction->IsCompactingVectorArenas()); | 
 |         return; | 
 |     } | 
 |   } | 
 |  | 
 |   int Value() const { return x_; } | 
 |  | 
 |   bool operator==(const IntWrapper& other) const { | 
 |     return other.Value() == Value(); | 
 |   } | 
 |  | 
 |   unsigned GetHash() { return IntHash<int>::GetHash(x_); } | 
 |  | 
 |   IntWrapper(int x, VerifyArenaCompaction verify) : x_(x), verify_(verify) {} | 
 |  | 
 |  private: | 
 |   IntWrapper() = delete; | 
 |  | 
 |   int x_; | 
 |   VerifyArenaCompaction verify_; | 
 | }; | 
 | static_assert(WTF::IsTraceable<IntWrapper>::value, | 
 |               "IsTraceable<> template failed to recognize trace method."); | 
 |  | 
 | }  // namespace | 
 |  | 
 | #if ENABLE_HEAP_COMPACTION | 
 |  | 
 | using IntVector = blink::HeapVector<blink::Member<IntWrapper>>; | 
 | using IntDeque = blink::HeapDeque<blink::Member<IntWrapper>>; | 
 | using IntMap = blink::HeapHashMap<blink::Member<IntWrapper>, int>; | 
 | // TODO(sof): decide if this ought to be a global trait specialization. | 
 | // (i.e., for HeapHash*<T>.) | 
 | WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(IntMap); | 
 |  | 
 | namespace blink { | 
 |  | 
 | static const size_t kChunkRange = SparseHeapBitmap::kBitmapChunkRange; | 
 | static const size_t kUnitPointer = 0x1u | 
 |                                    << SparseHeapBitmap::kPointerAlignmentInBits; | 
 |  | 
 | TEST(HeapCompactTest, SparseBitmapBasic) { | 
 |   Address base = reinterpret_cast<Address>(0x10000u); | 
 |   std::unique_ptr<SparseHeapBitmap> bitmap = SparseHeapBitmap::Create(base); | 
 |  | 
 |   size_t double_chunk = 2 * kChunkRange; | 
 |  | 
 |   // 101010... starting at |base|. | 
 |   for (size_t i = 0; i < double_chunk; i += 2 * kUnitPointer) | 
 |     bitmap->Add(base + i); | 
 |  | 
 |   // Check that hasRange() returns a bitmap subtree, if any, for a given | 
 |   // address. | 
 |   EXPECT_TRUE(!!bitmap->HasRange(base, 1)); | 
 |   EXPECT_TRUE(!!bitmap->HasRange(base + kUnitPointer, 1)); | 
 |   EXPECT_FALSE(!!bitmap->HasRange(base - kUnitPointer, 1)); | 
 |  | 
 |   // Test implementation details.. that each SparseHeapBitmap node maps | 
 |   // |s_bitmapChunkRange| ranges only. | 
 |   EXPECT_EQ(bitmap->HasRange(base + kUnitPointer, 1), | 
 |             bitmap->HasRange(base + 2 * kUnitPointer, 1)); | 
 |   // Second range will be just past the first. | 
 |   EXPECT_NE(bitmap->HasRange(base, 1), bitmap->HasRange(base + kChunkRange, 1)); | 
 |  | 
 |   // Iterate a range that will encompass more than one 'chunk'. | 
 |   SparseHeapBitmap* start = | 
 |       bitmap->HasRange(base + 2 * kUnitPointer, double_chunk); | 
 |   EXPECT_TRUE(!!start); | 
 |   for (size_t i = 2 * kUnitPointer; i < double_chunk; i += 2 * kUnitPointer) { | 
 |     EXPECT_TRUE(start->IsSet(base + i)); | 
 |     EXPECT_FALSE(start->IsSet(base + i + kUnitPointer)); | 
 |   } | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, SparseBitmapBuild) { | 
 |   Address base = reinterpret_cast<Address>(0x10000u); | 
 |   std::unique_ptr<SparseHeapBitmap> bitmap = SparseHeapBitmap::Create(base); | 
 |  | 
 |   size_t double_chunk = 2 * kChunkRange; | 
 |  | 
 |   // Create a sparse bitmap containing at least three chunks. | 
 |   bitmap->Add(base - double_chunk); | 
 |   bitmap->Add(base + double_chunk); | 
 |  | 
 |   // This is sanity testing internal implementation details of | 
 |   // SparseHeapBitmap; probing |isSet()| outside the bitmap | 
 |   // of the range used in |hasRange()|, is not defined. | 
 |   // | 
 |   // Regardless, the testing here verifies that a |hasRange()| that | 
 |   // straddles multiple internal nodes, returns a bitmap that is | 
 |   // capable of returning correct |isSet()| results. | 
 |   SparseHeapBitmap* start = bitmap->HasRange( | 
 |       base - double_chunk - 2 * kUnitPointer, 4 * kUnitPointer); | 
 |   EXPECT_TRUE(!!start); | 
 |   EXPECT_TRUE(start->IsSet(base - double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base - double_chunk + kUnitPointer)); | 
 |   EXPECT_FALSE(start->IsSet(base)); | 
 |   EXPECT_FALSE(start->IsSet(base + kUnitPointer)); | 
 |   EXPECT_FALSE(start->IsSet(base + double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base + double_chunk + kUnitPointer)); | 
 |  | 
 |   start = bitmap->HasRange(base - double_chunk - 2 * kUnitPointer, | 
 |                            2 * double_chunk + 2 * kUnitPointer); | 
 |   EXPECT_TRUE(!!start); | 
 |   EXPECT_TRUE(start->IsSet(base - double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base - double_chunk + kUnitPointer)); | 
 |   EXPECT_TRUE(start->IsSet(base)); | 
 |   EXPECT_FALSE(start->IsSet(base + kUnitPointer)); | 
 |   EXPECT_TRUE(start->IsSet(base + double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base + double_chunk + kUnitPointer)); | 
 |  | 
 |   start = bitmap->HasRange(base, 20); | 
 |   EXPECT_TRUE(!!start); | 
 |   // Probing for values outside of hasRange() should be considered | 
 |   // undefined, but do it to exercise the (left) tree traversal. | 
 |   EXPECT_TRUE(start->IsSet(base - double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base - double_chunk + kUnitPointer)); | 
 |   EXPECT_TRUE(start->IsSet(base)); | 
 |   EXPECT_FALSE(start->IsSet(base + kUnitPointer)); | 
 |   EXPECT_TRUE(start->IsSet(base + double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base + double_chunk + kUnitPointer)); | 
 |  | 
 |   start = bitmap->HasRange(base + kChunkRange + 2 * kUnitPointer, 2048); | 
 |   EXPECT_TRUE(!!start); | 
 |   // Probing for values outside of hasRange() should be considered | 
 |   // undefined, but do it to exercise node traversal. | 
 |   EXPECT_FALSE(start->IsSet(base - double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base - double_chunk + kUnitPointer)); | 
 |   EXPECT_FALSE(start->IsSet(base)); | 
 |   EXPECT_FALSE(start->IsSet(base + kUnitPointer)); | 
 |   EXPECT_FALSE(start->IsSet(base + kChunkRange)); | 
 |   EXPECT_TRUE(start->IsSet(base + double_chunk)); | 
 |   EXPECT_FALSE(start->IsSet(base + double_chunk + kUnitPointer)); | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, SparseBitmapLeftExtension) { | 
 |   Address base = reinterpret_cast<Address>(0x10000u); | 
 |   std::unique_ptr<SparseHeapBitmap> bitmap = SparseHeapBitmap::Create(base); | 
 |  | 
 |   SparseHeapBitmap* start = bitmap->HasRange(base, 1); | 
 |   EXPECT_TRUE(start); | 
 |  | 
 |   // Verify that re-adding is a no-op. | 
 |   bitmap->Add(base); | 
 |   EXPECT_EQ(start, bitmap->HasRange(base, 1)); | 
 |  | 
 |   // Adding an Address |A| before a single-address SparseHeapBitmap node should | 
 |   // cause that node to  be "left extended" to use |A| as its new base. | 
 |   bitmap->Add(base - 2 * kUnitPointer); | 
 |   EXPECT_EQ(bitmap->HasRange(base, 1), | 
 |             bitmap->HasRange(base - 2 * kUnitPointer, 1)); | 
 |  | 
 |   // Reset. | 
 |   bitmap = SparseHeapBitmap::Create(base); | 
 |  | 
 |   // If attempting same as above, but the Address |A| is outside the | 
 |   // chunk size of a node, a new SparseHeapBitmap node needs to be | 
 |   // created to the left of |bitmap|. | 
 |   bitmap->Add(base - kChunkRange); | 
 |   EXPECT_NE(bitmap->HasRange(base, 1), | 
 |             bitmap->HasRange(base - 2 * kUnitPointer, 1)); | 
 |  | 
 |   bitmap = SparseHeapBitmap::Create(base); | 
 |   bitmap->Add(base - kChunkRange + kUnitPointer); | 
 |   // This address is just inside the horizon and shouldn't create a new chunk. | 
 |   EXPECT_EQ(bitmap->HasRange(base, 1), | 
 |             bitmap->HasRange(base - 2 * kUnitPointer, 1)); | 
 |   // ..but this one should, like for the sub-test above. | 
 |   bitmap->Add(base - kChunkRange); | 
 |   EXPECT_EQ(bitmap->HasRange(base, 1), | 
 |             bitmap->HasRange(base - 2 * kUnitPointer, 1)); | 
 |   EXPECT_NE(bitmap->HasRange(base, 1), bitmap->HasRange(base - kChunkRange, 1)); | 
 | } | 
 |  | 
 | static void PerformHeapCompaction() { | 
 |   EXPECT_FALSE(HeapCompact::ScheduleCompactionGCForTesting(true)); | 
 |   PreciselyCollectGarbage(); | 
 |   EXPECT_FALSE(HeapCompact::ScheduleCompactionGCForTesting(false)); | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactVector) { | 
 |   ClearOutOldGarbage(); | 
 |  | 
 |   IntWrapper* val = IntWrapper::Create(1, VectorsAreCompacted); | 
 |   Persistent<IntVector> vector = new IntVector(10, val); | 
 |   EXPECT_EQ(10u, vector->size()); | 
 |  | 
 |   for (size_t i = 0; i < vector->size(); ++i) | 
 |     EXPECT_EQ(val, (*vector)[i]); | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   for (size_t i = 0; i < vector->size(); ++i) | 
 |     EXPECT_EQ(val, (*vector)[i]); | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactHashMap) { | 
 |   ClearOutOldGarbage(); | 
 |  | 
 |   Persistent<IntMap> int_map = new IntMap(); | 
 |   for (size_t i = 0; i < 100; ++i) { | 
 |     IntWrapper* val = IntWrapper::Create(i, HashTablesAreCompacted); | 
 |     int_map->insert(val, 100 - i); | 
 |   } | 
 |  | 
 |   EXPECT_EQ(100u, int_map->size()); | 
 |   for (auto k : *int_map) | 
 |     EXPECT_EQ(k.key->Value(), 100 - k.value); | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   for (auto k : *int_map) | 
 |     EXPECT_EQ(k.key->Value(), 100 - k.value); | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactVectorPartHashMap) { | 
 |   ClearOutOldGarbage(); | 
 |  | 
 |   using IntMapVector = HeapVector<IntMap>; | 
 |  | 
 |   Persistent<IntMapVector> int_map_vector = new IntMapVector(); | 
 |   for (size_t i = 0; i < 10; ++i) { | 
 |     IntMap map; | 
 |     for (size_t j = 0; j < 10; ++j) { | 
 |       IntWrapper* val = IntWrapper::Create(j, VectorsAreCompacted); | 
 |       map.insert(val, 10 - j); | 
 |     } | 
 |     int_map_vector->push_back(map); | 
 |   } | 
 |  | 
 |   EXPECT_EQ(10u, int_map_vector->size()); | 
 |   for (auto map : *int_map_vector) { | 
 |     EXPECT_EQ(10u, map.size()); | 
 |     for (auto k : map) { | 
 |       EXPECT_EQ(k.key->Value(), 10 - k.value); | 
 |     } | 
 |   } | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   EXPECT_EQ(10u, int_map_vector->size()); | 
 |   for (auto map : *int_map_vector) { | 
 |     EXPECT_EQ(10u, map.size()); | 
 |     for (auto k : map) { | 
 |       EXPECT_EQ(k.key->Value(), 10 - k.value); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactHashPartVector) { | 
 |   ClearOutOldGarbage(); | 
 |  | 
 |   using IntVectorMap = HeapHashMap<int, IntVector>; | 
 |  | 
 |   Persistent<IntVectorMap> int_vector_map = new IntVectorMap(); | 
 |   for (size_t i = 0; i < 10; ++i) { | 
 |     IntVector vector; | 
 |     for (size_t j = 0; j < 10; ++j) { | 
 |       vector.push_back(IntWrapper::Create(j, HashTablesAreCompacted)); | 
 |     } | 
 |     int_vector_map->insert(1 + i, vector); | 
 |   } | 
 |  | 
 |   EXPECT_EQ(10u, int_vector_map->size()); | 
 |   for (const IntVector& int_vector : int_vector_map->Values()) { | 
 |     EXPECT_EQ(10u, int_vector.size()); | 
 |     for (size_t i = 0; i < int_vector.size(); ++i) { | 
 |       EXPECT_EQ(static_cast<int>(i), int_vector[i]->Value()); | 
 |     } | 
 |   } | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   EXPECT_EQ(10u, int_vector_map->size()); | 
 |   for (const IntVector& int_vector : int_vector_map->Values()) { | 
 |     EXPECT_EQ(10u, int_vector.size()); | 
 |     for (size_t i = 0; i < int_vector.size(); ++i) { | 
 |       EXPECT_EQ(static_cast<int>(i), int_vector[i]->Value()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactDeques) { | 
 |   Persistent<IntDeque> deque = new IntDeque; | 
 |   for (int i = 0; i < 8; ++i) { | 
 |     deque->push_front(IntWrapper::Create(i, VectorsAreCompacted)); | 
 |   } | 
 |   EXPECT_EQ(8u, deque->size()); | 
 |  | 
 |   for (size_t i = 0; i < deque->size(); ++i) | 
 |     EXPECT_EQ(static_cast<int>(7 - i), deque->at(i)->Value()); | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   for (size_t i = 0; i < deque->size(); ++i) | 
 |     EXPECT_EQ(static_cast<int>(7 - i), deque->at(i)->Value()); | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactDequeVectors) { | 
 |   Persistent<HeapDeque<IntVector>> deque = new HeapDeque<IntVector>; | 
 |   for (int i = 0; i < 8; ++i) { | 
 |     IntWrapper* value = IntWrapper::Create(i, VectorsAreCompacted); | 
 |     IntVector vector = IntVector(8, value); | 
 |     deque->push_front(vector); | 
 |   } | 
 |   EXPECT_EQ(8u, deque->size()); | 
 |  | 
 |   for (size_t i = 0; i < deque->size(); ++i) | 
 |     EXPECT_EQ(static_cast<int>(7 - i), deque->at(i).at(i)->Value()); | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   for (size_t i = 0; i < deque->size(); ++i) | 
 |     EXPECT_EQ(static_cast<int>(7 - i), deque->at(i).at(i)->Value()); | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactLinkedHashSet) { | 
 |   using OrderedHashSet = HeapLinkedHashSet<Member<IntWrapper>>; | 
 |   Persistent<OrderedHashSet> set = new OrderedHashSet; | 
 |   for (int i = 0; i < 13; ++i) { | 
 |     IntWrapper* value = IntWrapper::Create(i, HashTablesAreCompacted); | 
 |     set->insert(value); | 
 |   } | 
 |   EXPECT_EQ(13u, set->size()); | 
 |  | 
 |   int expected = 0; | 
 |   for (IntWrapper* v : *set) { | 
 |     EXPECT_EQ(expected, v->Value()); | 
 |     expected++; | 
 |   } | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   expected = 0; | 
 |   for (IntWrapper* v : *set) { | 
 |     EXPECT_EQ(expected, v->Value()); | 
 |     expected++; | 
 |   } | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactLinkedHashSetVector) { | 
 |   using OrderedHashSet = HeapLinkedHashSet<Member<IntVector>>; | 
 |   Persistent<OrderedHashSet> set = new OrderedHashSet; | 
 |   for (int i = 0; i < 13; ++i) { | 
 |     IntWrapper* value = IntWrapper::Create(i); | 
 |     IntVector* vector = new IntVector(19, value); | 
 |     set->insert(vector); | 
 |   } | 
 |   EXPECT_EQ(13u, set->size()); | 
 |  | 
 |   int expected = 0; | 
 |   for (IntVector* v : *set) { | 
 |     EXPECT_EQ(expected, (*v)[0]->Value()); | 
 |     expected++; | 
 |   } | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   expected = 0; | 
 |   for (IntVector* v : *set) { | 
 |     EXPECT_EQ(expected, (*v)[0]->Value()); | 
 |     expected++; | 
 |   } | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactLinkedHashSetMap) { | 
 |   using Inner = HeapHashSet<Member<IntWrapper>>; | 
 |   using OrderedHashSet = HeapLinkedHashSet<Member<Inner>>; | 
 |  | 
 |   Persistent<OrderedHashSet> set = new OrderedHashSet; | 
 |   for (int i = 0; i < 13; ++i) { | 
 |     IntWrapper* value = IntWrapper::Create(i); | 
 |     Inner* inner = new Inner; | 
 |     inner->insert(value); | 
 |     set->insert(inner); | 
 |   } | 
 |   EXPECT_EQ(13u, set->size()); | 
 |  | 
 |   int expected = 0; | 
 |   for (const Inner* v : *set) { | 
 |     EXPECT_EQ(1u, v->size()); | 
 |     EXPECT_EQ(expected, (*v->begin())->Value()); | 
 |     expected++; | 
 |   } | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   expected = 0; | 
 |   for (const Inner* v : *set) { | 
 |     EXPECT_EQ(1u, v->size()); | 
 |     EXPECT_EQ(expected, (*v->begin())->Value()); | 
 |     expected++; | 
 |   } | 
 | } | 
 |  | 
 | TEST(HeapCompactTest, CompactLinkedHashSetNested) { | 
 |   using Inner = HeapLinkedHashSet<Member<IntWrapper>>; | 
 |   using OrderedHashSet = HeapLinkedHashSet<Member<Inner>>; | 
 |  | 
 |   Persistent<OrderedHashSet> set = new OrderedHashSet; | 
 |   for (int i = 0; i < 13; ++i) { | 
 |     IntWrapper* value = IntWrapper::Create(i); | 
 |     Inner* inner = new Inner; | 
 |     inner->insert(value); | 
 |     set->insert(inner); | 
 |   } | 
 |   EXPECT_EQ(13u, set->size()); | 
 |  | 
 |   int expected = 0; | 
 |   for (const Inner* v : *set) { | 
 |     EXPECT_EQ(1u, v->size()); | 
 |     EXPECT_EQ(expected, (*v->begin())->Value()); | 
 |     expected++; | 
 |   } | 
 |  | 
 |   PerformHeapCompaction(); | 
 |  | 
 |   expected = 0; | 
 |   for (const Inner* v : *set) { | 
 |     EXPECT_EQ(1u, v->size()); | 
 |     EXPECT_EQ(expected, (*v->begin())->Value()); | 
 |     expected++; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace blink | 
 |  | 
 | #endif  // ENABLE_HEAP_COMPACTION |