v8/test/unittests/heap/cppgc/write-barrier-unittest.cc
Anton Bikineev 5c92b06ead cppgc: young-gen: Fix compilation and tests with cppgc_enable_young_gen
The CL prepares the sources and the tests for enabling
cppgc_enable_young_generation by default. The static initializer
in YoungGenerationEnabler (due to v8::base::Mutex) changed to be lazy.
The tests are now checking the runtime flag.

Bug: chromium:1029379
Change-Id: I1497a3dd2b8d62c1acd48496821f07324b7944d5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3616726
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Auto-Submit: Anton Bikineev <bikineev@chromium.org>
Commit-Queue: Anton Bikineev <bikineev@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80304}
2022-05-02 13:34:04 +00:00

491 lines
16 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/heap-consistency.h"
#include "include/cppgc/internal/pointer-policies.h"
#include "src/base/logging.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 V8_NODISCARD 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 V8_NODISCARD ExpectWriteBarrierFires final
: private IncrementalMarkingScope {
public:
ExpectWriteBarrierFires(MarkerBase* marker,
std::initializer_list<void*> objects)
: IncrementalMarkingScope(marker),
marking_worklist_(
marker->MutatorMarkingStateForTesting().marking_worklist()),
write_barrier_worklist_(
marker->MutatorMarkingStateForTesting().write_barrier_worklist()),
retrace_marked_objects_worklist_(
marker->MutatorMarkingStateForTesting()
.retrace_marked_objects_worklist()),
objects_(objects) {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (void* object : objects) {
headers_.push_back(&HeapObjectHeader::FromObject(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->ObjectStart());
if (pos != objects_.end()) objects_.erase(pos);
}
}
{
HeapObjectHeader* item;
while (retrace_marked_objects_worklist_.Pop(&item)) {
auto pos =
std::find(objects_.begin(), objects_.end(), item->ObjectStart());
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_;
MarkingWorklists::RetraceMarkedObjectsWorklist::Local&
retrace_marked_objects_worklist_;
std::vector<void*> objects_;
std::vector<HeapObjectHeader*> headers_;
};
class V8_NODISCARD ExpectNoWriteBarrierFires final
: private IncrementalMarkingScope {
public:
ExpectNoWriteBarrierFires(MarkerBase* marker,
std::initializer_list<void*> objects)
: IncrementalMarkingScope(marker),
marking_worklist_(
marker->MutatorMarkingStateForTesting().marking_worklist()),
write_barrier_worklist_(
marker->MutatorMarkingStateForTesting().write_barrier_worklist()) {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (void* object : objects) {
auto* header = &HeapObjectHeader::FromObject(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::FromObject(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())) {
DCHECK_NULL(GetMarkerRef().get());
GetMarkerRef() =
std::make_unique<Marker>(*internal_heap_, GetPlatformHandle().get(),
IncrementalMarkingScope::kIncrementalConfig);
marker_ = GetMarkerRef().get();
marker_->StartMarking();
}
~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(WriteBarrier::IsEnabled());
}
}
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::FromObject(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::FromObject(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::FromObject(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);
}
}
// =============================================================================
// Raw barriers. ===============================================================
// =============================================================================
using WriteBarrierParams = subtle::HeapConsistency::WriteBarrierParams;
using WriteBarrierType = subtle::HeapConsistency::WriteBarrierType;
using subtle::HeapConsistency;
TEST_F(NoWriteBarrierTest, WriteBarrierBailoutWhenMarkingIsOff) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
EXPECT_FALSE(object1->IsMarked());
WriteBarrierParams params;
const WriteBarrierType expected =
Heap::From(GetHeap())->generational_gc_supported()
? WriteBarrierType::kGenerational
: WriteBarrierType::kNone;
EXPECT_EQ(expected, HeapConsistency::GetWriteBarrierType(
object2->next_ref().GetSlotForTesting(),
object2->next_ref().Get(), params));
EXPECT_FALSE(object1->IsMarked());
}
}
TEST_F(WriteBarrierTest, DijkstraWriteBarrierTriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_FALSE(object1->IsMarked());
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->next_ref().GetSlotForTesting(),
object2->next_ref().Get(), params));
HeapConsistency::DijkstraWriteBarrier(params, object2->next_ref().Get());
EXPECT_TRUE(object1->IsMarked());
}
}
TEST_F(WriteBarrierTest, DijkstraWriteBarrierBailoutIfMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->next_ref().GetSlotForTesting(),
object2->next_ref().Get(), params));
HeapConsistency::DijkstraWriteBarrier(params, object2->next_ref().Get());
}
}
namespace {
struct InlinedObject {
void Trace(cppgc::Visitor* v) const { v->Trace(ref); }
Member<GCed> ref;
};
class GCedWithInlinedArray : public GarbageCollected<GCedWithInlinedArray> {
public:
static constexpr size_t kNumReferences = 4;
explicit GCedWithInlinedArray(GCed* value2) {
new (&objects[2].ref) Member<GCed>(value2);
}
void Trace(cppgc::Visitor* v) const {
for (size_t i = 0; i < kNumReferences; ++i) {
v->Trace(objects[i]);
}
}
InlinedObject objects[kNumReferences];
};
} // namespace
TEST_F(WriteBarrierTest, DijkstraWriteBarrierRangeTriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCedWithInlinedArray>(
GetAllocationHandle(), object1);
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_FALSE(object1->IsMarked());
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->objects, params, [this]() -> HeapHandle& {
return GetHeap()->GetHeapHandle();
}));
HeapConsistency::DijkstraWriteBarrierRange(
params, object2->objects, sizeof(InlinedObject), 4,
TraceTrait<InlinedObject>::Trace);
EXPECT_TRUE(object1->IsMarked());
}
}
TEST_F(WriteBarrierTest, DijkstraWriteBarrierRangeBailoutIfMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCedWithInlinedArray>(
GetAllocationHandle(), object1);
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->objects, params, [this]() -> HeapHandle& {
return GetHeap()->GetHeapHandle();
}));
HeapConsistency::DijkstraWriteBarrierRange(
params, object2->objects, sizeof(InlinedObject), 4,
TraceTrait<InlinedObject>::Trace);
}
}
TEST_F(WriteBarrierTest, SteeleWriteBarrierTriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
&object2->next_ref(), object2->next_ref().Get(), params));
HeapConsistency::SteeleWriteBarrier(params, object2->next_ref().Get());
}
}
TEST_F(WriteBarrierTest, SteeleWriteBarrierBailoutIfNotMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
&object2->next_ref(), object2->next_ref().Get(), params));
HeapConsistency::SteeleWriteBarrier(params, object2->next_ref().Get());
}
}
} // namespace internal
} // namespace cppgc