Commit 05b241c6 authored by Michael Lippautz's avatar Michael Lippautz Committed by V8 LUCI CQ

heap: Remove unused Worklist

- Removes the unused Worklist implementation. All uses now refer to
  ::heap::base::Worklist.
- Renames CppgcWorklistTest -> WorklistTest
- Add test for Swap()

Bug: v8:12426
Change-Id: I62c3472c030b853a846cf13ab48597ea1af8f700
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3306507Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78208}
parent c1e0aa1e
......@@ -1429,7 +1429,6 @@ filegroup(
"src/heap/sweeper.h",
"src/heap/weak-object-worklists.cc",
"src/heap/weak-object-worklists.h",
"src/heap/worklist.h",
"src/ic/call-optimization.cc",
"src/ic/call-optimization.h",
"src/ic/handler-configuration-inl.h",
......
......@@ -3011,7 +3011,6 @@ v8_header_set("v8_internal_headers") {
"src/heap/stress-scavenge-observer.h",
"src/heap/sweeper.h",
"src/heap/weak-object-worklists.h",
"src/heap/worklist.h",
"src/ic/call-optimization.h",
"src/ic/handler-configuration-inl.h",
"src/ic/handler-configuration.h",
......
......@@ -228,14 +228,14 @@ class Worklist<EntryType, SegmentSize>::Segment : public internal::SegmentBase {
friend class Worklist<EntryType, SegmentSize>::Local;
FRIEND_TEST(CppgcWorkListTest, SegmentCreate);
FRIEND_TEST(CppgcWorkListTest, SegmentPush);
FRIEND_TEST(CppgcWorkListTest, SegmentPushPop);
FRIEND_TEST(CppgcWorkListTest, SegmentIsEmpty);
FRIEND_TEST(CppgcWorkListTest, SegmentIsFull);
FRIEND_TEST(CppgcWorkListTest, SegmentClear);
FRIEND_TEST(CppgcWorkListTest, SegmentUpdateFalse);
FRIEND_TEST(CppgcWorkListTest, SegmentUpdate);
FRIEND_TEST(WorkListTest, SegmentCreate);
FRIEND_TEST(WorkListTest, SegmentPush);
FRIEND_TEST(WorkListTest, SegmentPushPop);
FRIEND_TEST(WorkListTest, SegmentIsEmpty);
FRIEND_TEST(WorkListTest, SegmentIsFull);
FRIEND_TEST(WorkListTest, SegmentClear);
FRIEND_TEST(WorkListTest, SegmentUpdateFalse);
FRIEND_TEST(WorkListTest, SegmentUpdate);
};
template <typename EntryType, uint16_t SegmentSize>
......
......@@ -24,7 +24,6 @@
#include "src/heap/objects-visiting-inl.h"
#include "src/heap/objects-visiting.h"
#include "src/heap/weak-object-worklists.h"
#include "src/heap/worklist.h"
#include "src/init/v8.h"
#include "src/objects/data-handler-inl.h"
#include "src/objects/embedder-data-array-inl.h"
......
......@@ -16,7 +16,6 @@
#include "src/heap/memory-measurement.h"
#include "src/heap/slot-set.h"
#include "src/heap/spaces.h"
#include "src/heap/worklist.h"
#include "src/init/v8.h"
#include "src/tasks/cancelable-task.h"
#include "src/utils/allocation.h"
......
......@@ -41,7 +41,6 @@
#include "src/heap/spaces-inl.h"
#include "src/heap/sweeper.h"
#include "src/heap/weak-object-worklists.h"
#include "src/heap/worklist.h"
#include "src/ic/stub-cache.h"
#include "src/init/v8.h"
#include "src/logging/tracing-flags.h"
......
......@@ -12,7 +12,6 @@
#include "src/heap/objects-visiting.h"
#include "src/heap/spaces.h"
#include "src/heap/weak-object-worklists.h"
#include "src/heap/worklist.h"
namespace v8 {
namespace internal {
......
......@@ -12,11 +12,11 @@
#include "src/base/atomic-utils.h"
#include "src/base/bit-field.h"
#include "src/base/bits.h"
#include "src/heap/worklist.h"
#include "src/objects/compressed-slots.h"
#include "src/objects/slots.h"
#include "src/utils/allocation.h"
#include "src/utils/utils.h"
#include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck
namespace v8 {
namespace internal {
......
......@@ -6,7 +6,6 @@
#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/worklist.h"
#include "src/objects/hash-table.h"
#include "src/objects/heap-object.h"
#include "src/objects/js-function.h"
......
// 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.
#ifndef V8_HEAP_WORKLIST_H_
#define V8_HEAP_WORKLIST_H_
#include <cstddef>
#include <utility>
#include "src/base/atomic-utils.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck
namespace v8 {
namespace internal {
// A concurrent worklist based on segments. Each tasks gets private
// push and pop segments. Empty pop segments are swapped with their
// corresponding push segments. Full push segments are published to a global
// pool of segments and replaced with empty segments.
//
// Work stealing is best effort, i.e., there is no way to inform other tasks
// of the need of items.
template <typename EntryType, int SEGMENT_SIZE>
class Worklist {
public:
class View {
public:
View(Worklist<EntryType, SEGMENT_SIZE>* worklist, int task_id)
: worklist_(worklist), task_id_(task_id) {}
// Pushes an entry onto the worklist.
bool Push(EntryType entry) { return worklist_->Push(task_id_, entry); }
// Pops an entry from the worklist.
bool Pop(EntryType* entry) { return worklist_->Pop(task_id_, entry); }
// Returns true if the local portion of the worklist is empty.
bool IsLocalEmpty() { return worklist_->IsLocalEmpty(task_id_); }
// Returns true if the worklist is empty. Can only be used from the main
// thread without concurrent access.
bool IsEmpty() { return worklist_->IsEmpty(); }
bool IsGlobalPoolEmpty() { return worklist_->IsGlobalPoolEmpty(); }
size_t LocalPushSegmentSize() {
return worklist_->LocalPushSegmentSize(task_id_);
}
void FlushToGlobal() { worklist_->FlushToGlobal(task_id_); }
private:
Worklist<EntryType, SEGMENT_SIZE>* worklist_;
int task_id_;
};
static const int kMaxNumTasks = 8;
static const size_t kSegmentCapacity = SEGMENT_SIZE;
Worklist() : Worklist(kMaxNumTasks) {}
explicit Worklist(int num_tasks) : num_tasks_(num_tasks) {
DCHECK_LE(num_tasks, kMaxNumTasks);
for (int i = 0; i < num_tasks_; i++) {
private_push_segment(i) = NewSegment();
private_pop_segment(i) = NewSegment();
}
}
~Worklist() {
CHECK(IsEmpty());
for (int i = 0; i < num_tasks_; i++) {
DCHECK_NOT_NULL(private_push_segment(i));
DCHECK_NOT_NULL(private_pop_segment(i));
delete private_push_segment(i);
delete private_pop_segment(i);
}
}
// Swaps content with the given worklist. Local buffers need to
// be empty, not thread safe.
void Swap(Worklist<EntryType, SEGMENT_SIZE>& other) {
CHECK(AreLocalsEmpty());
CHECK(other.AreLocalsEmpty());
global_pool_.Swap(other.global_pool_);
}
bool Push(int task_id, EntryType entry) {
DCHECK_LT(task_id, num_tasks_);
DCHECK_NOT_NULL(private_push_segment(task_id));
if (!private_push_segment(task_id)->Push(entry)) {
PublishPushSegmentToGlobal(task_id);
bool success = private_push_segment(task_id)->Push(entry);
USE(success);
DCHECK(success);
}
return true;
}
bool Pop(int task_id, EntryType* entry) {
DCHECK_LT(task_id, num_tasks_);
DCHECK_NOT_NULL(private_pop_segment(task_id));
if (!private_pop_segment(task_id)->Pop(entry)) {
if (!private_push_segment(task_id)->IsEmpty()) {
Segment* tmp = private_pop_segment(task_id);
private_pop_segment(task_id) = private_push_segment(task_id);
private_push_segment(task_id) = tmp;
} else if (!StealPopSegmentFromGlobal(task_id)) {
return false;
}
bool success = private_pop_segment(task_id)->Pop(entry);
USE(success);
DCHECK(success);
}
return true;
}
size_t LocalPushSegmentSize(int task_id) {
return private_push_segment(task_id)->Size();
}
bool IsLocalEmpty(int task_id) {
return private_pop_segment(task_id)->IsEmpty() &&
private_push_segment(task_id)->IsEmpty();
}
bool IsGlobalPoolEmpty() { return global_pool_.IsEmpty(); }
bool IsEmpty() {
if (!AreLocalsEmpty()) return false;
return global_pool_.IsEmpty();
}
bool AreLocalsEmpty() {
for (int i = 0; i < num_tasks_; i++) {
if (!IsLocalEmpty(i)) return false;
}
return true;
}
size_t LocalSize(int task_id) {
return private_pop_segment(task_id)->Size() +
private_push_segment(task_id)->Size();
}
// Thread-safe but may return an outdated result.
size_t GlobalPoolSize() const { return global_pool_.Size(); }
// Clears all segments. Frees the global segment pool.
//
// Assumes that no other tasks are running.
void Clear() {
for (int i = 0; i < num_tasks_; i++) {
private_pop_segment(i)->Clear();
private_push_segment(i)->Clear();
}
global_pool_.Clear();
}
// Calls the specified callback on each element of the deques and replaces
// the element with the result of the callback.
// The signature of the callback is
// bool Callback(EntryType old, EntryType* new).
// If the callback returns |false| then the element is removed from the
// worklist. Otherwise the |new| entry is updated.
//
// Assumes that no other tasks are running.
template <typename Callback>
void Update(Callback callback) {
for (int i = 0; i < num_tasks_; i++) {
private_pop_segment(i)->Update(callback);
private_push_segment(i)->Update(callback);
}
global_pool_.Update(callback);
}
// Calls the specified callback on each element of the deques.
// The signature of the callback is:
// void Callback(EntryType entry).
//
// Assumes that no other tasks are running.
template <typename Callback>
void Iterate(Callback callback) {
for (int i = 0; i < num_tasks_; i++) {
private_pop_segment(i)->Iterate(callback);
private_push_segment(i)->Iterate(callback);
}
global_pool_.Iterate(callback);
}
template <typename Callback>
void IterateGlobalPool(Callback callback) {
global_pool_.Iterate(callback);
}
void FlushToGlobal(int task_id) {
PublishPushSegmentToGlobal(task_id);
PublishPopSegmentToGlobal(task_id);
}
void MergeGlobalPool(Worklist* other) {
global_pool_.Merge(&other->global_pool_);
}
private:
FRIEND_TEST(WorkListTest, SegmentCreate);
FRIEND_TEST(WorkListTest, SegmentPush);
FRIEND_TEST(WorkListTest, SegmentPushPop);
FRIEND_TEST(WorkListTest, SegmentIsEmpty);
FRIEND_TEST(WorkListTest, SegmentIsFull);
FRIEND_TEST(WorkListTest, SegmentClear);
FRIEND_TEST(WorkListTest, SegmentFullPushFails);
FRIEND_TEST(WorkListTest, SegmentEmptyPopFails);
FRIEND_TEST(WorkListTest, SegmentUpdateFalse);
FRIEND_TEST(WorkListTest, SegmentUpdate);
class Segment {
public:
static const size_t kCapacity = kSegmentCapacity;
Segment() : index_(0) {}
bool Push(EntryType entry) {
if (IsFull()) return false;
entries_[index_++] = entry;
return true;
}
bool Pop(EntryType* entry) {
if (IsEmpty()) return false;
*entry = entries_[--index_];
return true;
}
size_t Size() const { return index_; }
bool IsEmpty() const { return index_ == 0; }
bool IsFull() const { return index_ == kCapacity; }
void Clear() { index_ = 0; }
template <typename Callback>
void Update(Callback callback) {
size_t new_index = 0;
for (size_t i = 0; i < index_; i++) {
if (callback(entries_[i], &entries_[new_index])) {
new_index++;
}
}
index_ = new_index;
}
template <typename Callback>
void Iterate(Callback callback) const {
for (size_t i = 0; i < index_; i++) {
callback(entries_[i]);
}
}
Segment* next() const { return next_; }
void set_next(Segment* segment) { next_ = segment; }
private:
Segment* next_;
size_t index_;
EntryType entries_[kCapacity];
};
struct PrivateSegmentHolder {
Segment* private_push_segment;
Segment* private_pop_segment;
char cache_line_padding[64];
};
class GlobalPool {
public:
GlobalPool() : top_(nullptr) {}
// Swaps contents, not thread safe.
void Swap(GlobalPool& other) {
Segment* temp = top_;
set_top(other.top_);
other.set_top(temp);
size_t other_size = other.size_.exchange(
size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
size_.store(other_size, std::memory_order_relaxed);
}
V8_INLINE void Push(Segment* segment) {
base::MutexGuard guard(&lock_);
segment->set_next(top_);
set_top(segment);
size_.fetch_add(1, std::memory_order_relaxed);
}
V8_INLINE bool Pop(Segment** segment) {
base::MutexGuard guard(&lock_);
if (top_ != nullptr) {
DCHECK_LT(0U, size_);
size_.fetch_sub(1, std::memory_order_relaxed);
*segment = top_;
set_top(top_->next());
return true;
}
return false;
}
V8_INLINE bool IsEmpty() {
return base::AsAtomicPointer::Relaxed_Load(&top_) == nullptr;
}
V8_INLINE size_t Size() const {
// It is safe to read |size_| without a lock since this variable is
// atomic, keeping in mind that threads may not immediately see the new
// value when it is updated.
return size_.load(std::memory_order_relaxed);
}
void Clear() {
base::MutexGuard guard(&lock_);
size_.store(0, std::memory_order_relaxed);
Segment* current = top_;
while (current != nullptr) {
Segment* tmp = current;
current = current->next();
delete tmp;
}
set_top(nullptr);
}
// See Worklist::Update.
template <typename Callback>
void Update(Callback callback) {
base::MutexGuard guard(&lock_);
Segment* prev = nullptr;
Segment* current = top_;
size_t num_deleted = 0;
while (current != nullptr) {
current->Update(callback);
if (current->IsEmpty()) {
DCHECK_LT(0U, size_);
++num_deleted;
if (prev == nullptr) {
top_ = current->next();
} else {
prev->set_next(current->next());
}
Segment* tmp = current;
current = current->next();
delete tmp;
} else {
prev = current;
current = current->next();
}
}
size_.fetch_sub(num_deleted, std::memory_order_relaxed);
}
// See Worklist::Iterate.
template <typename Callback>
void Iterate(Callback callback) {
base::MutexGuard guard(&lock_);
for (Segment* current = top_; current != nullptr;
current = current->next()) {
current->Iterate(callback);
}
}
void Merge(GlobalPool* other) {
Segment* top = nullptr;
size_t other_size = 0;
{
base::MutexGuard guard(&other->lock_);
if (!other->top_) return;
top = other->top_;
other_size = other->size_.load(std::memory_order_relaxed);
other->size_.store(0, std::memory_order_relaxed);
other->set_top(nullptr);
}
// It's safe to iterate through these segments because the top was
// extracted from |other|.
Segment* end = top;
while (end->next()) end = end->next();
{
base::MutexGuard guard(&lock_);
size_.fetch_add(other_size, std::memory_order_relaxed);
end->set_next(top_);
set_top(top);
}
}
private:
void set_top(Segment* segment) {
base::AsAtomicPointer::Relaxed_Store(&top_, segment);
}
base::Mutex lock_;
Segment* top_;
std::atomic<size_t> size_{0};
};
V8_INLINE Segment*& private_push_segment(int task_id) {
return private_segments_[task_id].private_push_segment;
}
V8_INLINE Segment*& private_pop_segment(int task_id) {
return private_segments_[task_id].private_pop_segment;
}
V8_INLINE void PublishPushSegmentToGlobal(int task_id) {
if (!private_push_segment(task_id)->IsEmpty()) {
global_pool_.Push(private_push_segment(task_id));
private_push_segment(task_id) = NewSegment();
}
}
V8_INLINE void PublishPopSegmentToGlobal(int task_id) {
if (!private_pop_segment(task_id)->IsEmpty()) {
global_pool_.Push(private_pop_segment(task_id));
private_pop_segment(task_id) = NewSegment();
}
}
V8_INLINE bool StealPopSegmentFromGlobal(int task_id) {
if (global_pool_.IsEmpty()) return false;
Segment* new_segment = nullptr;
if (global_pool_.Pop(&new_segment)) {
delete private_pop_segment(task_id);
private_pop_segment(task_id) = new_segment;
return true;
}
return false;
}
V8_INLINE Segment* NewSegment() {
// Bottleneck for filtering in crash dumps.
return new Segment();
}
PrivateSegmentHolder private_segments_[kMaxNumTasks];
GlobalPool global_pool_;
int num_tasks_;
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_WORKLIST_H_
......@@ -10,7 +10,6 @@
#include "src/heap/mark-compact.h"
#include "src/heap/marking-worklist-inl.h"
#include "src/heap/marking-worklist.h"
#include "src/heap/worklist.h"
#include "src/init/v8.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
......
......@@ -339,7 +339,6 @@ v8_source_set("unittests_sources") {
"heap/slot-set-unittest.cc",
"heap/spaces-unittest.cc",
"heap/unmapper-unittest.cc",
"heap/worklist-unittest.cc",
"interpreter/bytecode-array-builder-unittest.cc",
"interpreter/bytecode-array-iterator-unittest.cc",
"interpreter/bytecode-array-random-iterator-unittest.cc",
......
......@@ -13,21 +13,21 @@ class SomeObject {};
using TestWorklist = Worklist<SomeObject*, 64>;
TEST(CppgcWorkListTest, SegmentCreate) {
TEST(WorkListTest, SegmentCreate) {
TestWorklist::Segment segment;
EXPECT_TRUE(segment.IsEmpty());
EXPECT_EQ(0u, segment.Size());
EXPECT_FALSE(segment.IsFull());
}
TEST(CppgcWorkListTest, SegmentPush) {
TEST(WorkListTest, SegmentPush) {
TestWorklist::Segment segment;
EXPECT_EQ(0u, segment.Size());
segment.Push(nullptr);
EXPECT_EQ(1u, segment.Size());
}
TEST(CppgcWorkListTest, SegmentPushPop) {
TEST(WorkListTest, SegmentPushPop) {
TestWorklist::Segment segment;
segment.Push(nullptr);
EXPECT_EQ(1u, segment.Size());
......@@ -38,14 +38,14 @@ TEST(CppgcWorkListTest, SegmentPushPop) {
EXPECT_EQ(nullptr, object);
}
TEST(CppgcWorkListTest, SegmentIsEmpty) {
TEST(WorkListTest, SegmentIsEmpty) {
TestWorklist::Segment segment;
EXPECT_TRUE(segment.IsEmpty());
segment.Push(nullptr);
EXPECT_FALSE(segment.IsEmpty());
}
TEST(CppgcWorkListTest, SegmentIsFull) {
TEST(WorkListTest, SegmentIsFull) {
TestWorklist::Segment segment;
EXPECT_FALSE(segment.IsFull());
for (size_t i = 0; i < TestWorklist::Segment::kSize; i++) {
......@@ -54,7 +54,7 @@ TEST(CppgcWorkListTest, SegmentIsFull) {
EXPECT_TRUE(segment.IsFull());
}
TEST(CppgcWorkListTest, SegmentClear) {
TEST(WorkListTest, SegmentClear) {
TestWorklist::Segment segment;
segment.Push(nullptr);
EXPECT_FALSE(segment.IsEmpty());
......@@ -65,7 +65,7 @@ TEST(CppgcWorkListTest, SegmentClear) {
}
}
TEST(CppgcWorkListTest, SegmentUpdateFalse) {
TEST(WorkListTest, SegmentUpdateFalse) {
TestWorklist::Segment segment;
SomeObject* object;
object = reinterpret_cast<SomeObject*>(&object);
......@@ -74,7 +74,7 @@ TEST(CppgcWorkListTest, SegmentUpdateFalse) {
EXPECT_TRUE(segment.IsEmpty());
}
TEST(CppgcWorkListTest, SegmentUpdate) {
TEST(WorkListTest, SegmentUpdate) {
TestWorklist::Segment segment;
SomeObject* objectA;
objectA = reinterpret_cast<SomeObject*>(&objectA);
......@@ -90,14 +90,14 @@ TEST(CppgcWorkListTest, SegmentUpdate) {
EXPECT_EQ(object, objectB);
}
TEST(CppgcWorkListTest, CreateEmpty) {
TEST(WorkListTest, CreateEmpty) {
TestWorklist worklist;
TestWorklist::Local worklist_local(&worklist);
EXPECT_TRUE(worklist_local.IsLocalEmpty());
EXPECT_TRUE(worklist.IsEmpty());
}
TEST(CppgcWorkListTest, LocalPushPop) {
TEST(WorkListTest, LocalPushPop) {
TestWorklist worklist;
TestWorklist::Local worklist_local(&worklist);
SomeObject dummy;
......@@ -108,7 +108,7 @@ TEST(CppgcWorkListTest, LocalPushPop) {
EXPECT_EQ(&dummy, retrieved);
}
TEST(CppgcWorkListTest, LocalPushStaysPrivate) {
TEST(WorkListTest, LocalPushStaysPrivate) {
TestWorklist worklist;
TestWorklist::Local worklist_local1(&worklist);
TestWorklist::Local worklist_local2(&worklist);
......@@ -125,7 +125,7 @@ TEST(CppgcWorkListTest, LocalPushStaysPrivate) {
EXPECT_EQ(0U, worklist.Size());
}
TEST(CppgcWorkListTest, LocalClear) {
TEST(WorkListTest, LocalClear) {
TestWorklist worklist;
TestWorklist::Local worklist_local(&worklist);
SomeObject* object;
......@@ -149,7 +149,7 @@ TEST(CppgcWorkListTest, LocalClear) {
EXPECT_TRUE(worklist_local.IsEmpty());
}
TEST(CppgcWorkListTest, GlobalUpdateNull) {
TEST(WorkListTest, GlobalUpdateNull) {
TestWorklist worklist;
TestWorklist::Local worklist_local(&worklist);
SomeObject* object;
......@@ -164,7 +164,7 @@ TEST(CppgcWorkListTest, GlobalUpdateNull) {
EXPECT_EQ(0U, worklist.Size());
}
TEST(CppgcWorkListTest, GlobalUpdate) {
TEST(WorkListTest, GlobalUpdate) {
TestWorklist worklist;
TestWorklist::Local worklist_local(&worklist);
SomeObject* objectA = nullptr;
......@@ -195,7 +195,7 @@ TEST(CppgcWorkListTest, GlobalUpdate) {
}
}
TEST(CppgcWorkListTest, FlushToGlobalPushSegment) {
TEST(WorkListTest, FlushToGlobalPushSegment) {
TestWorklist worklist;
TestWorklist::Local worklist_local0(&worklist);
TestWorklist::Local worklist_local1(&worklist);
......@@ -208,7 +208,7 @@ TEST(CppgcWorkListTest, FlushToGlobalPushSegment) {
EXPECT_TRUE(worklist_local1.Pop(&object));
}
TEST(CppgcWorkListTest, FlushToGlobalPopSegment) {
TEST(WorkListTest, FlushToGlobalPopSegment) {
TestWorklist worklist;
TestWorklist::Local worklist_local0(&worklist);
TestWorklist::Local worklist_local1(&worklist);
......@@ -223,7 +223,7 @@ TEST(CppgcWorkListTest, FlushToGlobalPopSegment) {
EXPECT_TRUE(worklist_local1.Pop(&object));
}
TEST(CppgcWorkListTest, Clear) {
TEST(WorkListTest, Clear) {
TestWorklist worklist;
TestWorklist::Local worklist_local(&worklist);
SomeObject* object;
......@@ -236,7 +236,7 @@ TEST(CppgcWorkListTest, Clear) {
EXPECT_EQ(0U, worklist.Size());
}
TEST(CppgcWorkListTest, SingleSegmentSteal) {
TEST(WorkListTest, SingleSegmentSteal) {
TestWorklist worklist;
TestWorklist::Local worklist_local1(&worklist);
TestWorklist::Local worklist_local2(&worklist);
......@@ -260,7 +260,7 @@ TEST(CppgcWorkListTest, SingleSegmentSteal) {
EXPECT_EQ(0U, worklist.Size());
}
TEST(CppgcWorkListTest, MultipleSegmentsStolen) {
TEST(WorkListTest, MultipleSegmentsStolen) {
TestWorklist worklist;
TestWorklist::Local worklist_local1(&worklist);
TestWorklist::Local worklist_local2(&worklist);
......@@ -302,7 +302,7 @@ TEST(CppgcWorkListTest, MultipleSegmentsStolen) {
EXPECT_TRUE(worklist.IsEmpty());
}
TEST(CppgcWorkListTest, MergeGlobalPool) {
TEST(WorkListTest, MergeGlobalPool) {
TestWorklist worklist1;
TestWorklist::Local worklist_local1(&worklist1);
SomeObject dummy;
......@@ -331,5 +331,25 @@ TEST(CppgcWorkListTest, MergeGlobalPool) {
EXPECT_TRUE(worklist2.IsEmpty());
}
TEST(WorkListTest, SwapGlobalPool) {
TestWorklist worklist1;
TestWorklist::Local worklist_local1(&worklist1);
SomeObject dummy;
worklist_local1.Push(&dummy);
worklist_local1.Publish();
TestWorklist worklist2;
EXPECT_FALSE(worklist1.IsEmpty());
EXPECT_TRUE(worklist2.IsEmpty());
worklist1.Swap(&worklist2);
EXPECT_TRUE(worklist1.IsEmpty());
EXPECT_FALSE(worklist2.IsEmpty());
TestWorklist::Local worklist_local2(&worklist2);
SomeObject* retrieved = nullptr;
EXPECT_TRUE(worklist_local2.Pop(&retrieved));
EXPECT_EQ(&dummy, retrieved);
EXPECT_FALSE(worklist_local2.Pop(&retrieved));
EXPECT_TRUE(worklist2.IsEmpty());
}
} // namespace base
} // namespace heap
// 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment