b7b3abe83a
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}
320 lines
9.9 KiB
C++
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
|