v8/test/unittests/heap/cppgc/write-barrier-unittest.cc
Omer Katz b7b3abe83a cppgc: Replace worklist implementation with new worklist
This CL migrates cppgc to use Ulan's new worklist implementation.

Since there is no central segments array anymore, we cannot rely on
getting the same view (now renamed to Local) given the same task id.
To avoid creating many short lived segments (e.g. for write barriers)
marking state now holds local views for all worklists and provides
access to them.

Bug: chromium:1056170
Change-Id: Id19fe1196b79ed251810e91074046998dc2a9177
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2390771
Commit-Queue: Omer Katz <omerkatz@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69767}
2020-09-09 09:51:59 +00:00

320 lines
9.9 KiB
C++

// Copyright 2020 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 "include/cppgc/internal/write-barrier.h"
#include <algorithm>
#include <initializer_list>
#include <vector>
#include "include/cppgc/internal/pointer-policies.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/marker.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class IncrementalMarkingScope {
public:
explicit IncrementalMarkingScope(MarkerBase* marker) : marker_(marker) {}
~IncrementalMarkingScope() V8_NOEXCEPT {
marker_->FinishMarking(kIncrementalConfig.stack_state);
}
static constexpr Marker::MarkingConfig kIncrementalConfig{
Marker::MarkingConfig::CollectionType::kMajor,
Marker::MarkingConfig::StackState::kNoHeapPointers,
Marker::MarkingConfig::MarkingType::kIncremental};
private:
MarkerBase* marker_;
};
constexpr Marker::MarkingConfig IncrementalMarkingScope::kIncrementalConfig;
class ExpectWriteBarrierFires final : private IncrementalMarkingScope {
public:
ExpectWriteBarrierFires(MarkerBase* marker,
std::initializer_list<void*> objects)
: IncrementalMarkingScope(marker),
marking_worklist_(marker->MarkingStateForTesting().marking_worklist()),
write_barrier_worklist_(
marker->MarkingStateForTesting().write_barrier_worklist()),
objects_(objects) {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (void* object : objects) {
headers_.push_back(&HeapObjectHeader::FromPayload(object));
EXPECT_FALSE(headers_.back()->IsMarked());
}
}
~ExpectWriteBarrierFires() V8_NOEXCEPT {
{
MarkingWorklists::MarkingItem item;
while (marking_worklist_.Pop(&item)) {
auto pos = std::find(objects_.begin(), objects_.end(),
item.base_object_payload);
if (pos != objects_.end()) objects_.erase(pos);
}
}
{
HeapObjectHeader* item;
while (write_barrier_worklist_.Pop(&item)) {
auto pos = std::find(objects_.begin(), objects_.end(), item->Payload());
if (pos != objects_.end()) objects_.erase(pos);
}
}
EXPECT_TRUE(objects_.empty());
for (auto* header : headers_) {
EXPECT_TRUE(header->IsMarked());
header->Unmark();
}
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
}
private:
MarkingWorklists::MarkingWorklist::Local& marking_worklist_;
MarkingWorklists::WriteBarrierWorklist::Local& write_barrier_worklist_;
std::vector<void*> objects_;
std::vector<HeapObjectHeader*> headers_;
};
class ExpectNoWriteBarrierFires final : private IncrementalMarkingScope {
public:
ExpectNoWriteBarrierFires(MarkerBase* marker,
std::initializer_list<void*> objects)
: IncrementalMarkingScope(marker),
marking_worklist_(marker->MarkingStateForTesting().marking_worklist()),
write_barrier_worklist_(
marker->MarkingStateForTesting().write_barrier_worklist()) {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (void* object : objects) {
auto* header = &HeapObjectHeader::FromPayload(object);
headers_.emplace_back(header, header->IsMarked());
}
}
~ExpectNoWriteBarrierFires() {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (const auto& pair : headers_) {
EXPECT_EQ(pair.second, pair.first->IsMarked());
}
}
private:
MarkingWorklists::MarkingWorklist::Local& marking_worklist_;
MarkingWorklists::WriteBarrierWorklist::Local& write_barrier_worklist_;
std::vector<std::pair<HeapObjectHeader*, bool /* was marked */>> headers_;
};
class GCed : public GarbageCollected<GCed> {
public:
GCed() = default;
explicit GCed(GCed* next) : next_(next) {}
void Trace(cppgc::Visitor* v) const { v->Trace(next_); }
bool IsMarked() const {
return HeapObjectHeader::FromPayload(this).IsMarked();
}
void set_next(GCed* next) { next_ = next; }
GCed* next() const { return next_; }
Member<GCed>& next_ref() { return next_; }
private:
Member<GCed> next_ = nullptr;
};
} // namespace
class WriteBarrierTest : public testing::TestWithHeap {
public:
WriteBarrierTest() : internal_heap_(Heap::From(GetHeap())) {
GetMarkerRef() = MarkerFactory::CreateAndStartMarking<Marker>(
*internal_heap_, GetPlatformHandle().get(),
IncrementalMarkingScope::kIncrementalConfig);
marker_ = GetMarkerRef().get();
}
~WriteBarrierTest() override {
marker_->ClearAllWorklistsForTesting();
GetMarkerRef().reset();
}
MarkerBase* marker() const { return marker_; }
private:
Heap* internal_heap_;
MarkerBase* marker_;
};
class NoWriteBarrierTest : public testing::TestWithHeap {};
// =============================================================================
// Basic support. ==============================================================
// =============================================================================
TEST_F(WriteBarrierTest, EnableDisableIncrementalMarking) {
{
IncrementalMarkingScope scope(marker());
EXPECT_TRUE(ProcessHeap::IsAnyIncrementalOrConcurrentMarking());
}
}
TEST_F(WriteBarrierTest, TriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_FALSE(object1->IsMarked());
object2->set_next(object1);
EXPECT_TRUE(object1->IsMarked());
}
}
TEST_F(NoWriteBarrierTest, BailoutWhenMarkingIsOff) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_FALSE(object1->IsMarked());
object2->set_next(object1);
EXPECT_FALSE(object1->IsMarked());
}
TEST_F(WriteBarrierTest, BailoutIfMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_TRUE(HeapObjectHeader::FromPayload(object1).TryMarkAtomic());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
object2->set_next(object1);
}
}
TEST_F(WriteBarrierTest, MemberInitializingStoreNoBarrier) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
HeapObjectHeader& object2_header = HeapObjectHeader::FromPayload(object2);
EXPECT_FALSE(object2_header.IsMarked());
}
}
TEST_F(WriteBarrierTest, MemberReferenceAssignMember) {
auto* obj = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* ref_obj = MakeGarbageCollected<GCed>(GetAllocationHandle());
Member<GCed>& m2 = ref_obj->next_ref();
Member<GCed> m3(obj);
{
ExpectWriteBarrierFires scope(marker(), {obj});
m2 = m3;
}
}
TEST_F(WriteBarrierTest, MemberSetSentinelValueNoBarrier) {
auto* obj = MakeGarbageCollected<GCed>(GetAllocationHandle());
Member<GCed>& m = obj->next_ref();
{
ExpectNoWriteBarrierFires scope(marker(), {});
m = kSentinelPointer;
}
}
TEST_F(WriteBarrierTest, MemberCopySentinelValueNoBarrier) {
auto* obj1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
Member<GCed>& m1 = obj1->next_ref();
m1 = kSentinelPointer;
{
ExpectNoWriteBarrierFires scope(marker(), {});
auto* obj2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
obj2->next_ref() = m1;
}
}
// =============================================================================
// Mixin support. ==============================================================
// =============================================================================
namespace {
class Mixin : public GarbageCollectedMixin {
public:
void Trace(cppgc::Visitor* visitor) const override { visitor->Trace(next_); }
virtual void Bar() {}
protected:
Member<GCed> next_;
};
class ClassWithVirtual {
protected:
virtual void Foo() {}
};
class Child : public GarbageCollected<Child>,
public ClassWithVirtual,
public Mixin {
public:
Child() : ClassWithVirtual(), Mixin() {}
~Child() = default;
void Trace(cppgc::Visitor* visitor) const override { Mixin::Trace(visitor); }
void Foo() override {}
void Bar() override {}
};
class ParentWithMixinPointer : public GarbageCollected<ParentWithMixinPointer> {
public:
ParentWithMixinPointer() = default;
void set_mixin(Mixin* mixin) { mixin_ = mixin; }
virtual void Trace(cppgc::Visitor* visitor) const { visitor->Trace(mixin_); }
protected:
Member<Mixin> mixin_;
};
} // namespace
TEST_F(WriteBarrierTest, WriteBarrierOnUnmarkedMixinApplication) {
ParentWithMixinPointer* parent =
MakeGarbageCollected<ParentWithMixinPointer>(GetAllocationHandle());
auto* child = MakeGarbageCollected<Child>(GetAllocationHandle());
Mixin* mixin = static_cast<Mixin*>(child);
EXPECT_NE(static_cast<void*>(child), static_cast<void*>(mixin));
{
ExpectWriteBarrierFires scope(marker(), {child});
parent->set_mixin(mixin);
}
}
TEST_F(WriteBarrierTest, NoWriteBarrierOnMarkedMixinApplication) {
ParentWithMixinPointer* parent =
MakeGarbageCollected<ParentWithMixinPointer>(GetAllocationHandle());
auto* child = MakeGarbageCollected<Child>(GetAllocationHandle());
EXPECT_TRUE(HeapObjectHeader::FromPayload(child).TryMarkAtomic());
Mixin* mixin = static_cast<Mixin*>(child);
EXPECT_NE(static_cast<void*>(child), static_cast<void*>(mixin));
{
ExpectNoWriteBarrierFires scope(marker(), {child});
parent->set_mixin(mixin);
}
}
} // namespace internal
} // namespace cppgc