blob: b3e547e9a3cb977e09896a5fee8b2e8f1b581b61 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include <algorithm>
#include <iomanip>
#include <limits>
#include <memory>
#include "base/atomicops.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/task_runner.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/bindings/active_script_wrappable_base.h"
#include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/heap/blink_gc.h"
#include "third_party/blink/renderer/platform/heap/blink_gc_memory_dump_provider.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_buildflags.h"
#include "third_party/blink/renderer/platform/heap/heap_stats_collector.h"
#include "third_party/blink/renderer/platform/heap/impl/heap_compact.h"
#include "third_party/blink/renderer/platform/heap/impl/marking_scheduling_oracle.h"
#include "third_party/blink/renderer/platform/heap/impl/marking_visitor.h"
#include "third_party/blink/renderer/platform/heap/impl/page_pool.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/heap/thread_state_scopes.h"
#include "third_party/blink/renderer/platform/heap/unified_heap_controller.h"
#include "third_party/blink/renderer/platform/heap/unified_heap_marking_visitor.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/stack_util.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
#include "v8/include/v8-profiler.h"
#include "v8/include/v8.h"
#if defined(OS_WIN)
#include <stddef.h>
#include <windows.h>
#include <winnt.h>
#endif
#if defined(MEMORY_SANITIZER)
#include <sanitizer/msan_interface.h>
#endif
#if defined(OS_FREEBSD)
#include <pthread_np.h>
#endif
namespace blink {
WTF::ThreadSpecific<ThreadState*>* ThreadState::thread_specific_ = nullptr;
uint8_t ThreadState::main_thread_state_storage_[sizeof(ThreadState)];
namespace {
constexpr double kMarkingScheduleRatioBeforeConcurrentPriorityIncrease = 0.5;
// Helper function to convert a byte count to a KB count, capping at
// INT_MAX if the number is larger than that.
constexpr base::Histogram::Sample CappedSizeInKB(size_t size_in_bytes) {
return base::saturated_cast<base::Histogram::Sample>(size_in_bytes / 1024);
}
class WorkerPoolTaskRunner : public base::TaskRunner {
public:
bool PostDelayedTask(const base::Location& location,
base::OnceClosure task,
base::TimeDelta) override {
worker_pool::PostTask(location, WTF::CrossThreadBindOnce(std::move(task)));
return true;
}
};
} // namespace
class ThreadState::IncrementalMarkingScheduler {
public:
explicit IncrementalMarkingScheduler(ThreadState* thread_state)
: thread_state_(thread_state) {}
// Starts incremental marking with further scheduled steps.
void Start(BlinkGC::GCReason reason) {
Init(reason);
thread_state_->IncrementalMarkingStart(reason_);
ScheduleTask();
}
void Restart() {
DCHECK(!task_.IsActive());
ScheduleTask();
}
// Cancels incremental marking task in case there is any pending.
void Cancel() { task_.Cancel(); }
private:
void Init(BlinkGC::GCReason reason) {
DCHECK(!task_.IsActive());
reason_ = reason;
}
void ScheduleTask() {
// Reassigning to the task will cancel the currently scheduled one.
task_ = PostNonNestableCancellableTask(
*ThreadScheduler::Current()->V8TaskRunner(), FROM_HERE,
WTF::Bind(&IncrementalMarkingScheduler::Dispatch,
WTF::Unretained(this)));
}
void Dispatch() {
switch (thread_state_->GetGCState()) {
case ThreadState::kIncrementalMarkingStepScheduled:
thread_state_->IncrementalMarkingStep(BlinkGC::kNoHeapPointersOnStack);
if (thread_state_->GetGCState() !=
ThreadState::kIncrementalMarkingStepPaused) {
ScheduleTask();
}
break;
case ThreadState::kIncrementalMarkingFinalizeScheduled:
thread_state_->IncrementalMarkingFinalize();
break;
default:
break;
}
}
ThreadState* thread_state_;
BlinkGC::GCReason reason_;
TaskHandle task_;
};
ThreadState::ThreadState()
: thread_(CurrentThread()),
persistent_region_(std::make_unique<PersistentRegion>()),
weak_persistent_region_(std::make_unique<PersistentRegion>()),
start_of_stack_(reinterpret_cast<Address*>(WTF::GetStackStart())),
#if defined(ADDRESS_SANITIZER)
asan_fake_stack_(__asan_get_current_fake_stack()),
#endif
incremental_marking_scheduler_(
std::make_unique<IncrementalMarkingScheduler>(this)) {
DCHECK(CheckThread());
DCHECK(!**thread_specific_);
**thread_specific_ = this;
heap_ = std::make_unique<ThreadHeap>(this);
}
ThreadState::~ThreadState() {
DCHECK(CheckThread());
if (IsMainThread())
DCHECK_EQ(0u, Heap().stats_collector()->allocated_space_bytes());
CHECK(GetGCState() == ThreadState::kNoGCScheduled);
**thread_specific_ = nullptr;
}
ThreadState* ThreadState::AttachMainThread() {
thread_specific_ = new WTF::ThreadSpecific<ThreadState*>();
return new (main_thread_state_storage_) ThreadState();
}
ThreadState* ThreadState::AttachCurrentThread() {
return new ThreadState();
}
void ThreadState::DetachCurrentThread() {
ThreadState* state = Current();
DCHECK(!state->IsMainThread());
state->RunTerminationGC();
delete state;
}
void ThreadState::AttachToIsolate(
v8::Isolate* isolate,
V8BuildEmbedderGraphCallback v8_build_embedder_graph) {
DCHECK(isolate);
isolate_ = isolate;
v8_build_embedder_graph_ = v8_build_embedder_graph;
unified_heap_controller_.reset(new UnifiedHeapController(this));
isolate_->SetEmbedderHeapTracer(unified_heap_controller_.get());
unified_heap_controller_.get()->SetStackStart(WTF::GetStackStart());
if (v8::HeapProfiler* profiler = isolate->GetHeapProfiler()) {
profiler->AddBuildEmbedderGraphCallback(v8_build_embedder_graph, nullptr);
}
}
void ThreadState::DetachFromIsolate() {
FinishIncrementalMarkingIfRunning(
BlinkGC::CollectionType::kMajor, BlinkGC::kHeapPointersOnStack,
BlinkGC::kAtomicMarking, BlinkGC::kEagerSweeping,
BlinkGC::GCReason::kThreadTerminationGC);
if (isolate_) {
isolate_->SetEmbedderHeapTracer(nullptr);
if (v8::HeapProfiler* profiler = isolate_->GetHeapProfiler()) {
profiler->RemoveBuildEmbedderGraphCallback(v8_build_embedder_graph_,
nullptr);
}
}
isolate_ = nullptr;
v8_build_embedder_graph_ = nullptr;
unified_heap_controller_.reset();
}
void ThreadState::RunTerminationGC() {
DCHECK(!IsMainThread());
DCHECK(CheckThread());
FinishIncrementalMarkingIfRunning(BlinkGC::CollectionType::kMajor,
BlinkGC::kNoHeapPointersOnStack,
BlinkGC::kIncrementalAndConcurrentMarking,
BlinkGC::kConcurrentAndLazySweeping,
BlinkGC::GCReason::kThreadTerminationGC);
// Finish sweeping.
CompleteSweep();
// The constant specifies how many rounds of GCs should at most be needed to
// clean up the heap. If we crash below this means that there's finalizers
// adding more objects and roots than the GC is able to clean up.
constexpr size_t kMaxTerminationGCsForHeapCleanup = 20;
size_t i = 0;
do {
CHECK_LT(i++, kMaxTerminationGCsForHeapCleanup);
// Remove strong cross-thread roots.
ProcessHeap::GetCrossThreadPersistentRegion()
.PrepareForThreadStateTermination(this);
// Remove regular roots.
GetPersistentRegion()->PrepareForThreadStateTermination(this);
CHECK_EQ(0, GetPersistentRegion()->NodesInUse());
CollectGarbage(BlinkGC::CollectionType::kMajor,
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping,
BlinkGC::GCReason::kThreadTerminationGC);
} while (GetPersistentRegion()->NodesInUse() != 0);
// All of pre-finalizers should be consumed.
CHECK(ordered_pre_finalizers_.empty());
CHECK_EQ(GetGCState(), kNoGCScheduled);
Heap().RemoveAllPages();
}
NO_SANITIZE_ADDRESS
void ThreadState::VisitAsanFakeStackForPointer(MarkingVisitor* visitor,
Address ptr,
Address* start_of_stack,
Address* end_of_stack) {
#if defined(ADDRESS_SANITIZER)
Address* fake_frame_start = nullptr;
Address* fake_frame_end = nullptr;
Address* maybe_fake_frame = reinterpret_cast<Address*>(ptr);
Address* real_frame_for_fake_frame = reinterpret_cast<Address*>(
__asan_addr_is_in_fake_stack(asan_fake_stack_, maybe_fake_frame,
reinterpret_cast<void**>(&fake_frame_start),
reinterpret_cast<void**>(&fake_frame_end)));
if (real_frame_for_fake_frame) {
// This is a fake frame from the asan fake stack.
if (real_frame_for_fake_frame > end_of_stack &&
start_of_stack > real_frame_for_fake_frame) {
// The real stack address for the asan fake frame is
// within the stack range that we need to scan so we need
// to visit the values in the fake frame.
for (Address* p = fake_frame_start; p < fake_frame_end; ++p)
heap_->CheckAndMarkPointer(visitor, *p);
}
}
#endif // ADDRESS_SANITIZER
}
// Stack scanning may overrun the bounds of local objects and/or race with
// other threads that use this stack.
NO_SANITIZE_ADDRESS
NO_SANITIZE_HWADDRESS
NO_SANITIZE_THREAD
void ThreadState::VisitStackImpl(MarkingVisitor* visitor,
Address* start_of_stack,
Address* end_of_stack) {
DCHECK_EQ(current_gc_data_.stack_state, BlinkGC::kHeapPointersOnStack);
// Ensure that current is aligned by address size otherwise the loop below
// will read past start address.
Address* current = reinterpret_cast<Address*>(
reinterpret_cast<intptr_t>(end_of_stack) & ~(sizeof(Address) - 1));
for (; current < start_of_stack; ++current) {
Address ptr = *current;
#if defined(MEMORY_SANITIZER)
// |ptr| may be uninitialized by design. Mark it as initialized to keep
// MSan from complaining.
// Note: it may be tempting to get rid of |ptr| and simply use |current|
// here, but that would be incorrect. We intentionally use a local
// variable because we don't want to unpoison the original stack.
__msan_unpoison(&ptr, sizeof(ptr));
#endif
heap_->CheckAndMarkPointer(visitor, ptr);
VisitAsanFakeStackForPointer(visitor, ptr, start_of_stack, end_of_stack);
}
}
void ThreadState::VisitStack(MarkingVisitor* visitor, Address* end_of_stack) {
VisitStackImpl(visitor, start_of_stack_, end_of_stack);
}
void ThreadState::VisitUnsafeStack(MarkingVisitor* visitor) {
#if HAS_FEATURE(safe_stack)
VisitStackImpl(visitor,
static_cast<Address*>(__builtin___get_unsafe_stack_top()),
static_cast<Address*>(__builtin___get_unsafe_stack_ptr()));
#endif // HAS_FEATURE(safe_stack)
}
void ThreadState::VisitPersistents(Visitor* visitor) {
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kVisitPersistentRoots);
{
ThreadHeapStatsCollector::Scope inner_stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kVisitCrossThreadPersistents);
ProcessHeap::CrossThreadPersistentMutex().AssertAcquired();
ProcessHeap::GetCrossThreadPersistentRegion().TraceNodes(visitor);
}
{
ThreadHeapStatsCollector::Scope inner_stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitPersistents);
persistent_region_->TraceNodes(visitor);
}
}
void ThreadState::VisitRememberedSets(MarkingVisitor* visitor) {
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitRememberedSets);
Heap().VisitRememberedSets(visitor);
}
void ThreadState::VisitWeakPersistents(Visitor* visitor) {
ProcessHeap::GetCrossThreadWeakPersistentRegion().TraceNodes(visitor);
weak_persistent_region_->TraceNodes(visitor);
}
void ThreadState::ScheduleForcedGCForTesting() {
DCHECK(CheckThread());
CompleteSweep();
SetGCState(kForcedGCForTestingScheduled);
}
void ThreadState::ScheduleGCIfNeeded() {
VLOG(2) << "[state:" << this << "] ScheduleGCIfNeeded";
DCHECK(CheckThread());
// Allocation is allowed during sweeping, but those allocations should not
// trigger nested GCs.
if (IsGCForbidden() || SweepForbidden())
return;
// This method should not call out to V8 during unified heap garbage
// collections. Specifically, reporting memory to V8 may trigger a marking
// step which is not allowed during construction of an object. The reason is
// that a parent object's constructor is potentially being invoked which may
// have already published the object. In that case the object may be colored
// black in a v8 marking step which invalidates the assumption that write
// barriers may be avoided when constructing an object as it is white.
if (IsUnifiedGCMarkingInProgress())
return;
if (GetGCState() == kNoGCScheduled &&
base::FeatureList::IsEnabled(
blink::features::kBlinkHeapIncrementalMarkingStress)) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleGCIfNeeded: Scheduled incremental marking for testing";
StartIncrementalMarking(BlinkGC::GCReason::kForcedGCForTesting);
return;
}
}
ThreadState* ThreadState::FromObject(const void* object) {
DCHECK(object);
BasePage* page = PageFromObject(object);
DCHECK(page);
DCHECK(page->Arena());
return page->Arena()->GetThreadState();
}
void ThreadState::PerformIdleLazySweep(base::TimeTicks deadline) {
DCHECK(CheckThread());
// If we are not in a sweeping phase, there is nothing to do here.
if (!IsSweepingInProgress())
return;
// This check is here to prevent performIdleLazySweep() from being called
// recursively. I'm not sure if it can happen but it would be safer to have
// the check just in case.
if (SweepForbidden())
return;
if (!AdvanceLazySweep(deadline)) {
ScheduleIdleLazySweep();
}
}
bool ThreadState::AdvanceLazySweep(base::TimeTicks deadline) {
DCHECK(CheckThread());
DCHECK(IsSweepingInProgress());
DCHECK(!SweepForbidden());
RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(
GetIsolate(), RuntimeCallStats::CounterId::kPerformIdleLazySweep);
bool sweep_completed = false;
{
AtomicPauseScope atomic_pause_scope(this);
ScriptForbiddenScope script_forbidden_scope;
SweepForbiddenScope scope(this);
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kLazySweepInIdle,
"idleDeltaInSeconds", (deadline - base::TimeTicks::Now()).InSecondsF());
sweep_completed = Heap().AdvanceLazySweep(deadline);
// We couldn't finish the sweeping within the deadline.
// We request another idle task for the remaining sweeping.
if (sweep_completed) {
SynchronizeAndFinishConcurrentSweeping();
}
}
if (sweep_completed) {
NotifySweepDone();
}
return sweep_completed;
}
void ThreadState::PerformConcurrentSweep(base::JobDelegate* job) {
VLOG(2) << "[state:" << this << "] [threadid:" << CurrentThread() << "] "
<< "ConcurrentSweep";
ThreadHeapStatsCollector::EnabledConcurrentScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kConcurrentSweepingStep);
if (Heap().AdvanceConcurrentSweep(job))
has_unswept_pages_.store(false, std::memory_order_relaxed);
}
void ThreadState::StartIncrementalMarking(BlinkGC::GCReason reason) {
DCHECK(CheckThread());
// Schedule an incremental GC only when no GC is scheduled. Otherwise, already
// scheduled GCs should be prioritized.
if (GetGCState() != kNoGCScheduled) {
return;
}
CompleteSweep();
reason_for_scheduled_gc_ = reason;
SetGCState(kIncrementalGCScheduled);
incremental_marking_scheduler_->Start(reason);
}
void ThreadState::ScheduleIdleLazySweep() {
ThreadScheduler::Current()->PostIdleTask(
FROM_HERE,
WTF::Bind(&ThreadState::PerformIdleLazySweep, WTF::Unretained(this)));
}
void ThreadState::ScheduleConcurrentAndLazySweep() {
ScheduleIdleLazySweep();
if (!base::FeatureList::IsEnabled(
blink::features::kBlinkHeapConcurrentSweeping)) {
return;
}
has_unswept_pages_ = true;
sweeper_handle_ = base::PostJob(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
ConvertToBaseRepeatingCallback(
WTF::CrossThreadBindRepeating(&ThreadState::PerformConcurrentSweep,
WTF::CrossThreadUnretained(this))),
ConvertToBaseRepeatingCallback(WTF::CrossThreadBindRepeating(
[](ThreadState* state, size_t /*worker_count*/) -> size_t {
return state->has_unswept_pages_.load(std::memory_order_relaxed)
? 1
: 0;
},
WTF::CrossThreadUnretained(this))));
}
namespace {
#define UNEXPECTED_GCSTATE(s) \
case ThreadState::s: \
LOG(FATAL) << "Unexpected transition while in GCState " #s; \
return
void UnexpectedGCState(ThreadState::GCState gc_state) {
switch (gc_state) {
UNEXPECTED_GCSTATE(kNoGCScheduled);
UNEXPECTED_GCSTATE(kForcedGCForTestingScheduled);
UNEXPECTED_GCSTATE(kIncrementalMarkingStepPaused);
UNEXPECTED_GCSTATE(kIncrementalMarkingStepScheduled);
UNEXPECTED_GCSTATE(kIncrementalMarkingFinalizeScheduled);
UNEXPECTED_GCSTATE(kIncrementalGCScheduled);
}
}
#undef UNEXPECTED_GCSTATE
} // namespace
#define VERIFY_STATE_TRANSITION(condition) \
if (UNLIKELY(!(condition))) \
UnexpectedGCState(gc_state_)
void ThreadState::SetGCState(GCState gc_state) {
switch (gc_state) {
case kNoGCScheduled:
DCHECK(CheckThread());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled ||
gc_state_ == kForcedGCForTestingScheduled ||
gc_state_ == kIncrementalMarkingStepPaused ||
gc_state_ == kIncrementalMarkingStepScheduled ||
gc_state_ ==
kIncrementalMarkingFinalizeScheduled ||
gc_state_ == kIncrementalGCScheduled);
break;
case kIncrementalMarkingStepScheduled:
DCHECK(CheckThread());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled ||
gc_state_ == kIncrementalMarkingStepScheduled ||
gc_state_ == kIncrementalGCScheduled ||
gc_state_ == kIncrementalMarkingStepPaused);
break;
case kIncrementalMarkingFinalizeScheduled:
DCHECK(CheckThread());
VERIFY_STATE_TRANSITION(gc_state_ == kIncrementalMarkingStepScheduled);
break;
case kForcedGCForTestingScheduled:
DCHECK(CheckThread());
DCHECK(!IsSweepingInProgress());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled ||
gc_state_ == kIncrementalMarkingStepPaused ||
gc_state_ == kIncrementalMarkingStepScheduled ||
gc_state_ ==
kIncrementalMarkingFinalizeScheduled ||
gc_state_ == kForcedGCForTestingScheduled ||
gc_state_ == kIncrementalGCScheduled);
break;
case kIncrementalGCScheduled:
DCHECK(CheckThread());
DCHECK(!IsMarkingInProgress());
DCHECK(!IsSweepingInProgress());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled);
break;
case kIncrementalMarkingStepPaused:
DCHECK(CheckThread());
DCHECK(IsMarkingInProgress());
DCHECK(!IsSweepingInProgress());
VERIFY_STATE_TRANSITION(gc_state_ == kIncrementalMarkingStepScheduled);
break;
default:
NOTREACHED();
}
gc_state_ = gc_state;
}
#undef VERIFY_STATE_TRANSITION
void ThreadState::SetGCPhase(GCPhase gc_phase) {
switch (gc_phase) {
case GCPhase::kNone:
DCHECK_EQ(gc_phase_, GCPhase::kSweeping);
break;
case GCPhase::kMarking:
DCHECK_EQ(gc_phase_, GCPhase::kNone);
break;
case GCPhase::kSweeping:
DCHECK_EQ(gc_phase_, GCPhase::kMarking);
break;
}
gc_phase_ = gc_phase;
}
void ThreadState::RunScheduledGC(BlinkGC::StackState stack_state) {
DCHECK(CheckThread());
if (stack_state != BlinkGC::kNoHeapPointersOnStack)
return;
// If a safe point is entered while initiating a GC, we clearly do
// not want to do another as part of that -- the safe point is only
// entered after checking if a scheduled GC ought to run first.
// Prevent that from happening by marking GCs as forbidden while
// one is initiated and later running.
if (IsGCForbidden())
return;
switch (GetGCState()) {
case kForcedGCForTestingScheduled:
forced_scheduled_gc_for_testing_ = true;
CollectAllGarbageForTesting();
forced_scheduled_gc_for_testing_ = false;
break;
default:
break;
}
}
void ThreadState::AtomicPauseMarkPrologue(
BlinkGC::CollectionType collection_type,
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::GCReason reason) {
ThreadHeapStatsCollector::EnabledScope mark_prologue_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPauseMarkPrologue);
EnterAtomicPause();
EnterNoAllocationScope();
EnterGCForbiddenScope();
if (HeapPointersOnStackForced()) {
stack_state = BlinkGC::kHeapPointersOnStack;
}
if (IsMarkingInProgress()) {
// Incremental marking is already in progress. Only update the state
// that is necessary to update.
SetGCState(kNoGCScheduled);
if (base::FeatureList::IsEnabled(
blink::features::kBlinkHeapConcurrentMarking)) {
// Stop concurrent markers and wait synchronously until they have all
// returned.
marker_handle_.Cancel();
}
#if DCHECK_IS_ON()
MarkingWorklist* marking_worklist = Heap().GetMarkingWorklist();
WriteBarrierWorklist* write_barrier_worklist =
Heap().GetWriteBarrierWorklist();
for (int concurrent_task = WorklistTaskId::ConcurrentThreadBase;
concurrent_task < MarkingWorklist::kNumTasks; ++concurrent_task) {
DCHECK(marking_worklist->IsLocalEmpty(concurrent_task));
DCHECK(write_barrier_worklist->IsLocalEmpty(concurrent_task));
}
#endif // DCHECK_IS_ON()
// Compaction needs to be canceled when incremental marking ends with a
// conservative GC.
if (stack_state == BlinkGC::kHeapPointersOnStack)
Heap().Compaction()->Cancel();
DisableIncrementalMarkingBarrier();
current_gc_data_.reason = reason;
current_gc_data_.stack_state = stack_state;
Heap().stats_collector()->UpdateReason(reason);
} else {
DCHECK(!Heap().Compaction()->IsCompacting());
MarkPhasePrologue(collection_type, stack_state, marking_type, reason);
}
if (stack_state == BlinkGC::kNoHeapPointersOnStack) {
Heap().FlushNotFullyConstructedObjects();
}
DCHECK(InAtomicMarkingPause());
Heap().MakeConsistentForGC();
// AtomicPauseMarkPrologue is the common entry point for marking. The
// requirement is to lock from roots marking to weakness processing which is
// why the lock is taken at the end of the prologue.
static_cast<MutexBase&>(ProcessHeap::CrossThreadPersistentMutex()).lock();
}
void ThreadState::AtomicPauseEpilogue() {
if (!IsSweepingInProgress()) {
// Sweeping was finished during the atomic pause. Update statistics needs to
// run outside of the top-most stats scope.
PostSweep();
}
}
void ThreadState::CompleteSweep() {
DCHECK(CheckThread());
// If we are not in a sweeping phase, there is nothing to do here.
if (!IsSweepingInProgress())
return;
// CompleteSweep() can be called recursively if finalizers can allocate
// memory and the allocation triggers completeSweep(). This check prevents
// the sweeping from being executed recursively.
if (SweepForbidden())
return;
{
// CompleteSweep may be called during regular mutator execution, from a
// task, or from the atomic pause in which the atomic scope has already been
// opened.
const bool was_in_atomic_pause = in_atomic_pause();
if (!was_in_atomic_pause)
EnterAtomicPause();
ScriptForbiddenScope script_forbidden;
SweepForbiddenScope scope(this);
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kCompleteSweep);
// Boost priority of sweeping job to complete ASAP and avoid taking time on
// the main thread.
if (sweeper_handle_)
sweeper_handle_.UpdatePriority(base::TaskPriority::USER_BLOCKING);
Heap().CompleteSweep();
SynchronizeAndFinishConcurrentSweeping();
if (!was_in_atomic_pause)
LeaveAtomicPause();
}
NotifySweepDone();
}
void ThreadState::SynchronizeAndFinishConcurrentSweeping() {
DCHECK(CheckThread());
DCHECK(IsSweepingInProgress());
DCHECK(SweepForbidden());
// Wait for concurrent sweepers.
if (sweeper_handle_)
sweeper_handle_.Cancel();
// Concurrent sweepers may perform some work at the last stage (e.g.
// sweeping the last page and preparing finalizers).
Heap().InvokeFinalizersOnSweptPages();
}
namespace {
// Update trace counters with statistics from the current and previous garbage
// collection cycle. We allow emitting current values here since these values
// can be useful for inspecting traces.
void UpdateTraceCounters(const ThreadHeapStatsCollector& stats_collector) {
bool gc_tracing_enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
&gc_tracing_enabled);
if (!gc_tracing_enabled)
return;
// Previous garbage collection cycle values.
const ThreadHeapStatsCollector::Event& event = stats_collector.previous();
const int collection_rate_percent =
static_cast<int>(100 * (1.0 - event.live_object_rate));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.CollectionRate", collection_rate_percent);
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.MarkedObjectSizeAtLastCompleteSweepKB",
CappedSizeInKB(event.marked_bytes));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.ObjectSizeAtLastGCKB",
CappedSizeInKB(event.object_size_in_bytes_before_sweeping));
TRACE_COUNTER1(
TRACE_DISABLED_BY_DEFAULT("blink_gc"), "BlinkGC.AllocatedSpaceAtLastGCKB",
CappedSizeInKB(event.allocated_space_in_bytes_before_sweeping));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.PartitionAllocSizeAtLastGCKB",
CappedSizeInKB(event.partition_alloc_bytes_before_sweeping));
// Current values.
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.AllocatedSpaceKB",
CappedSizeInKB(stats_collector.allocated_space_bytes()));
size_t allocated_bytes_since_prev_gc =
stats_collector.allocated_bytes_since_prev_gc() > 0
? static_cast<size_t>(stats_collector.allocated_bytes_since_prev_gc())
: 0;
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.AllocatedObjectSizeSincePreviousGCKB",
CappedSizeInKB(allocated_bytes_since_prev_gc));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"PartitionAlloc.TotalSizeOfCommittedPagesKB",
CappedSizeInKB(WTF::Partitions::TotalSizeOfCommittedPages()));
}
// Update histograms with statistics from the previous garbage collection cycle.
// Anything that is part of a histogram should have a well-defined lifetime wrt.
// to a garbage collection cycle.
void UpdateHistograms(const ThreadHeapStatsCollector::Event& event) {
UMA_HISTOGRAM_ENUMERATION("BlinkGC.GCReason", event.reason);
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForAtomicPhase", event.atomic_pause_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForAtomicPhaseMarking",
event.atomic_marking_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForGCCycle", event.gc_cycle_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForMarkingRoots",
event.roots_marking_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForIncrementalMarking",
event.incremental_marking_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForMarking.Foreground",
event.foreground_marking_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForMarking.Background",
event.background_marking_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForMarking", event.marking_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForNestedInV8", event.gc_nested_in_v8);
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForSweepingForeground",
event.foreground_sweeping_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForSweepingBackground",
event.background_sweeping_time());
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForSweepingSum", event.sweeping_time());
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForCompleteSweep",
event.scope_data[ThreadHeapStatsCollector::kCompleteSweep]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForInvokingPreFinalizers",
event.scope_data[ThreadHeapStatsCollector::kInvokePreFinalizers]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForHeapCompaction",
event.scope_data[ThreadHeapStatsCollector::kAtomicPauseCompaction]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForGlobalWeakProcessing",
event.scope_data[ThreadHeapStatsCollector::kMarkWeakProcessing]);
base::TimeDelta marking_duration = event.foreground_marking_time();
constexpr size_t kMinMarkedBytesForReportingThroughput = 1024 * 1024;
if (base::TimeTicks::IsHighResolution() &&
(event.marked_bytes > kMinMarkedBytesForReportingThroughput) &&
!marking_duration.is_zero()) {
DCHECK_GT(marking_duration.InMillisecondsF(), 0.0);
const int main_thread_marking_throughput_mb_per_s = static_cast<int>(
static_cast<double>(event.marked_bytes) /
marking_duration.InMillisecondsF() * 1000 / 1024 / 1024);
UMA_HISTOGRAM_COUNTS_100000("BlinkGC.MainThreadMarkingThroughput",
main_thread_marking_throughput_mb_per_s);
}
DEFINE_STATIC_LOCAL(
CustomCountHistogram, object_size_freed_by_heap_compaction,
("BlinkGC.ObjectSizeFreedByHeapCompaction", 1, 4 * 1024 * 1024, 50));
if (event.compaction_recorded_events) {
object_size_freed_by_heap_compaction.Count(
CappedSizeInKB(event.compaction_freed_bytes));
}
DEFINE_STATIC_LOCAL(CustomCountHistogram, object_size_before_gc_histogram,
("BlinkGC.ObjectSizeBeforeGC", 1, 4 * 1024 * 1024, 50));
object_size_before_gc_histogram.Count(
CappedSizeInKB(event.object_size_in_bytes_before_sweeping));
DEFINE_STATIC_LOCAL(CustomCountHistogram, object_size_after_gc_histogram,
("BlinkGC.ObjectSizeAfterGC", 1, 4 * 1024 * 1024, 50));
object_size_after_gc_histogram.Count(CappedSizeInKB(event.marked_bytes));
const int collection_rate_percent =
static_cast<int>(100 * (1.0 - event.live_object_rate));
DEFINE_STATIC_LOCAL(CustomCountHistogram, collection_rate_histogram,
("BlinkGC.CollectionRate", 1, 100, 20));
collection_rate_histogram.Count(collection_rate_percent);
// Per GCReason metrics.
switch (event.reason) {
#define COUNT_BY_GC_REASON(reason) \
case BlinkGC::GCReason::k##reason: { \
UMA_HISTOGRAM_TIMES("BlinkGC.AtomicPhaseMarking_" #reason, \
event.atomic_marking_time()); \
DEFINE_STATIC_LOCAL(CustomCountHistogram, \
collection_rate_reason_histogram, \
("BlinkGC.CollectionRate_" #reason, 1, 100, 20)); \
collection_rate_reason_histogram.Count(collection_rate_percent); \
break; \
}
COUNT_BY_GC_REASON(ForcedGCForTesting)
COUNT_BY_GC_REASON(ThreadTerminationGC)
COUNT_BY_GC_REASON(UnifiedHeapGC)
COUNT_BY_GC_REASON(UnifiedHeapForMemoryReductionGC)
COUNT_BY_GC_REASON(UnifiedHeapForcedForTestingGC)
#undef COUNT_BY_GC_REASON
}
}
} // namespace
void ThreadState::NotifySweepDone() {
DCHECK(CheckThread());
SetGCPhase(GCPhase::kNone);
if (!in_atomic_pause()) {
PostSweep();
}
ThreadState::StatisticsCollector(this).Verify();
}
void ThreadState::PostSweep() {
DCHECK(!in_atomic_pause());
DCHECK(!IsSweepingInProgress());
Heap().stats_collector()->NotifySweepingCompleted();
if (IsMainThread())
UpdateHistograms(Heap().stats_collector()->previous());
// Emit trace counters for all threads.
UpdateTraceCounters(*Heap().stats_collector());
}
void ThreadState::SafePoint(BlinkGC::StackState stack_state) {
DCHECK(CheckThread());
RunScheduledGC(stack_state);
}
using PushAllRegistersCallback = void (*)(ThreadState*, intptr_t*);
extern "C" void PushAllRegisters(ThreadState*, PushAllRegistersCallback);
// static
void ThreadState::VisitStackAfterPushingRegisters(ThreadState* state,
intptr_t* end_of_stack) {
state->VisitStack(static_cast<MarkingVisitor*>(state->CurrentVisitor()),
reinterpret_cast<Address*>(end_of_stack));
}
void ThreadState::PushRegistersAndVisitStack() {
DCHECK(CheckThread());
DCHECK(IsGCForbidden());
DCHECK_EQ(current_gc_data_.stack_state, BlinkGC::kHeapPointersOnStack);
// Visit registers, native stack, and asan fake stack.
PushAllRegisters(this, ThreadState::VisitStackAfterPushingRegisters);
// For builds that use safe stack, also visit the unsafe stack.
VisitUnsafeStack(static_cast<MarkingVisitor*>(CurrentVisitor()));
}
void ThreadState::InvokePreFinalizers() {
DCHECK(CheckThread());
DCHECK(!SweepForbidden());
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kInvokePreFinalizers);
SweepForbiddenScope sweep_forbidden(this);
// Pre finalizers are forbidden from allocating objects
NoAllocationScope no_allocation_scope(this);
// Call the prefinalizers in the opposite order to their registration.
//
// Deque does not support modification during iteration, so
// copy items first.
//
// The prefinalizer callback wrapper returns |true| when its associated
// object is unreachable garbage and the prefinalizer callback has run.
// The registered prefinalizer entry must then be removed and deleted.
LivenessBroker broker = internal::LivenessBrokerFactory::Create();
Deque<PreFinalizer> remaining_ordered_pre_finalizers;
for (auto rit = ordered_pre_finalizers_.rbegin();
rit != ordered_pre_finalizers_.rend(); ++rit) {
const PreFinalizer& pre_finalizer = *rit;
if (!(pre_finalizer.second)(broker, pre_finalizer.first))
remaining_ordered_pre_finalizers.push_front(pre_finalizer);
}
ordered_pre_finalizers_ = std::move(remaining_ordered_pre_finalizers);
}
// static
AtomicEntryFlag ThreadState::incremental_marking_flag_;
void ThreadState::EnableIncrementalMarkingBarrier() {
CHECK(!IsIncrementalMarking());
incremental_marking_flag_.Enter();
SetIncrementalMarking(true);
}
void ThreadState::DisableIncrementalMarkingBarrier() {
CHECK(IsIncrementalMarking());
incremental_marking_flag_.Exit();
SetIncrementalMarking(false);
}
void ThreadState::IncrementalMarkingStartForTesting() {
// kIncrementalGCScheduled state requires sweeping to not be in progress.
CompleteSweep();
SetGCState(kIncrementalGCScheduled);
IncrementalMarkingStart(BlinkGC::GCReason::kForcedGCForTesting);
}
void ThreadState::IncrementalMarkingStart(BlinkGC::GCReason reason) {
DCHECK(!IsGCForbidden());
DCHECK_EQ(kIncrementalGCScheduled, GetGCState());
VLOG(2) << "[state:" << this << "] "
<< "IncrementalMarking: Start";
DCHECK(!IsMarkingInProgress());
// Sweeping is performed in driver functions.
DCHECK(!IsSweepingInProgress());
Heap().stats_collector()->NotifyMarkingStarted(
BlinkGC::CollectionType::kMajor, reason, IsForcedGC(reason));
{
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingStartMarking, "reason",
BlinkGC::ToString(reason));
AtomicPauseScope atomic_pause_scope(this);
ScriptForbiddenScope script_forbidden_scope;
MarkPhasePrologue(BlinkGC::CollectionType::kMajor,
BlinkGC::kNoHeapPointersOnStack,
BlinkGC::kIncrementalAndConcurrentMarking, reason);
{
MutexLocker persistent_lock(ProcessHeap::CrossThreadPersistentMutex());
MarkPhaseVisitRoots();
}
DCHECK(Heap().GetV8ReferencesWorklist()->IsGlobalEmpty());
EnableIncrementalMarkingBarrier();
if (base::FeatureList::IsEnabled(
blink::features::kBlinkHeapConcurrentMarking)) {
current_gc_data_.visitor->FlushMarkingWorklists();
static_assert(
MarkingWorklist::kNumTasks == WriteBarrierWorklist::kNumTasks,
"Marking worklist and write-barrier worklist should be the "
"same size");
last_concurrently_marked_bytes_ = 0;
last_concurrently_marked_bytes_update_ = base::TimeTicks::Now();
concurrent_marking_priority_increased_ = false;
ScheduleConcurrentMarking();
}
SetGCState(kIncrementalMarkingStepScheduled);
DCHECK(IsMarkingInProgress());
}
}
void ThreadState::IncrementalMarkingStep(BlinkGC::StackState stack_state) {
DCHECK(IsMarkingInProgress());
DCHECK_EQ(kIncrementalMarkingStepScheduled, GetGCState());
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingStep);
VLOG(2) << "[state:" << this << "] "
<< "IncrementalMarking: Step "
<< "Reason: " << BlinkGC::ToString(current_gc_data_.reason);
AtomicPauseScope atomic_pause_scope(this);
ScriptForbiddenScope script_forbidden_scope;
if (stack_state == BlinkGC::kNoHeapPointersOnStack) {
Heap().FlushNotFullyConstructedObjects();
}
bool complete;
if (skip_incremental_marking_for_testing_) {
complete = true;
skip_incremental_marking_for_testing_ = false;
} else {
complete = MarkPhaseAdvanceMarking(
marking_scheduling_->GetNextIncrementalStepDurationForTask(
Heap().stats_collector()->object_size_in_bytes()),
EphemeronProcessing::kPartialProcessing);
}
if (base::FeatureList::IsEnabled(
blink::features::kBlinkHeapConcurrentMarking)) {
complete = ConcurrentMarkingStep() && complete;
}
if (complete) {
if (IsUnifiedGCMarkingInProgress()) {
// If there are no more objects to mark for unified garbage collections
// just bail out of helping incrementally using tasks. V8 will drive
// further marking if new objects are discovered. Otherwise, just process
// the rest in the atomic pause.
DCHECK(IsUnifiedGCMarkingInProgress());
SetGCState(kIncrementalMarkingStepPaused);
} else {
SetGCState(kIncrementalMarkingFinalizeScheduled);
}
} else {
SetGCState(kIncrementalMarkingStepScheduled);
}
DCHECK(IsMarkingInProgress());
}
bool ThreadState::ConcurrentMarkingStep() {
current_gc_data_.visitor->FlushMarkingWorklists();
if (Heap().HasWorkForConcurrentMarking()) {
// Notifies the scheduler that max concurrency might have increased.
// This will adjust the number of markers if necessary.
marker_handle_.NotifyConcurrencyIncrease();
if (!concurrent_marking_priority_increased_) {
// If concurrent tasks aren't executed, it might delay GC finalization.
// As long as GC is active so is the write barrier, which incurs a
// performance cost. Marking is estimated to take overall
// |MarkingSchedulingOracle::kEstimatedMarkingTimeMs| (500ms). If
// concurrent marking tasks have not reported any progress (i.e. the
// concurrently marked bytes count as not changed) in over
// |kMarkingScheduleRatioBeforeConcurrentPriorityIncrease| (50%) of
// that expected duration, we increase the concurrent task priority
// for the duration of the current GC. This is meant to prevent the
// GC from exceeding it's expected end time.
size_t current_concurrently_marked_bytes_ =
marking_scheduling_->GetConcurrentlyMarkedBytes();
if (current_concurrently_marked_bytes_ >
last_concurrently_marked_bytes_) {
last_concurrently_marked_bytes_ = current_concurrently_marked_bytes_;
last_concurrently_marked_bytes_update_ = base::TimeTicks::Now();
} else if ((base::TimeTicks::Now() -
last_concurrently_marked_bytes_update_)
.InMilliseconds() >
kMarkingScheduleRatioBeforeConcurrentPriorityIncrease *
MarkingSchedulingOracle::kEstimatedMarkingTimeMs) {
marker_handle_.UpdatePriority(base::TaskPriority::USER_BLOCKING);
concurrent_marking_priority_increased_ = true;
}
}
return false;
}
return !marker_handle_.IsActive();
}
void ThreadState::IncrementalMarkingFinalize() {
DCHECK(IsMarkingInProgress());
DCHECK(!IsUnifiedGCMarkingInProgress());
DCHECK_EQ(kIncrementalMarkingFinalizeScheduled, GetGCState());
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingFinalize);
VLOG(2) << "[state:" << this << "] "
<< "IncrementalMarking: Finalize "
<< "Reason: " << BlinkGC::ToString(current_gc_data_.reason);
// Call into the regular bottleneck instead of the internal version to get
// UMA accounting and allow follow up GCs if necessary.
DCHECK_EQ(BlinkGC::kIncrementalAndConcurrentMarking,
current_gc_data_.marking_type);
CollectGarbage(current_gc_data_.collection_type,
BlinkGC::kNoHeapPointersOnStack, current_gc_data_.marking_type,
BlinkGC::kConcurrentAndLazySweeping, current_gc_data_.reason);
}
bool ThreadState::FinishIncrementalMarkingIfRunning(
BlinkGC::CollectionType collection_type,
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
if (IsMarkingInProgress()) {
// TODO(mlippautz): Consider improving this mechanism as it will pull in
// finalization of V8 upon Oilpan GCs during a unified GC. Alternative
// include either breaking up the GCs or avoiding the call in first place.
if (IsUnifiedGCMarkingInProgress()) {
unified_heap_controller()->FinalizeTracing();
} else {
RunAtomicPause(collection_type, stack_state, marking_type, sweeping_type,
reason);
}
return true;
}
return false;
}
void ThreadState::RestartIncrementalMarkingIfPaused() {
if (GetGCState() != ThreadState::kIncrementalMarkingStepPaused)
return;
SetGCState(ThreadState::kIncrementalMarkingStepScheduled);
incremental_marking_scheduler_->Restart();
}
void ThreadState::CollectGarbage(BlinkGC::CollectionType collection_type,
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
// Nested garbage collection invocations are not supported.
CHECK(!IsGCForbidden());
// Garbage collection during sweeping is not supported. This can happen when
// finalizers trigger garbage collections.
if (SweepForbidden())
return;
base::TimeTicks start_total_collect_garbage_time = base::TimeTicks::Now();
RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(
GetIsolate(), RuntimeCallStats::CounterId::kCollectGarbage);
const bool was_incremental_marking = FinishIncrementalMarkingIfRunning(
collection_type, stack_state, marking_type, sweeping_type, reason);
// We don't want floating garbage for the specific garbage collection types
// mentioned below. In this case we will follow up with a regular full
// garbage collection.
const bool should_do_full_gc =
!was_incremental_marking ||
reason == BlinkGC::GCReason::kForcedGCForTesting ||
reason == BlinkGC::GCReason::kThreadTerminationGC;
if (should_do_full_gc) {
CompleteSweep();
SetGCState(kNoGCScheduled);
Heap().stats_collector()->NotifyMarkingStarted(collection_type, reason,
IsForcedGC(reason));
RunAtomicPause(collection_type, stack_state, marking_type, sweeping_type,
reason);
}
const base::TimeDelta total_collect_garbage_time =
base::TimeTicks::Now() - start_total_collect_garbage_time;
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForTotalCollectGarbage",
total_collect_garbage_time);
#define COUNT_BY_GC_REASON(reason) \
case BlinkGC::GCReason::k##reason: { \
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForTotalCollectGarbage_" #reason, \
total_collect_garbage_time); \
break; \
}
switch (reason) {
COUNT_BY_GC_REASON(ForcedGCForTesting)
COUNT_BY_GC_REASON(ThreadTerminationGC)
COUNT_BY_GC_REASON(UnifiedHeapGC)
COUNT_BY_GC_REASON(UnifiedHeapForMemoryReductionGC)
COUNT_BY_GC_REASON(UnifiedHeapForcedForTestingGC)
}
#undef COUNT_BY_GC_REASON
VLOG(1) << "[state:" << this << "]"
<< " CollectGarbage: time: " << std::setprecision(2)
<< total_collect_garbage_time.InMillisecondsF() << "ms"
<< " stack: " << BlinkGC::ToString(stack_state)
<< " marking: " << BlinkGC::ToString(marking_type)
<< " sweeping: " << BlinkGC::ToString(sweeping_type)
<< " reason: " << BlinkGC::ToString(reason);
}
void ThreadState::AtomicPauseMarkRoots(BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::GCReason reason) {
ThreadHeapStatsCollector::EnabledScope advance_tracing_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPauseMarkRoots);
MarkPhaseVisitRoots();
MarkPhaseVisitNotFullyConstructedObjects();
}
void ThreadState::AtomicPauseMarkTransitiveClosure() {
ThreadHeapStatsCollector::EnabledScope advance_tracing_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPauseMarkTransitiveClosure);
// base::TimeTicks::Now() + base::TimeDelta::Max() == base::TimeTicks::Max()
CHECK(MarkPhaseAdvanceMarking(base::TimeDelta::Max(),
EphemeronProcessing::kFullProcessing));
}
void ThreadState::AtomicPauseMarkEpilogue(BlinkGC::MarkingType marking_type) {
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPauseMarkEpilogue);
MarkPhaseEpilogue(marking_type);
LeaveGCForbiddenScope();
LeaveNoAllocationScope();
LeaveAtomicPause();
static_cast<MutexBase&>(ProcessHeap::CrossThreadPersistentMutex()).unlock();
}
void ThreadState::AtomicPauseSweepAndCompact(
BlinkGC::CollectionType collection_type,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type) {
ThreadHeapStatsCollector::EnabledScope stats(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPauseSweepAndCompact);
AtomicPauseScope atomic_pause_scope(this);
ScriptForbiddenScope script_forbidden_scope;
DCHECK(InAtomicMarkingPause());
DCHECK(CheckThread());
Heap().PrepareForSweep(collection_type);
// We have to set the GCPhase to Sweeping before calling pre-finalizers
// to disallow a GC during the pre-finalizers.
SetGCPhase(GCPhase::kSweeping);
InvokePreFinalizers();
if (collection_type == BlinkGC::CollectionType::kMajor) {
// Slots filtering requires liveness information which is only present
// before sweeping any arena.
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPauseCompaction);
Heap().Compaction()->FilterNonLiveSlots();
}
// Last point where all mark bits are present.
VerifyMarking(marking_type);
if (collection_type == BlinkGC::CollectionType::kMajor) {
// Any sweep compaction must happen after pre-finalizers, as it will
// finalize dead objects in compactable arenas (e.g., backing stores
// for container objects.)
//
// As per-contract for prefinalizers, those finalizable objects must
// still be accessible when the prefinalizer runs, hence we cannot
// schedule compaction until those have run.
SweepForbiddenScope scope(this);
NoAllocationScope no_allocation_scope(this);
Heap().Compact();
Heap().DestroyCompactionWorklists();
}
#if defined(ADDRESS_SANITIZER)
PoisonUnmarkedObjects();
#endif // ADDRESS_SANITIZER
DCHECK(IsSweepingInProgress());
if (sweeping_type == BlinkGC::kEagerSweeping) {
// Eager sweeping should happen only in testing.
CompleteSweep();
} else {
DCHECK(sweeping_type == BlinkGC::kConcurrentAndLazySweeping);
// The default behavior is concurrent and lazy sweeping.
ScheduleConcurrentAndLazySweep();
}
}
#if defined(ADDRESS_SANITIZER)
namespace {
// Visitor unpoisoning all handles. Unpoisoning is required when dead objects
// are poisoned until they are later on processed.
//
// The raceful operations are:
// a. Running destructor that clears the handle.
// b. Running a stand-alone V8 GC (e.g. Scavenger) that clears the handle.
//
// Both operations run on the main thread and not concurrent.
class UnpoisonHandlesVisitor final
: public v8::PersistentHandleVisitor,
public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor {
public:
explicit UnpoisonHandlesVisitor(ThreadHeap* heap) : heap_(heap) {}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) final {
VisitSlot(value, sizeof(v8::Persistent<v8::Value>));
}
void VisitTracedGlobalHandle(const v8::TracedGlobal<v8::Value>&) final {
CHECK(false) << "Blink does not use v8::TracedGlobal.";
}
void VisitTracedReference(const v8::TracedReference<v8::Value>& value) final {
// TODO(mlippautz): Avoid const_cast after changing the API to allow
// modificaton of the handle.
VisitSlot(&const_cast<v8::TracedReference<v8::Value>&>(value),
sizeof(v8::TracedReference<v8::Value>));
}
private:
void VisitSlot(void* address, size_t size) {
// Filter slots not on the heap.
if (!heap_->LookupPageForAddress(reinterpret_cast<Address>(address)))
return;
HeapObjectHeader* header = HeapObjectHeader::FromInnerAddress(address);
if (!header->IsMarked()) {
DCHECK(ASAN_REGION_IS_POISONED(address, size));
ASAN_UNPOISON_MEMORY_REGION(address, size);
}
}
ThreadHeap* const heap_;
};
} // namespace
void ThreadState::PoisonUnmarkedObjects() {
{
// This lock must be held because other threads may access cross-thread
// persistents and should not observe them in a poisoned state.
MutexLocker lock(ProcessHeap::CrossThreadPersistentMutex());
Heap().PoisonUnmarkedObjects();
// CrossThreadPersistents in unmarked objects may be accessed from other
// threads (e.g. in CrossThreadPersistentRegion::ShouldTracePersistent) and
// that would be fine.
ProcessHeap::GetCrossThreadPersistentRegion()
.UnpoisonCrossThreadPersistents();
ProcessHeap::GetCrossThreadWeakPersistentRegion()
.UnpoisonCrossThreadPersistents();
}
// Similarly, unmarked object may contain handles to V8 that may be accessed
// (cleared) until the destructors are run.
if (GetIsolate()) {
UnpoisonHandlesVisitor visitor(&Heap());
GetIsolate()->VisitHandlesWithClassIds(&visitor);
unified_heap_controller()->IterateTracedGlobalHandles(&visitor);
}
}
#endif // ADDRESS_SANITIZER
void ThreadState::RunAtomicPause(BlinkGC::CollectionType collection_type,
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
// Legacy scope that is used to add stand-alone Oilpan GCs to DevTools
// timeline.
TRACE_EVENT1("blink_gc,devtools.timeline", "BlinkGC.AtomicPhase", "forced",
IsForcedGC(reason));
AtomicPauseMarkPrologue(collection_type, stack_state, marking_type, reason);
AtomicPauseMarkRoots(stack_state, marking_type, reason);
AtomicPauseMarkTransitiveClosure();
AtomicPauseMarkEpilogue(marking_type);
AtomicPauseSweepAndCompact(collection_type, marking_type, sweeping_type);
AtomicPauseEpilogue();
}
namespace {
MarkingVisitor::MarkingMode GetMarkingMode(bool should_compact) {
return (should_compact) ? MarkingVisitor::kGlobalMarkingWithCompaction
: MarkingVisitor::kGlobalMarking;
}
} // namespace
void ThreadState::MarkPhasePrologue(BlinkGC::CollectionType collection_type,
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::GCReason reason) {
SetGCPhase(GCPhase::kMarking);
ThreadState::StatisticsCollector(this).Verify();
const bool compaction_enabled =
Heap().Compaction()->ShouldCompact(stack_state, marking_type, reason);
Heap().SetupWorklists(compaction_enabled);
if (compaction_enabled) {
Heap().Compaction()->Initialize(this);
}
#if BUILDFLAG(BLINK_HEAP_YOUNG_GENERATION)
if (collection_type == BlinkGC::CollectionType::kMajor) {
// Unmark heap before doing major collection cycle.
Heap().Unmark();
}
#endif
current_gc_data_.reason = reason;
current_gc_data_.collection_type = collection_type;
current_gc_data_.visitor =
IsUnifiedGCMarkingInProgress()
? std::make_unique<UnifiedHeapMarkingVisitor>(
this, GetMarkingMode(compaction_enabled), GetIsolate())
: std::make_unique<MarkingVisitor>(
this, GetMarkingMode(compaction_enabled));
current_gc_data_.stack_state = stack_state;
current_gc_data_.marking_type = marking_type;
marking_scheduling_ = std::make_unique<MarkingSchedulingOracle>();
}
void ThreadState::MarkPhaseVisitRoots() {
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitRoots);
Visitor* visitor = current_gc_data_.visitor.get();
VisitPersistents(visitor);
if (current_gc_data_.stack_state == BlinkGC::kHeapPointersOnStack) {
ThreadHeapStatsCollector::Scope stack_stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitStackRoots);
PushRegistersAndVisitStack();
}
// Visit remembered sets (card tables) for minor collections.
if (current_gc_data_.collection_type == BlinkGC::CollectionType::kMinor) {
VisitRememberedSets(static_cast<MarkingVisitor*>(visitor));
}
}
bool ThreadState::MarkPhaseAdvanceMarkingBasedOnSchedule(
base::TimeDelta max_deadline,
EphemeronProcessing ephemeron_processing) {
return MarkPhaseAdvanceMarking(
std::min(max_deadline,
marking_scheduling_->GetNextIncrementalStepDurationForTask(
Heap().stats_collector()->object_size_in_bytes())),
ephemeron_processing);
}
bool ThreadState::MarkPhaseAdvanceMarking(
base::TimeDelta deadline,
EphemeronProcessing ephemeron_processing) {
MarkingVisitor* visitor = current_gc_data_.visitor.get();
ThreadHeapStatsCollector::EnabledScope deadline_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingWithDeadline, "deadline_ms",
deadline.InMillisecondsF());
const bool finished = Heap().AdvanceMarking(
reinterpret_cast<MarkingVisitor*>(visitor),
base::TimeTicks::Now() + deadline, ephemeron_processing);
// visitor->marked_bytes() can also include bytes marked during roots
// visitation which is not counted in worklist_processing_time_foreground.
// Since the size of the roots is usually small relative to the size of
// the object graph, this is fine.
marking_scheduling_->UpdateIncrementalMarkingStats(
visitor->marked_bytes(),
Heap().stats_collector()->worklist_processing_time_foreground(),
Heap().stats_collector()->flushing_v8_references_time());
return finished;
}
bool ThreadState::IsVerifyMarkingEnabled() const {
bool should_verify_marking = base::FeatureList::IsEnabled(
blink::features::kBlinkHeapIncrementalMarkingStress);
#if BUILDFLAG(BLINK_HEAP_VERIFICATION)
should_verify_marking = (disable_heap_verification_scope_ == 0);
#endif // BLINK_HEAP_VERIFICATION
return should_verify_marking;
}
void ThreadState::MarkPhaseVisitNotFullyConstructedObjects() {
Heap().MarkNotFullyConstructedObjects(
reinterpret_cast<MarkingVisitor*>(current_gc_data_.visitor.get()));
}
void ThreadState::MarkPhaseEpilogue(BlinkGC::MarkingType marking_type) {
MarkingVisitor* visitor = current_gc_data_.visitor.get();
{
ProcessHeap::CrossThreadPersistentMutex().AssertAcquired();
VisitWeakPersistents(visitor);
Heap().WeakProcessing(visitor);
}
Heap().DestroyMarkingWorklists(current_gc_data_.stack_state);
incremental_marking_scheduler_->Cancel();
current_gc_data_.visitor->FlushCompactionWorklists();
current_gc_data_.visitor.reset();
Heap().stats_collector()->NotifyMarkingCompleted(
marking_scheduling_->GetOverallMarkedBytes());
marking_scheduling_.reset();
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, total_object_space_histogram,
("BlinkGC.TotalObjectSpace", 0, 4 * 1024 * 1024, 50));
total_object_space_histogram.Count(
CappedSizeInKB(ProcessHeap::TotalAllocatedObjectSize()));
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, total_allocated_space_histogram,
("BlinkGC.TotalAllocatedSpace", 0, 4 * 1024 * 1024, 50));
total_allocated_space_histogram.Count(
CappedSizeInKB(ProcessHeap::TotalAllocatedSpace()));
}
void ThreadState::VerifyMarking(BlinkGC::MarkingType marking_type) {
if (IsVerifyMarkingEnabled())
Heap().VerifyMarking();
}
void ThreadState::CollectGarbageForTesting(
BlinkGC::CollectionType collection_type,
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
CollectGarbage(collection_type, stack_state, marking_type, sweeping_type,
reason);
}
void ThreadState::CollectAllGarbageForTesting(BlinkGC::StackState stack_state) {
// We need to run multiple GCs to collect a chain of persistent handles.
size_t previous_live_objects = 0;
for (int i = 0; i < 5; ++i) {
if (isolate_) {
unified_heap_controller()->GarbageCollectionForTesting(
stack_state == BlinkGC::kNoHeapPointersOnStack
? v8::EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers
: v8::EmbedderHeapTracer::EmbedderStackState::
kMayContainHeapPointers);
} else {
CollectGarbage(BlinkGC::CollectionType::kMajor, stack_state,
BlinkGC::kAtomicMarking, BlinkGC::kEagerSweeping,
BlinkGC::GCReason::kForcedGCForTesting);
}
const size_t live_objects =
Heap().stats_collector()->previous().marked_bytes;
if (live_objects == previous_live_objects)
break;
previous_live_objects = live_objects;
}
}
void ThreadState::EnableCompactionForNextGCForTesting() {
Heap().Compaction()->EnableCompactionForNextGCForTesting();
}
void ThreadState::ScheduleConcurrentMarking() {
DCHECK(base::FeatureList::IsEnabled(
blink::features::kBlinkHeapConcurrentMarking));
marker_handle_ = base::PostJob(
FROM_HERE, {base::ThreadPool(), base::TaskPriority::USER_VISIBLE},
ConvertToBaseRepeatingCallback(
WTF::CrossThreadBindRepeating(&ThreadState::PerformConcurrentMark,
WTF::CrossThreadUnretained(this))),
ConvertToBaseRepeatingCallback(WTF::CrossThreadBindRepeating(
[](ThreadState* state, size_t active_worker_count) -> size_t {
// We need to account for local segments in addition to
// ConcurrentMarkingGlobalWorkSize().
return std::min<size_t>(
state->Heap().ConcurrentMarkingGlobalWorkSize() +
active_worker_count,
MarkingWorklist::kNumTasks -
WorklistTaskId::ConcurrentThreadBase);
},
WTF::CrossThreadUnretained(this))));
}
void ThreadState::PerformConcurrentMark(base::JobDelegate* job) {
VLOG(2) << "[state:" << this << "] [threadid:" << CurrentThread() << "] "
<< "ConcurrentMark";
ThreadHeapStatsCollector::EnabledConcurrentScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kConcurrentMarkingStep);
if (!Heap().HasWorkForConcurrentMarking())
return;
uint8_t task_id = job->GetTaskId() + 1;
std::unique_ptr<ConcurrentMarkingVisitor> concurrent_visitor =
IsUnifiedGCMarkingInProgress()
? std::make_unique<ConcurrentUnifiedHeapMarkingVisitor>(
this, GetMarkingMode(Heap().Compaction()->IsCompacting()),
GetIsolate(), task_id)
: std::make_unique<ConcurrentMarkingVisitor>(
this, GetMarkingMode(Heap().Compaction()->IsCompacting()),
task_id);
Heap().AdvanceConcurrentMarking(concurrent_visitor.get(), job,
marking_scheduling_.get());
marking_scheduling_->AddConcurrentlyMarkedBytes(
concurrent_visitor->RecentlyMarkedBytes());
concurrent_visitor->FlushWorklists();
}
void ThreadState::NotifyGarbageCollection(v8::GCType type,
v8::GCCallbackFlags flags) {
if (!IsGCForbidden() && (flags & v8::kGCCallbackFlagForced)) {
// Forces a precise GC at the end of the current event loop. This is
// required for testing code that cannot use GC internals but rather has
// to rely on window.gc(). Only schedule additional GCs if the last GC was
// using conservative stack scanning.
if (type == v8::kGCTypeScavenge || RequiresForcedGCForTesting()) {
ScheduleForcedGCForTesting();
}
}
}
void ThreadState::CollectNodeAndCssStatistics(
base::OnceCallback<void(size_t allocated_node_bytes,
size_t allocated_css_bytes)> callback) {
if (IsSweepingInProgress()) {
// Help the sweeper to make progress using short-running delayed tasks.
// We use delayed tasks to give background threads a chance to sweep
// most of the heap.
const base::TimeDelta kStepSizeMs = base::TimeDelta::FromMilliseconds(5);
const base::TimeDelta kTaskDelayMs = base::TimeDelta::FromMilliseconds(10);
if (!AdvanceLazySweep(base::TimeTicks::Now() + kStepSizeMs)) {
ThreadScheduler::Current()->V8TaskRunner()->PostDelayedTask(
FROM_HERE,
WTF::Bind(&ThreadState::CollectNodeAndCssStatistics,
WTF::Unretained(this), std::move(callback)),
kTaskDelayMs);
return;
}
}
size_t allocated_node_bytes =
Heap().Arena(BlinkGC::kNodeArenaIndex)->AllocatedBytes();
size_t allocated_css_bytes =
Heap().Arena(BlinkGC::kCSSValueArenaIndex)->AllocatedBytes();
std::move(callback).Run(allocated_node_bytes, allocated_css_bytes);
}
} // namespace blink