blob: d0f3e987df94ed361dff725e44fc7ee00c445df4 [file] [log] [blame]
/*
* Copyright (C) 2009 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/bindings/core/v8/v8_gc_controller.h"
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include "third_party/blink/public/platform/blame_context.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
#include "third_party/blink/renderer/core/dom/attr.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/html/imports/html_imports_controller.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable_marking_visitor.h"
#include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
#include "third_party/blink/renderer/platform/heap/heap_stats_collector.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
Node* V8GCController::OpaqueRootForGC(v8::Isolate*, Node* node) {
DCHECK(node);
if (node->isConnected())
return &node->GetDocument().MasterDocument();
if (node->IsAttributeNode()) {
Node* owner_element = ToAttr(node)->ownerElement();
if (!owner_element)
return node;
node = owner_element;
}
while (Node* parent = node->ParentOrShadowHostOrTemplateHostNode())
node = parent;
return node;
}
namespace {
bool IsDOMWrapperClassId(uint16_t class_id) {
return class_id == WrapperTypeInfo::kNodeClassId ||
class_id == WrapperTypeInfo::kObjectClassId ||
class_id == WrapperTypeInfo::kCustomWrappableId;
}
class MinorGCUnmodifiedWrapperVisitor : public v8::PersistentHandleVisitor {
public:
explicit MinorGCUnmodifiedWrapperVisitor(v8::Isolate* isolate)
: isolate_(isolate) {}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) override {
if (!IsDOMWrapperClassId(class_id))
return;
if (class_id == WrapperTypeInfo::kCustomWrappableId) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
// MinorGC does not collect objects because it may be expensive to
// update references during minorGC
if (class_id == WrapperTypeInfo::kObjectClassId) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(
isolate_, v8::Persistent<v8::Object>::Cast(*value));
DCHECK(V8DOMWrapper::HasInternalFieldsSet(wrapper));
if (ToWrapperTypeInfo(wrapper)->IsActiveScriptWrappable() &&
ToScriptWrappable(wrapper)->HasPendingActivity()) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
if (class_id == WrapperTypeInfo::kNodeClassId) {
DCHECK(V8Node::hasInstance(wrapper, isolate_));
Node* node = V8Node::ToImpl(wrapper);
if (node->HasEventListeners()) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
// FIXME: Remove the special handling for SVG elements.
// We currently can't collect SVG Elements from minor gc, as we have
// strong references from SVG property tear-offs keeping context SVG
// element alive.
if (node->IsSVGElement()) {
v8::Persistent<v8::Object>::Cast(*value).MarkActive();
return;
}
}
}
private:
v8::Isolate* isolate_;
};
size_t UsedHeapSize(v8::Isolate* isolate) {
v8::HeapStatistics heap_statistics;
isolate->GetHeapStatistics(&heap_statistics);
return heap_statistics.used_heap_size();
}
void VisitWeakHandlesForMinorGC(v8::Isolate* isolate) {
MinorGCUnmodifiedWrapperVisitor visitor(isolate);
isolate->VisitWeakHandles(&visitor);
}
} // namespace
void V8GCController::GcPrologue(v8::Isolate* isolate,
v8::GCType type,
v8::GCCallbackFlags flags) {
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kGcPrologue);
ScriptForbiddenScope::Enter();
// Attribute garbage collection to the all frames instead of a specific
// frame.
if (BlameContext* blame_context =
Platform::Current()->GetTopLevelBlameContext())
blame_context->Enter();
// TODO(haraken): A GC callback is not allowed to re-enter V8. This means
// that it's unsafe to run Oilpan's GC in the GC callback because it may
// run finalizers that call into V8. To avoid the risk, we should post
// a task to schedule the Oilpan's GC.
// (In practice, there is no finalizer that calls into V8 and thus is safe.)
v8::HandleScope scope(isolate);
switch (type) {
case v8::kGCTypeScavenge:
TRACE_EVENT_BEGIN1("devtools.timeline,v8", "MinorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate));
VisitWeakHandlesForMinorGC(isolate);
break;
case v8::kGCTypeMarkSweepCompact:
if (ThreadState::Current())
ThreadState::Current()->WillStartV8GC(BlinkGC::kV8MajorGC);
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate), "type",
"atomic pause");
break;
case v8::kGCTypeIncrementalMarking:
if (ThreadState::Current())
ThreadState::Current()->WillStartV8GC(BlinkGC::kV8MajorGC);
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate), "type",
"incremental marking");
break;
case v8::kGCTypeProcessWeakCallbacks:
TRACE_EVENT_BEGIN2("devtools.timeline,v8", "MajorGC",
"usedHeapSizeBefore", UsedHeapSize(isolate), "type",
"weak processing");
break;
default:
NOTREACHED();
}
}
namespace {
void UpdateCollectedPhantomHandles(v8::Isolate* isolate) {
ThreadHeapStatsCollector* stats_collector =
ThreadState::Current()->Heap().stats_collector();
const size_t count = isolate->NumberOfPhantomHandleResetsSinceLastCall();
stats_collector->DecreaseWrapperCount(count);
stats_collector->IncreaseCollectedWrapperCount(count);
}
void ScheduleFollowupGCs(ThreadState* thread_state,
v8::GCCallbackFlags flags,
bool is_unified) {
DCHECK(!thread_state->IsGCForbidden());
// Schedules followup garbage collections. Such garbage collections may be
// needed when:
// 1. GC is not precise because it has to scan on-stack pointers.
// 2. GC needs to reclaim chains persistent handles.
// v8::kGCCallbackFlagForced is used for testing GCs that need to verify
// that objects indeed died.
if (flags & v8::kGCCallbackFlagForced) {
if (!is_unified) {
thread_state->CollectGarbage(
BlinkGC::kHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping, BlinkGC::GCReason::kForcedGC);
}
// Forces a precise GC at the end of the current event loop.
thread_state->ScheduleFullGC();
}
// In the unified world there is little need to schedule followup garbage
// collections as the current GC already computed the whole transitive
// closure. We ignore chains of persistent handles here. Cleanup of such
// handle chains requires GC loops at the caller side, e.g., see thread
// termination.
if (is_unified)
return;
if ((flags & v8::kGCCallbackFlagCollectAllAvailableGarbage) ||
(flags & v8::kGCCallbackFlagCollectAllExternalMemory)) {
// This single GC is not enough. See the above comment.
thread_state->CollectGarbage(
BlinkGC::kHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping, BlinkGC::GCReason::kForcedGC);
// The conservative GC might have left floating garbage. Schedule
// precise GC to ensure that we collect all available garbage.
thread_state->SchedulePreciseGC();
}
// Schedules a precise GC for the next idle time period.
if (flags & v8::kGCCallbackScheduleIdleGarbageCollection) {
thread_state->ScheduleIdleGC();
}
}
} // namespace
void V8GCController::GcEpilogue(v8::Isolate* isolate,
v8::GCType type,
v8::GCCallbackFlags flags) {
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kGcEpilogue);
UpdateCollectedPhantomHandles(isolate);
switch (type) {
case v8::kGCTypeScavenge:
TRACE_EVENT_END1("devtools.timeline,v8", "MinorGC", "usedHeapSizeAfter",
UsedHeapSize(isolate));
// Scavenger might have dropped nodes.
if (ThreadState::Current()) {
ThreadState::Current()->ScheduleV8FollowupGCIfNeeded(
BlinkGC::kV8MinorGC);
}
break;
case v8::kGCTypeMarkSweepCompact:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter",
UsedHeapSize(isolate));
if (ThreadState::Current())
ThreadState::Current()->ScheduleV8FollowupGCIfNeeded(
BlinkGC::kV8MajorGC);
break;
case v8::kGCTypeIncrementalMarking:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter",
UsedHeapSize(isolate));
break;
case v8::kGCTypeProcessWeakCallbacks:
TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter",
UsedHeapSize(isolate));
break;
default:
NOTREACHED();
}
ScriptForbiddenScope::Exit();
if (BlameContext* blame_context =
Platform::Current()->GetTopLevelBlameContext())
blame_context->Leave();
ThreadState* current_thread_state = ThreadState::Current();
if (current_thread_state && !current_thread_state->IsGCForbidden()) {
ScheduleFollowupGCs(
ThreadState::Current(), flags,
RuntimeEnabledFeatures::HeapUnifiedGarbageCollectionEnabled());
}
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data",
InspectorUpdateCountersEvent::Data());
}
void V8GCController::CollectAllGarbageForTesting(
v8::Isolate* isolate,
v8::EmbedderHeapTracer::EmbedderStackState stack_state) {
constexpr unsigned kNumberOfGCs = 5;
if (stack_state != v8::EmbedderHeapTracer::EmbedderStackState::kUnknown) {
V8PerIsolateData* data = V8PerIsolateData::From(isolate);
v8::EmbedderHeapTracer* tracer =
RuntimeEnabledFeatures::HeapUnifiedGarbageCollectionEnabled()
? static_cast<v8::EmbedderHeapTracer*>(
data->GetUnifiedHeapController())
: static_cast<v8::EmbedderHeapTracer*>(
data->GetScriptWrappableMarkingVisitor());
// Passing a stack state is only supported when either wrapper tracing or
// unified heap is enabled.
CHECK(tracer);
for (unsigned i = 0; i < kNumberOfGCs; i++)
tracer->GarbageCollectionForTesting(stack_state);
return;
}
for (unsigned i = 0; i < kNumberOfGCs; i++)
isolate->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
}
namespace {
// Traces all DOM persistent handles using the provided visitor.
class DOMWrapperTracer final : public v8::PersistentHandleVisitor {
public:
explicit DOMWrapperTracer(Visitor* visitor) : visitor_(visitor) {
DCHECK(visitor_);
}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) final {
if (!IsDOMWrapperClassId(class_id))
return;
WrapperTypeInfo* wrapper_type_info = const_cast<WrapperTypeInfo*>(
ToWrapperTypeInfo(v8::Persistent<v8::Object>::Cast(*value)));
// WrapperTypeInfo pointer may have been cleared before termination GCs on
// worker threads.
if (!wrapper_type_info)
return;
wrapper_type_info->Trace(
visitor_, ToUntypedWrappable(v8::Persistent<v8::Object>::Cast(*value)));
}
private:
Visitor* const visitor_;
};
// Purges all DOM persistent handles.
class DOMWrapperPurger final : public v8::PersistentHandleVisitor {
public:
explicit DOMWrapperPurger(v8::Isolate* isolate)
: isolate_(isolate), scope_(isolate) {}
void VisitPersistentHandle(v8::Persistent<v8::Value>* value,
uint16_t class_id) final {
if (!IsDOMWrapperClassId(class_id))
return;
// Clear out wrapper type information, essentially disconnecting the Blink
// wrappable from the V8 wrapper. This way, V8 cannot find the C++ object
// anymore.
int indices[] = {kV8DOMWrapperObjectIndex, kV8DOMWrapperTypeIndex};
void* values[] = {nullptr, nullptr};
v8::Local<v8::Object> wrapper = v8::Local<v8::Object>::New(
isolate_, v8::Persistent<v8::Object>::Cast(*value));
wrapper->SetAlignedPointerInInternalFields(base::size(indices), indices,
values);
}
private:
v8::Isolate* isolate_;
v8::HandleScope scope_;
};
} // namespace
void V8GCController::TraceDOMWrappers(v8::Isolate* isolate, Visitor* visitor) {
DOMWrapperTracer tracer(visitor);
isolate->VisitHandlesWithClassIds(&tracer);
}
void V8GCController::ClearDOMWrappers(v8::Isolate* isolate) {
DOMWrapperPurger purger(isolate);
isolate->VisitHandlesWithClassIds(&purger);
}
} // namespace blink