cppgc: Support ephemeron tracing

Cppgc exposes EphemeronPair that contains a WeakMember key and a Member
value and can be used to denote ephemeron semantics in the standalone
library.
Tracing EphemeronPairs goes through TraceEphemeron that is exposed on
the api for the blink usecase.

Bug: chromium:1056170
Change-Id: I9fbaa284fa2034248cdf36ea8b0cd5be6a55f676
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2467842
Commit-Queue: Omer Katz <omerkatz@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70525}
This commit is contained in:
Omer Katz 2020-10-15 11:03:15 +02:00 committed by Commit Bot
parent 58b4f729a1
commit 718fbb89ef
17 changed files with 312 additions and 8 deletions

View File

@ -4338,6 +4338,7 @@ v8_source_set("cppgc_base") {
"include/cppgc/common.h",
"include/cppgc/custom-space.h",
"include/cppgc/default-platform.h",
"include/cppgc/ephemeron-pair.h",
"include/cppgc/garbage-collected.h",
"include/cppgc/heap.h",
"include/cppgc/internal/api-constants.h",

View File

@ -0,0 +1,25 @@
// 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.
#ifndef INCLUDE_CPPGC_EPHEMERON_PAIR_H_
#define INCLUDE_CPPGC_EPHEMERON_PAIR_H_
#include "cppgc/member.h"
namespace cppgc {
/**
* An ephemeron pair is used to conditionally retain an object.
* The |value| will be kept alive only if the |key| is alive.
*/
template <typename K, typename V>
struct EphemeronPair {
EphemeronPair(K* k, V* v) : key(k), value(v) {}
WeakMember<K> key;
Member<V> value;
};
} // namespace cppgc
#endif // INCLUDE_CPPGC_EPHEMERON_PAIR_H_

View File

@ -5,6 +5,7 @@
#ifndef INCLUDE_CPPGC_VISITOR_H_
#define INCLUDE_CPPGC_VISITOR_H_
#include "cppgc/ephemeron-pair.h"
#include "cppgc/garbage-collected.h"
#include "cppgc/internal/logging.h"
#include "cppgc/internal/pointer-policies.h"
@ -123,6 +124,30 @@ class V8_EXPORT Visitor {
RegisterWeakCallback(&WeakCallbackMethodDelegate<T, method>, object);
}
/**
* Trace method for EphemeronPair.
*
* \param ephemeron_pair EphemeronPair reference weakly retaining a key object
* and strongly retaining a value object in case the key object is alive.
*/
template <typename K, typename V>
void Trace(const EphemeronPair<K, V>& ephemeron_pair) {
TraceEphemeron(ephemeron_pair.key, ephemeron_pair.value.GetRawAtomic());
}
/**
* Trace method for ephemerons. Used for tracing raw ephemeron in which the
* key and value are kept separately.
*
* \param key WeakMember reference weakly retaining a key object.
* \param value Member reference weakly retaining a value object.
*/
template <typename K, typename V>
void TraceEphemeron(const WeakMember<K>& key, const V* value) {
TraceDescriptor value_desc = TraceTrait<V>::GetTraceDescriptor(value);
VisitEphemeron(key, value_desc);
}
/**
* Registers a weak callback that is invoked during garbage collection.
*
@ -157,6 +182,7 @@ class V8_EXPORT Visitor {
virtual void VisitRoot(const void*, TraceDescriptor, const SourceLocation&) {}
virtual void VisitWeakRoot(const void* self, TraceDescriptor, WeakCallback,
const void* weak_root, const SourceLocation&) {}
virtual void VisitEphemeron(const void* key, TraceDescriptor value_desc) {}
private:
template <typename T, void (T::*method)(const LivenessBroker&)>

View File

@ -32,6 +32,11 @@ void UnifiedHeapMarkingVisitorBase::VisitWeak(const void* object,
weak_member);
}
void UnifiedHeapMarkingVisitorBase::VisitEphemeron(const void* key,
TraceDescriptor value_desc) {
marking_state_.ProcessEphemeron(key, value_desc);
}
void UnifiedHeapMarkingVisitorBase::RegisterWeakCallback(WeakCallback callback,
const void* object) {
marking_state_.RegisterWeakCallback(callback, object);

View File

@ -43,6 +43,7 @@ class V8_EXPORT_PRIVATE UnifiedHeapMarkingVisitorBase : public JSVisitor {
// C++ handling.
void Visit(const void*, TraceDescriptor) final;
void VisitWeak(const void*, TraceDescriptor, WeakCallback, const void*) final;
void VisitEphemeron(const void*, TraceDescriptor) final;
void RegisterWeakCallback(WeakCallback, const void*) final;
// JS handling.

View File

@ -145,6 +145,18 @@ void ConcurrentMarkingTask::ProcessWorklists(
})) {
return;
}
if (!DrainWorklistWithYielding(
job_delegate, concurrent_marking_state,
concurrent_marker_.incremental_marking_schedule(),
concurrent_marking_state.ephemeron_pairs_for_processing_worklist(),
[&concurrent_marking_state](
const MarkingWorklists::EphemeronPairItem& item) {
concurrent_marking_state.ProcessEphemeron(item.key,
item.value_desc);
})) {
return;
}
} while (
!concurrent_marking_state.marking_worklist().IsLocalAndGlobalEmpty());
}

View File

@ -11,6 +11,9 @@
namespace cppgc {
namespace internal {
// static
constexpr size_t IncrementalMarkingSchedule::kInvalidLastEstimatedLiveBytes;
const double IncrementalMarkingSchedule::kEstimatedMarkingTimeMs = 500.0;
const size_t IncrementalMarkingSchedule::kMinimumMarkedBytesPerIncrementalStep =
64 * kKB;
@ -52,6 +55,7 @@ double IncrementalMarkingSchedule::GetElapsedTimeInMs(
size_t IncrementalMarkingSchedule::GetNextIncrementalStepDuration(
size_t estimated_live_bytes) {
last_estimated_live_bytes_ = estimated_live_bytes;
DCHECK(!incremental_marking_start_time_.IsNull());
double elapsed_time_in_ms =
GetElapsedTimeInMs(incremental_marking_start_time_);
@ -73,5 +77,17 @@ size_t IncrementalMarkingSchedule::GetNextIncrementalStepDuration(
expected_marked_bytes - actual_marked_bytes);
}
constexpr double
IncrementalMarkingSchedule::kEphemeronPairsFlushingRatioIncrements;
bool IncrementalMarkingSchedule::ShouldFlushEphemeronPairs() {
DCHECK_NE(kInvalidLastEstimatedLiveBytes, last_estimated_live_bytes_);
if (GetOverallMarkedBytes() <
(ephemeron_pairs_flushing_ratio_target * last_estimated_live_bytes_))
return false;
ephemeron_pairs_flushing_ratio_target +=
kEphemeronPairsFlushingRatioIncrements;
return true;
}
} // namespace internal
} // namespace cppgc

View File

@ -35,6 +35,8 @@ class V8_EXPORT_PRIVATE IncrementalMarkingSchedule {
elapsed_time_for_testing_ = elapsed_time;
}
bool ShouldFlushEphemeronPairs();
private:
double GetElapsedTimeInMs(v8::base::TimeTicks);
@ -46,6 +48,11 @@ class V8_EXPORT_PRIVATE IncrementalMarkingSchedule {
// Using -1 as sentinel to denote
static constexpr double kNoSetElapsedTimeForTesting = -1;
double elapsed_time_for_testing_ = kNoSetElapsedTimeForTesting;
static constexpr size_t kInvalidLastEstimatedLiveBytes = -1;
size_t last_estimated_live_bytes_ = kInvalidLastEstimatedLiveBytes;
double ephemeron_pairs_flushing_ratio_target = 0.25;
static constexpr double kEphemeronPairsFlushingRatioIncrements = 0.25;
};
} // namespace internal

View File

@ -173,6 +173,20 @@ MarkerBase::~MarkerBase() {
for (HeapObjectHeader* object : objects) DCHECK(object->IsMarked());
#else
marking_worklists_.not_fully_constructed_worklist()->Clear();
#endif
}
// |discovered_ephemeron_pairs_worklist_| may still hold ephemeron pairs with
// dead keys.
if (!marking_worklists_.discovered_ephemeron_pairs_worklist()->IsEmpty()) {
#if DEBUG
MarkingWorklists::EphemeronPairItem item;
while (mutator_marking_state_.discovered_ephemeron_pairs_worklist().Pop(
&item)) {
DCHECK(!HeapObjectHeader::FromPayload(item.key).IsMarked());
}
#else
marking_worklists_.discovered_ephemeron_pairs_worklist()->Clear();
#endif
}
}
@ -215,6 +229,7 @@ void MarkerBase::EnterAtomicPause(MarkingConfig::StackState stack_state) {
VisitRoots(config_.stack_state);
if (config_.stack_state == MarkingConfig::StackState::kNoHeapPointers) {
mutator_marking_state_.FlushNotFullyConstructedObjects();
DCHECK(marking_worklists_.not_fully_constructed_worklist()->IsEmpty());
} else {
MarkNotFullyConstructedObjects();
}
@ -318,8 +333,9 @@ bool MarkerBase::AdvanceMarkingWithDeadline(v8::base::TimeDelta max_duration) {
is_done = ProcessWorklistsWithDeadline(
mutator_marking_state_.marked_bytes() + step_size_in_bytes,
v8::base::TimeTicks::Now() + max_duration);
schedule_.UpdateIncrementalMarkedBytes(
mutator_marking_state_.marked_bytes());
}
schedule_.UpdateIncrementalMarkedBytes(mutator_marking_state_.marked_bytes());
mutator_marking_state_.Publish();
if (!is_done) {
// If marking is atomic, |is_done| should always be true.
@ -336,6 +352,11 @@ bool MarkerBase::AdvanceMarkingWithDeadline(v8::base::TimeDelta max_duration) {
bool MarkerBase::ProcessWorklistsWithDeadline(
size_t marked_bytes_deadline, v8::base::TimeTicks time_deadline) {
do {
if ((config_.marking_type == MarkingConfig::MarkingType::kAtomic) ||
schedule_.ShouldFlushEphemeronPairs()) {
mutator_marking_state_.FlushDiscoveredEphemeronPairs();
}
// Bailout objects may be complicated to trace and thus might take longer
// than other objects. Therefore we reduce the interval between deadline
// checks to guarantee the deadline is not exceeded.
@ -387,6 +408,16 @@ bool MarkerBase::ProcessWorklistsWithDeadline(
})) {
return false;
}
if (!DrainWorklistWithBytesAndTimeDeadline(
mutator_marking_state_, marked_bytes_deadline, time_deadline,
mutator_marking_state_.ephemeron_pairs_for_processing_worklist(),
[this](const MarkingWorklists::EphemeronPairItem& item) {
mutator_marking_state_.ProcessEphemeron(item.key,
item.value_desc);
})) {
return false;
}
} while (!mutator_marking_state_.marking_worklist().IsLocalAndGlobalEmpty());
return true;
}

View File

@ -16,7 +16,14 @@ void MutatorMarkingState::FlushNotFullyConstructedObjects() {
if (MarkNoPush(*object))
previously_not_fully_constructed_worklist_.Push(object);
}
DCHECK(not_fully_constructed_worklist_.IsEmpty());
}
void MutatorMarkingState::FlushDiscoveredEphemeronPairs() {
discovered_ephemeron_pairs_worklist_.Publish();
if (!discovered_ephemeron_pairs_worklist_.IsGlobalEmpty()) {
ephemeron_pairs_for_processing_worklist_.Merge(
&discovered_ephemeron_pairs_worklist_);
}
}
} // namespace internal

View File

@ -30,6 +30,8 @@ class MarkingStateBase {
WeakCallback, const void*);
inline void RegisterWeakCallback(WeakCallback, const void*);
inline void ProcessEphemeron(const void*, TraceDescriptor);
inline void AccountMarkedBytes(const HeapObjectHeader&);
inline void AccountMarkedBytes(size_t);
size_t marked_bytes() const { return marked_bytes_; }
@ -40,6 +42,8 @@ class MarkingStateBase {
weak_callback_worklist_.Publish();
write_barrier_worklist_.Publish();
concurrent_marking_bailout_worklist_.Publish();
discovered_ephemeron_pairs_worklist_.Publish();
ephemeron_pairs_for_processing_worklist_.Publish();
}
MarkingWorklists::MarkingWorklist::Local& marking_worklist() {
@ -63,6 +67,14 @@ class MarkingStateBase {
concurrent_marking_bailout_worklist() {
return concurrent_marking_bailout_worklist_;
}
MarkingWorklists::EphemeronPairsWorklist::Local&
discovered_ephemeron_pairs_worklist() {
return discovered_ephemeron_pairs_worklist_;
}
MarkingWorklists::EphemeronPairsWorklist::Local&
ephemeron_pairs_for_processing_worklist() {
return ephemeron_pairs_for_processing_worklist_;
}
protected:
inline void MarkAndPush(HeapObjectHeader&, TraceDescriptor);
@ -82,6 +94,10 @@ class MarkingStateBase {
MarkingWorklists::WriteBarrierWorklist::Local write_barrier_worklist_;
MarkingWorklists::ConcurrentMarkingBailoutWorklist::Local
concurrent_marking_bailout_worklist_;
MarkingWorklists::EphemeronPairsWorklist::Local
discovered_ephemeron_pairs_worklist_;
MarkingWorklists::EphemeronPairsWorklist::Local
ephemeron_pairs_for_processing_worklist_;
size_t marked_bytes_ = 0;
};
@ -100,7 +116,11 @@ MarkingStateBase::MarkingStateBase(HeapBase& heap,
weak_callback_worklist_(marking_worklists.weak_callback_worklist()),
write_barrier_worklist_(marking_worklists.write_barrier_worklist()),
concurrent_marking_bailout_worklist_(
marking_worklists.concurrent_marking_bailout_worklist()) {
marking_worklists.concurrent_marking_bailout_worklist()),
discovered_ephemeron_pairs_worklist_(
marking_worklists.discovered_ephemeron_pairs_worklist()),
ephemeron_pairs_for_processing_worklist_(
marking_worklists.ephemeron_pairs_for_processing_worklist()) {
}
void MarkingStateBase::MarkAndPush(const void* object, TraceDescriptor desc) {
@ -150,6 +170,24 @@ void MarkingStateBase::RegisterWeakReferenceIfNeeded(const void* object,
RegisterWeakCallback(weak_callback, parameter);
}
void MarkingStateBase::RegisterWeakCallback(WeakCallback callback,
const void* object) {
weak_callback_worklist_.Push({callback, object});
}
void MarkingStateBase::ProcessEphemeron(const void* key,
TraceDescriptor value_desc) {
// Filter out already marked keys. The write barrier for WeakMember
// ensures that any newly set value after this point is kept alive and does
// not require the callback.
if (HeapObjectHeader::FromPayload(key)
.IsMarked<HeapObjectHeader::AccessMode::kAtomic>()) {
MarkAndPush(value_desc.base_object_payload, value_desc);
return;
}
discovered_ephemeron_pairs_worklist_.Push({key, value_desc});
}
void MarkingStateBase::AccountMarkedBytes(const HeapObjectHeader& header) {
AccountMarkedBytes(
header.IsLargeObject<HeapObjectHeader::AccessMode::kAtomic>()
@ -177,6 +215,10 @@ class MutatorMarkingState : public MarkingStateBase {
// previously_not_full_constructed_worklists_.
void FlushNotFullyConstructedObjects();
// Moves ephemeron pairs in discovered_ephemeron_pairs_worklist_ to
// ephemeron_pairs_for_processing_worklist_.
void FlushDiscoveredEphemeronPairs();
inline void InvokeWeakRootsCallbackIfNeeded(const void*, TraceDescriptor,
WeakCallback, const void*);
};
@ -206,11 +248,6 @@ void MutatorMarkingState::InvokeWeakRootsCallbackIfNeeded(
weak_callback(LivenessBrokerFactory::Create(), parameter);
}
void MarkingStateBase::RegisterWeakCallback(WeakCallback callback,
const void* object) {
weak_callback_worklist_.Push({callback, object});
}
class ConcurrentMarkingState : public MarkingStateBase {
public:
ConcurrentMarkingState(HeapBase& heap, MarkingWorklists& marking_worklists)

View File

@ -25,6 +25,11 @@ void MarkingVisitorBase::VisitWeak(const void* object, TraceDescriptor desc,
weak_member);
}
void MarkingVisitorBase::VisitEphemeron(const void* key,
TraceDescriptor value_desc) {
marking_state_.ProcessEphemeron(key, value_desc);
}
void MarkingVisitorBase::RegisterWeakCallback(WeakCallback callback,
const void* object) {
marking_state_.RegisterWeakCallback(callback, object);

View File

@ -28,6 +28,7 @@ class V8_EXPORT_PRIVATE MarkingVisitorBase : public VisitorBase {
protected:
void Visit(const void*, TraceDescriptor) final;
void VisitWeak(const void*, TraceDescriptor, WeakCallback, const void*) final;
void VisitEphemeron(const void*, TraceDescriptor) final;
void RegisterWeakCallback(WeakCallback, const void*) final;
MarkingStateBase& marking_state_;

View File

@ -17,6 +17,8 @@ void MarkingWorklists::ClearForTesting() {
write_barrier_worklist_.Clear();
weak_callback_worklist_.Clear();
concurrent_marking_bailout_worklist_.Clear();
discovered_ephemeron_pairs_worklist_.Clear();
ephemeron_pairs_for_processing_worklist_.Clear();
}
void MarkingWorklists::NotFullyConstructedWorklist::Push(

View File

@ -33,6 +33,11 @@ class MarkingWorklists {
size_t bailedout_size;
};
struct EphemeronPairItem {
const void* key;
TraceDescriptor value_desc;
};
// Segment size of 512 entries necessary to avoid throughput regressions.
// Since the work list is currently a temporary object this is not a problem.
using MarkingWorklist =
@ -46,6 +51,8 @@ class MarkingWorklists {
using ConcurrentMarkingBailoutWorklist =
heap::base::Worklist<ConcurrentMarkingBailoutItem,
64 /* local entries */>;
using EphemeronPairsWorklist =
heap::base::Worklist<EphemeronPairItem, 64 /* local entries */>;
class V8_EXPORT_PRIVATE NotFullyConstructedWorklist {
public:
@ -85,6 +92,12 @@ class MarkingWorklists {
ConcurrentMarkingBailoutWorklist* concurrent_marking_bailout_worklist() {
return &concurrent_marking_bailout_worklist_;
}
EphemeronPairsWorklist* discovered_ephemeron_pairs_worklist() {
return &discovered_ephemeron_pairs_worklist_;
}
EphemeronPairsWorklist* ephemeron_pairs_for_processing_worklist() {
return &ephemeron_pairs_for_processing_worklist_;
}
void ClearForTesting();
@ -96,6 +109,8 @@ class MarkingWorklists {
WriteBarrierWorklist write_barrier_worklist_;
WeakCallbackWorklist weak_callback_worklist_;
ConcurrentMarkingBailoutWorklist concurrent_marking_bailout_worklist_;
EphemeronPairsWorklist discovered_ephemeron_pairs_worklist_;
EphemeronPairsWorklist ephemeron_pairs_for_processing_worklist_;
};
} // namespace internal

View File

@ -84,6 +84,7 @@ v8_source_set("cppgc_unittests_sources") {
"heap/cppgc/concurrent-sweeper-unittest.cc",
"heap/cppgc/cross-thread-persistent-unittest.cc",
"heap/cppgc/custom-spaces-unittest.cc",
"heap/cppgc/ephemeron-pair-unittest.cc",
"heap/cppgc/finalizer-trait-unittest.cc",
"heap/cppgc/free-list-unittest.cc",
"heap/cppgc/garbage-collected-unittest.cc",

View File

@ -0,0 +1,112 @@
// 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/ephemeron-pair.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/marking-visitor.h"
#include "src/heap/cppgc/stats-collector.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class GCed : public GarbageCollected<GCed> {
public:
void Trace(cppgc::Visitor*) const {}
};
class EphemeronHolder : public GarbageCollected<GCed> {
public:
EphemeronHolder(GCed* key, GCed* value) : ephemeron_pair_(key, value) {}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(ephemeron_pair_); }
private:
EphemeronPair<GCed, GCed> ephemeron_pair_;
};
class EhpemeronPairTest : public testing::TestWithHeap {
using MarkingConfig = Marker::MarkingConfig;
static constexpr Marker::MarkingConfig IncrementalPreciseMarkingConfig = {
MarkingConfig::CollectionType::kMajor,
MarkingConfig::StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kIncremental};
public:
void FinishSteps() {
while (!SingleStep()) {
}
}
void FinishMarking() {
marker_->FinishMarking(MarkingConfig::StackState::kNoHeapPointers);
// Pretend do finish sweeping as StatsCollector verifies that Notify*
// methods are called in the right order.
Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted();
}
void InitializeMarker(HeapBase& heap, cppgc::Platform* platform) {
marker_ = MarkerFactory::CreateAndStartMarking<Marker>(
heap, platform, IncrementalPreciseMarkingConfig);
}
Marker* marker() const { return marker_.get(); }
private:
bool SingleStep() {
return marker_->IncrementalMarkingStepForTesting(
MarkingConfig::StackState::kNoHeapPointers);
}
std::unique_ptr<Marker> marker_;
};
// static
constexpr Marker::MarkingConfig
EhpemeronPairTest::IncrementalPreciseMarkingConfig;
} // namespace
TEST_F(EhpemeronPairTest, ValueMarkedWhenKeyIsMarked) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder =
MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value);
HeapObjectHeader::FromPayload(key).TryMarkAtomic();
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishMarking();
EXPECT_TRUE(HeapObjectHeader::FromPayload(value).IsMarked());
}
TEST_F(EhpemeronPairTest, ValueNotMarkedWhenKeyIsNotMarked) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder =
MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value);
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishMarking();
EXPECT_FALSE(HeapObjectHeader::FromPayload(key).IsMarked());
EXPECT_FALSE(HeapObjectHeader::FromPayload(value).IsMarked());
}
TEST_F(EhpemeronPairTest, ValueNotMarkedBeforeKey) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder =
MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value);
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishSteps();
EXPECT_FALSE(HeapObjectHeader::FromPayload(value).IsMarked());
HeapObjectHeader::FromPayload(key).TryMarkAtomic();
FinishMarking();
EXPECT_TRUE(HeapObjectHeader::FromPayload(value).IsMarked());
}
} // namespace internal
} // namespace cppgc