| // Copyright 2017 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/heap/worklist.h" |
| |
| #include "test/unittests/test-utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| class SomeObject {}; |
| |
| using TestWorklist = Worklist<SomeObject*, 64>; |
| |
| TEST(WorkListTest, SegmentCreate) { |
| TestWorklist::Segment segment; |
| EXPECT_TRUE(segment.IsEmpty()); |
| EXPECT_EQ(0u, segment.Size()); |
| EXPECT_FALSE(segment.IsFull()); |
| } |
| |
| TEST(WorkListTest, SegmentPush) { |
| TestWorklist::Segment segment; |
| EXPECT_EQ(0u, segment.Size()); |
| EXPECT_TRUE(segment.Push(nullptr)); |
| EXPECT_EQ(1u, segment.Size()); |
| } |
| |
| TEST(WorkListTest, SegmentPushPop) { |
| TestWorklist::Segment segment; |
| EXPECT_TRUE(segment.Push(nullptr)); |
| EXPECT_EQ(1u, segment.Size()); |
| SomeObject dummy; |
| SomeObject* object = &dummy; |
| EXPECT_TRUE(segment.Pop(&object)); |
| EXPECT_EQ(0u, segment.Size()); |
| EXPECT_EQ(nullptr, object); |
| } |
| |
| TEST(WorkListTest, SegmentIsEmpty) { |
| TestWorklist::Segment segment; |
| EXPECT_TRUE(segment.IsEmpty()); |
| EXPECT_TRUE(segment.Push(nullptr)); |
| EXPECT_FALSE(segment.IsEmpty()); |
| } |
| |
| TEST(WorkListTest, SegmentIsFull) { |
| TestWorklist::Segment segment; |
| EXPECT_FALSE(segment.IsFull()); |
| for (size_t i = 0; i < TestWorklist::Segment::kCapacity; i++) { |
| EXPECT_TRUE(segment.Push(nullptr)); |
| } |
| EXPECT_TRUE(segment.IsFull()); |
| } |
| |
| TEST(WorkListTest, SegmentClear) { |
| TestWorklist::Segment segment; |
| EXPECT_TRUE(segment.Push(nullptr)); |
| EXPECT_FALSE(segment.IsEmpty()); |
| segment.Clear(); |
| EXPECT_TRUE(segment.IsEmpty()); |
| for (size_t i = 0; i < TestWorklist::Segment::kCapacity; i++) { |
| EXPECT_TRUE(segment.Push(nullptr)); |
| } |
| } |
| |
| TEST(WorkListTest, SegmentFullPushFails) { |
| TestWorklist::Segment segment; |
| EXPECT_FALSE(segment.IsFull()); |
| for (size_t i = 0; i < TestWorklist::Segment::kCapacity; i++) { |
| EXPECT_TRUE(segment.Push(nullptr)); |
| } |
| EXPECT_TRUE(segment.IsFull()); |
| EXPECT_FALSE(segment.Push(nullptr)); |
| } |
| |
| TEST(WorkListTest, SegmentEmptyPopFails) { |
| TestWorklist::Segment segment; |
| EXPECT_TRUE(segment.IsEmpty()); |
| SomeObject* object; |
| EXPECT_FALSE(segment.Pop(&object)); |
| } |
| |
| TEST(WorkListTest, SegmentUpdateFalse) { |
| TestWorklist::Segment segment; |
| SomeObject* object; |
| object = reinterpret_cast<SomeObject*>(&object); |
| EXPECT_TRUE(segment.Push(object)); |
| segment.Update([](SomeObject* object, SomeObject** out) { return false; }); |
| EXPECT_TRUE(segment.IsEmpty()); |
| } |
| |
| TEST(WorkListTest, SegmentUpdate) { |
| TestWorklist::Segment segment; |
| SomeObject* objectA; |
| objectA = reinterpret_cast<SomeObject*>(&objectA); |
| SomeObject* objectB; |
| objectB = reinterpret_cast<SomeObject*>(&objectB); |
| EXPECT_TRUE(segment.Push(objectA)); |
| segment.Update([objectB](SomeObject* object, SomeObject** out) { |
| *out = objectB; |
| return true; |
| }); |
| SomeObject* object; |
| EXPECT_TRUE(segment.Pop(&object)); |
| EXPECT_EQ(object, objectB); |
| } |
| |
| TEST(WorkListTest, CreateEmpty) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view(&worklist, 0); |
| EXPECT_TRUE(worklist_view.IsLocalEmpty()); |
| EXPECT_TRUE(worklist.IsEmpty()); |
| } |
| |
| TEST(WorkListTest, LocalPushPop) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view(&worklist, 0); |
| SomeObject dummy; |
| SomeObject* retrieved = nullptr; |
| EXPECT_TRUE(worklist_view.Push(&dummy)); |
| EXPECT_FALSE(worklist_view.IsLocalEmpty()); |
| EXPECT_TRUE(worklist_view.Pop(&retrieved)); |
| EXPECT_EQ(&dummy, retrieved); |
| } |
| |
| TEST(WorkListTest, LocalIsBasedOnId) { |
| TestWorklist worklist; |
| // Use the same id. |
| TestWorklist::View worklist_view1(&worklist, 0); |
| TestWorklist::View worklist_view2(&worklist, 0); |
| SomeObject dummy; |
| SomeObject* retrieved = nullptr; |
| EXPECT_TRUE(worklist_view1.Push(&dummy)); |
| EXPECT_FALSE(worklist_view1.IsLocalEmpty()); |
| EXPECT_FALSE(worklist_view2.IsLocalEmpty()); |
| EXPECT_TRUE(worklist_view2.Pop(&retrieved)); |
| EXPECT_EQ(&dummy, retrieved); |
| EXPECT_TRUE(worklist_view1.IsLocalEmpty()); |
| EXPECT_TRUE(worklist_view2.IsLocalEmpty()); |
| } |
| |
| TEST(WorkListTest, LocalPushStaysPrivate) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view1(&worklist, 0); |
| TestWorklist::View worklist_view2(&worklist, 1); |
| SomeObject dummy; |
| SomeObject* retrieved = nullptr; |
| EXPECT_TRUE(worklist.IsEmpty()); |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| EXPECT_TRUE(worklist_view1.Push(&dummy)); |
| EXPECT_FALSE(worklist.IsEmpty()); |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| EXPECT_FALSE(worklist_view2.Pop(&retrieved)); |
| EXPECT_EQ(nullptr, retrieved); |
| EXPECT_TRUE(worklist_view1.Pop(&retrieved)); |
| EXPECT_EQ(&dummy, retrieved); |
| EXPECT_TRUE(worklist.IsEmpty()); |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| } |
| |
| TEST(WorkListTest, GlobalUpdateNull) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view(&worklist, 0); |
| SomeObject* object; |
| object = reinterpret_cast<SomeObject*>(&object); |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view.Push(object)); |
| } |
| EXPECT_TRUE(worklist_view.Push(object)); |
| worklist.Update([](SomeObject* object, SomeObject** out) { return false; }); |
| EXPECT_TRUE(worklist.IsEmpty()); |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| } |
| |
| TEST(WorkListTest, GlobalUpdate) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view(&worklist, 0); |
| SomeObject* objectA = nullptr; |
| objectA = reinterpret_cast<SomeObject*>(&objectA); |
| SomeObject* objectB = nullptr; |
| objectB = reinterpret_cast<SomeObject*>(&objectB); |
| SomeObject* objectC = nullptr; |
| objectC = reinterpret_cast<SomeObject*>(&objectC); |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view.Push(objectA)); |
| } |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view.Push(objectB)); |
| } |
| EXPECT_TRUE(worklist_view.Push(objectA)); |
| worklist.Update([objectA, objectC](SomeObject* object, SomeObject** out) { |
| if (object != objectA) { |
| *out = objectC; |
| return true; |
| } |
| return false; |
| }); |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| SomeObject* object; |
| EXPECT_TRUE(worklist_view.Pop(&object)); |
| EXPECT_EQ(object, objectC); |
| } |
| } |
| |
| TEST(WorkListTest, FlushToGlobalPushSegment) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view0(&worklist, 0); |
| TestWorklist::View worklist_view1(&worklist, 1); |
| SomeObject* object = nullptr; |
| SomeObject* objectA = nullptr; |
| objectA = reinterpret_cast<SomeObject*>(&objectA); |
| EXPECT_TRUE(worklist_view0.Push(objectA)); |
| worklist.FlushToGlobal(0); |
| EXPECT_EQ(1U, worklist.GlobalPoolSize()); |
| EXPECT_TRUE(worklist_view1.Pop(&object)); |
| } |
| |
| TEST(WorkListTest, FlushToGlobalPopSegment) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view0(&worklist, 0); |
| TestWorklist::View worklist_view1(&worklist, 1); |
| SomeObject* object = nullptr; |
| SomeObject* objectA = nullptr; |
| objectA = reinterpret_cast<SomeObject*>(&objectA); |
| EXPECT_TRUE(worklist_view0.Push(objectA)); |
| EXPECT_TRUE(worklist_view0.Push(objectA)); |
| EXPECT_TRUE(worklist_view0.Pop(&object)); |
| worklist.FlushToGlobal(0); |
| EXPECT_EQ(1U, worklist.GlobalPoolSize()); |
| EXPECT_TRUE(worklist_view1.Pop(&object)); |
| } |
| |
| TEST(WorkListTest, Clear) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view(&worklist, 0); |
| SomeObject* object; |
| object = reinterpret_cast<SomeObject*>(&object); |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view.Push(object)); |
| } |
| EXPECT_TRUE(worklist_view.Push(object)); |
| EXPECT_EQ(1U, worklist.GlobalPoolSize()); |
| worklist.Clear(); |
| EXPECT_TRUE(worklist.IsEmpty()); |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| } |
| |
| TEST(WorkListTest, SingleSegmentSteal) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view1(&worklist, 0); |
| TestWorklist::View worklist_view2(&worklist, 1); |
| SomeObject dummy; |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view1.Push(&dummy)); |
| } |
| SomeObject* retrieved = nullptr; |
| // One more push/pop to publish the full segment. |
| EXPECT_TRUE(worklist_view1.Push(nullptr)); |
| EXPECT_TRUE(worklist_view1.Pop(&retrieved)); |
| EXPECT_EQ(nullptr, retrieved); |
| EXPECT_EQ(1U, worklist.GlobalPoolSize()); |
| // Stealing. |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view2.Pop(&retrieved)); |
| EXPECT_EQ(&dummy, retrieved); |
| EXPECT_FALSE(worklist_view1.Pop(&retrieved)); |
| } |
| EXPECT_TRUE(worklist.IsEmpty()); |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| } |
| |
| TEST(WorkListTest, MultipleSegmentsStolen) { |
| TestWorklist worklist; |
| TestWorklist::View worklist_view1(&worklist, 0); |
| TestWorklist::View worklist_view2(&worklist, 1); |
| TestWorklist::View worklist_view3(&worklist, 2); |
| SomeObject dummy1; |
| SomeObject dummy2; |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view1.Push(&dummy1)); |
| } |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view1.Push(&dummy2)); |
| } |
| SomeObject* retrieved = nullptr; |
| SomeObject dummy3; |
| // One more push/pop to publish the full segment. |
| EXPECT_TRUE(worklist_view1.Push(&dummy3)); |
| EXPECT_TRUE(worklist_view1.Pop(&retrieved)); |
| EXPECT_EQ(&dummy3, retrieved); |
| EXPECT_EQ(2U, worklist.GlobalPoolSize()); |
| // Stealing. |
| EXPECT_TRUE(worklist_view2.Pop(&retrieved)); |
| SomeObject* const expect_bag2 = retrieved; |
| EXPECT_TRUE(worklist_view3.Pop(&retrieved)); |
| SomeObject* const expect_bag3 = retrieved; |
| EXPECT_EQ(0U, worklist.GlobalPoolSize()); |
| EXPECT_NE(expect_bag2, expect_bag3); |
| EXPECT_TRUE(expect_bag2 == &dummy1 || expect_bag2 == &dummy2); |
| EXPECT_TRUE(expect_bag3 == &dummy1 || expect_bag3 == &dummy2); |
| for (size_t i = 1; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view2.Pop(&retrieved)); |
| EXPECT_EQ(expect_bag2, retrieved); |
| EXPECT_FALSE(worklist_view1.Pop(&retrieved)); |
| } |
| for (size_t i = 1; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view3.Pop(&retrieved)); |
| EXPECT_EQ(expect_bag3, retrieved); |
| EXPECT_FALSE(worklist_view1.Pop(&retrieved)); |
| } |
| EXPECT_TRUE(worklist.IsEmpty()); |
| } |
| |
| TEST(WorkListTest, MergeGlobalPool) { |
| TestWorklist worklist1; |
| TestWorklist::View worklist_view1(&worklist1, 0); |
| SomeObject dummy; |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view1.Push(&dummy)); |
| } |
| SomeObject* retrieved = nullptr; |
| // One more push/pop to publish the full segment. |
| EXPECT_TRUE(worklist_view1.Push(nullptr)); |
| EXPECT_TRUE(worklist_view1.Pop(&retrieved)); |
| EXPECT_EQ(nullptr, retrieved); |
| EXPECT_EQ(1U, worklist1.GlobalPoolSize()); |
| // Merging global pool into a new Worklist. |
| TestWorklist worklist2; |
| TestWorklist::View worklist_view2(&worklist2, 0); |
| EXPECT_EQ(0U, worklist2.GlobalPoolSize()); |
| worklist2.MergeGlobalPool(&worklist1); |
| EXPECT_EQ(1U, worklist2.GlobalPoolSize()); |
| EXPECT_FALSE(worklist2.IsEmpty()); |
| for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { |
| EXPECT_TRUE(worklist_view2.Pop(&retrieved)); |
| EXPECT_EQ(&dummy, retrieved); |
| EXPECT_FALSE(worklist_view1.Pop(&retrieved)); |
| } |
| EXPECT_TRUE(worklist1.IsEmpty()); |
| EXPECT_TRUE(worklist2.IsEmpty()); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |