blob: 94a956b339b49dc996aa322626562b7e9a521fa5 [file] [log] [blame]
// Copyright 2018 The Chromium 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 "third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.h"
#include <memory>
#include <sstream>
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/active_script_wrappable_manager.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
#include "third_party/blink/renderer/platform/heap/unified_heap_controller.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/buildflags.h"
#if BUILDFLAG(USE_V8_OILPAN)
namespace blink {
void EmbedderGraphBuilder::BuildEmbedderGraphCallback(v8::Isolate* isolate,
v8::EmbedderGraph* graph,
void*) {}
} // namespace blink
#else // !USE_V8_OILPAN
namespace blink {
namespace {
using Traceable = const void*;
using Graph = v8::EmbedderGraph;
using Detachedness = v8::EmbedderGraph::Node::Detachedness;
// Information about whether a node is attached to the main DOM tree
// or not. It is computed as follows:
// 1) A ExecutionContext with IsContextDestroyed() = true is detached.
// 2) A ExecutionContext with IsContextDestroyed() = false is attached.
// 3) A Node that is not connected to any ExecutionContext is detached.
// 4) A Node that is connected to a detached ExecutionContext is detached.
// 5) A Node that is connected to an attached ExecutionContext is attached.
// 6) A ScriptWrappable that is reachable from an attached Node is
// attached.
// 7) A ScriptWrappable that is reachable from a detached Node is
// detached.
// 8) A ScriptWrappable that is not reachable from any Node is
// considered (conservatively) as attached.
// The unknown state applies to ScriptWrappables during graph
// traversal when we don't have reachability information yet.
Detachedness DetachednessFromWrapper(v8::Isolate* isolate,
uint16_t class_id,
v8::Local<v8::Object> v8_value) {
if (class_id != WrapperTypeInfo::kNodeClassId)
return Detachedness::kUnknown;
Node* node = V8Node::ToImpl(v8_value);
Node* root = V8GCController::OpaqueRootForGC(isolate, node);
if (root->isConnected() && node->GetExecutionContext())
return Detachedness::kAttached;
return Detachedness::kDetached;
}
class EmbedderNode : public Graph::Node {
public:
EmbedderNode(const char* name,
Graph::Node* wrapper,
Detachedness detachedness)
: name_(name), wrapper_(wrapper), detachedness_(detachedness) {}
// Graph::Node overrides.
const char* Name() override { return name_; }
size_t SizeInBytes() override { return 0; }
Graph::Node* WrapperNode() override { return wrapper_; }
Detachedness GetDetachedness() override { return detachedness_; }
void AddEdgeName(std::unique_ptr<char[]> edge_name) {
edge_names_.push_back(std::move(edge_name));
}
private:
const char* name_;
Graph::Node* wrapper_;
const Detachedness detachedness_;
// V8's API uses raw strings for edge names and expect the underlying memory
// to be retained until the end of graph building where strings are copied
// into its internal storage. The following vector retains those edge names
// until a node is freed which is at the end of graph building.
Vector<std::unique_ptr<char[]>> edge_names_;
};
class EmbedderRootNode : public EmbedderNode {
public:
explicit EmbedderRootNode(const char* name)
: EmbedderNode(name, nullptr, Detachedness::kUnknown) {}
// Graph::Node override.
bool IsRootNode() override { return true; }
};
class NodeBuilder final {
USING_FAST_MALLOC(NodeBuilder);
public:
explicit NodeBuilder(Graph* graph) : graph_(graph) {}
Graph::Node* GraphNode(const v8::Local<v8::Value>&);
EmbedderNode* GraphNode(Traceable,
const char* name,
Graph::Node* wrapper,
Detachedness);
bool Contains(Traceable traceable) const {
return graph_nodes_.Contains(traceable);
}
private:
Graph* const graph_;
HashMap<Traceable, EmbedderNode*> graph_nodes_;
};
v8::EmbedderGraph::Node* NodeBuilder::GraphNode(
const v8::Local<v8::Value>& value) {
return graph_->V8Node(value);
}
EmbedderNode* NodeBuilder::GraphNode(Traceable traceable,
const char* name,
v8::EmbedderGraph::Node* wrapper,
Detachedness detachedness) {
auto iter = graph_nodes_.find(traceable);
if (iter != graph_nodes_.end()) {
return iter->value;
}
// Ownership of the new node is transferred to the graph_.
// graph_node_.at(tracable) is valid for all BuildEmbedderGraph execution.
auto* raw_node = new EmbedderNode(name, wrapper, detachedness);
EmbedderNode* node = static_cast<EmbedderNode*>(
graph_->AddNode(std::unique_ptr<Graph::Node>(raw_node)));
graph_nodes_.insert(traceable, node);
return node;
}
// V8EmbedderGraphBuilder is used to build heap snapshots of Blink's managed
// object graph. On a high level, the following operations are performed:
// - Node objects are classified as attached, detached, or unknown.
// - Depending an implicit mode, objects are classified as relevant or internal.
// This classification happens based on NameTrait and the fact that all
// ScriptWrappable objects (those that can have JS properties) are using that
// trait.
// - Not relevant objects are filtered where possible, e.g., sub graphs of
// internals are filtered and not reported.
//
// The algorithm performs a single pass on the graph, starting from V8-to-Blink
// references that identify attached nodes. Each object then starts a recursion
// into its own subgraph to identify and filter subgraphs that only consist of
// internals. Roots, which are potentially Blink only, are transitively
// traversed after handling JavaScript related objects.
class GC_PLUGIN_IGNORE(
"This class is not managed by Oilpan but GC plugin recognizes it as such "
"due to Trace methods.") V8EmbedderGraphBuilder
: public Visitor,
public v8::PersistentHandleVisitor,
public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor {
public:
V8EmbedderGraphBuilder(v8::Isolate*, Graph*, NodeBuilder*);
~V8EmbedderGraphBuilder() override;
void BuildEmbedderGraph();
// v8::PersistentHandleVisitor override.
void VisitPersistentHandle(v8::Persistent<v8::Value>*,
uint16_t class_id) override;
// v8::EmbedderHeapTracer::TracedGlobalHandleVisitor override.
void VisitTracedReference(
const v8::TracedReference<v8::Value>& value) override;
void VisitTracedGlobalHandle(const v8::TracedGlobal<v8::Value>&) override;
// Visitor overrides.
void VisitRoot(const void*, TraceDescriptor, const base::Location&) final;
void Visit(const TraceWrapperV8Reference<v8::Value>&) final;
void Visit(const void*, TraceDescriptor) final;
void VisitEphemeron(const void*, TraceDescriptor) final;
void VisitWeakContainer(const void*,
const void* const*,
TraceDescriptor,
TraceDescriptor,
WeakCallback,
const void*) final;
private:
class ParentScope {
STACK_ALLOCATED();
public:
ParentScope(V8EmbedderGraphBuilder* visitor, Traceable traceable)
: visitor_(visitor), old_parent_(visitor->current_parent_) {
visitor->current_parent_ = traceable;
}
~ParentScope() { visitor_->current_parent_ = old_parent_; }
ParentScope(const ParentScope&) = delete;
ParentScope& operator=(const ParentScope&) = delete;
private:
V8EmbedderGraphBuilder* const visitor_;
Traceable old_parent_;
};
class State final {
USING_FAST_MALLOC(State);
public:
State(Traceable traceable, const char* name, Detachedness detachedness)
: traceable_(traceable), name_(name) {}
explicit State(EmbedderNode* node) : node_(node) {}
bool IsVisited() const { return visited_; }
void MarkVisited() { visited_ = true; }
bool IsPending() const { return pending_; }
void MarkPending() { pending_ = true; }
void UnmarkPending() { pending_ = false; }
bool HasNode() const { return node_; }
EmbedderNode* GetOrCreateNode(NodeBuilder* builder) {
if (!node_) {
DCHECK(name_);
node_ = builder->GraphNode(traceable_, name_, nullptr,
Detachedness::kUnknown);
}
return node_;
}
void AddEdgeName(State* destination, std::string edge_name) {
auto result = named_edges_.insert(destination, std::move(edge_name));
DCHECK(result.is_new_entry);
}
void AddRootEdgeName(State* destination, std::string edge_name) {
// State may represent root groups in which case there may exist multiple
// references to the same |destination|.
named_edges_.insert(destination, std::move(edge_name));
}
std::string EdgeName(State* destination) {
auto it = named_edges_.find(destination);
if (it != named_edges_.end())
return it->value;
return std::string();
}
private:
EmbedderNode* node_ = nullptr;
Traceable traceable_ = nullptr;
const char* name_ = nullptr;
HashMap<State* /*destination*/, std::string> named_edges_;
bool visited_ = false;
bool pending_ = false;
};
// WorklistItemBase is used for different kinds of items that require
// processing the regular worklist.
class WorklistItemBase {
USING_FAST_MALLOC(WorklistItemBase);
public:
explicit WorklistItemBase(State* parent, State* to_process)
: parent_(parent), to_process_(to_process) {}
virtual ~WorklistItemBase() = default;
virtual void Process(V8EmbedderGraphBuilder*) = 0;
State* to_process() const { return to_process_; }
State* parent() const { return parent_; }
private:
State* const parent_;
State* const to_process_;
};
// A VisitationItem processes a given object and visits all its children.
class VisitationItem final : public WorklistItemBase {
public:
VisitationItem(State* parent,
State* to_process,
Traceable traceable,
TraceCallback trace_callback)
: WorklistItemBase(parent, to_process),
traceable_(traceable),
trace_callback_(trace_callback) {}
void Process(V8EmbedderGraphBuilder* builder) final {
// Post-order traversal as parent needs the full information on child.
builder->worklist_.push_back(std::unique_ptr<WorklistItemBase>{
new VisitationDoneItem(parent(), to_process())});
to_process()->MarkPending();
DCHECK(to_process()->IsPending());
DCHECK(to_process()->IsVisited());
ParentScope parent_scope(builder, traceable_);
trace_callback_(builder, const_cast<void*>(traceable_));
}
private:
Traceable traceable_;
TraceCallback trace_callback_;
};
// A VisitationDoneItem unmarks the pending state of an object and creates
// an edge from a parent in case there is one.
class VisitationDoneItem final : public WorklistItemBase {
public:
VisitationDoneItem(State* parent, State* to_process)
: WorklistItemBase(parent, to_process) {}
void Process(V8EmbedderGraphBuilder* builder) final {
if (parent() && to_process()->HasNode()) {
builder->AddEdge(parent(), to_process());
}
to_process()->UnmarkPending();
}
};
class EphemeronItem final {
public:
EphemeronItem(Traceable backing,
Traceable key,
Traceable value,
TraceCallback value_tracing_callback)
: backing_(backing),
key_(key),
value_(value),
value_tracing_callback_(value_tracing_callback) {}
bool Process(V8EmbedderGraphBuilder* builder) {
if (!key_) {
// Don't trace the value if the key is nullptr.
return true;
}
if (!builder->StateExists(key_))
return false;
{
ParentScope scope(builder, key_);
builder->current_ephemeron_backing_ = backing_;
value_tracing_callback_(builder, const_cast<void*>(value_));
builder->current_ephemeron_backing_ = nullptr;
}
return true;
}
private:
Traceable backing_;
Traceable key_;
Traceable value_;
TraceCallback value_tracing_callback_;
};
State* GetOrCreateState(Traceable traceable,
const char* name,
Detachedness detachedness) {
if (!states_.Contains(traceable)) {
states_.insert(traceable, new State(traceable, name, detachedness));
}
return states_.at(traceable);
}
bool StateExists(Traceable traceable) const {
return states_.Contains(traceable);
}
State* GetStateNotNull(Traceable traceable) {
CHECK(states_.Contains(traceable));
return states_.at(traceable);
}
State* EnsureState(Traceable traceable, EmbedderNode* node) {
if (!states_.Contains(traceable)) {
states_.insert(traceable, new State(node));
}
return states_.at(traceable);
}
void EnsureRootState(EmbedderNode* node) {
CHECK(!states_.Contains(node));
states_.insert(node, new State(node));
}
void AddEdge(State*, State*);
void AddEphemeronEdgeName(Traceable backing, State* parent, State* current);
void VisitPersistentHandleInternal(v8::Local<v8::Object>, uint16_t);
void VisitPendingActivities();
void VisitBlinkRoots();
void VisitTransitiveClosure();
// Push a VisitatonItem to the main worklist in case the State has not been
// already visited.
void CreateAndPushVisitationItem(State* parent,
State* to_process,
Traceable traceable,
TraceCallback trace_callback) {
DCHECK(!to_process->IsVisited());
to_process->MarkVisited();
worklist_.push_back(std::unique_ptr<WorklistItemBase>{
new VisitationItem(parent, to_process, traceable, trace_callback)});
}
void PushVisitationItem(std::unique_ptr<VisitationItem> item) {
if (!item->to_process()->IsVisited()) {
item->to_process()->MarkVisited();
worklist_.push_back(std::move(item));
}
}
v8::Isolate* const isolate_;
Graph* const graph_;
NodeBuilder* const node_builder_;
Traceable current_parent_ = nullptr;
Traceable current_ephemeron_backing_ = nullptr;
HashMap<Traceable, State*> states_;
// Worklist that is used to visit transitive closure.
Deque<std::unique_ptr<WorklistItemBase>> worklist_;
// The worklist that collects Ephemeron entries for later processing.
Deque<std::unique_ptr<EphemeronItem>> ephemeron_worklist_;
};
V8EmbedderGraphBuilder::V8EmbedderGraphBuilder(v8::Isolate* isolate,
Graph* graph,
NodeBuilder* node_builder)
: Visitor(ThreadState::Current()),
isolate_(isolate),
graph_(graph),
node_builder_(node_builder) {
CHECK(isolate);
CHECK(graph);
CHECK_EQ(isolate, ThreadState::Current()->GetIsolate());
}
V8EmbedderGraphBuilder::~V8EmbedderGraphBuilder() {
for (const auto& kvp : states_) {
delete kvp.value;
}
}
void V8EmbedderGraphBuilder::BuildEmbedderGraph() {
isolate_->VisitHandlesWithClassIds(this);
v8::EmbedderHeapTracer* const tracer = static_cast<v8::EmbedderHeapTracer*>(
ThreadState::Current()->unified_heap_controller());
tracer->IterateTracedGlobalHandles(this);
VisitBlinkRoots();
VisitPendingActivities();
VisitTransitiveClosure();
DCHECK(worklist_.empty());
// ephemeron_worklist_ might not be empty. We might have an ephemeron whose
// key is alive but was never observed by the snapshot (e.g. objects pointed
// to by the stack). Such entries will remain in the worklist.
//
// TODO(omerkatz): add DCHECK(ephemeron_worklist_.empty()) when heap snapshot
// covers all live objects.
}
void V8EmbedderGraphBuilder::VisitPersistentHandleInternal(
v8::Local<v8::Object> v8_value,
uint16_t class_id) {
const ScriptWrappable* traceable = ToScriptWrappable(v8_value);
if (!traceable)
return;
Graph::Node* wrapper = node_builder_->GraphNode(v8_value);
auto detachedness = DetachednessFromWrapper(isolate_, class_id, v8_value);
EmbedderNode* graph_node = node_builder_->GraphNode(
traceable, traceable->NameInHeapSnapshot(), wrapper, detachedness);
State* const to_process_state = EnsureState(traceable, graph_node);
if (to_process_state->IsVisited()) {
return;
}
const TraceDescriptor& descriptor =
TraceDescriptorFor<ScriptWrappable>(traceable);
CreateAndPushVisitationItem(nullptr, to_process_state, traceable,
descriptor.callback);
}
void V8EmbedderGraphBuilder::VisitTracedReference(
const v8::TracedReference<v8::Value>& value) {
const uint16_t class_id = value.WrapperClassId();
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId)
return;
VisitPersistentHandleInternal(value.As<v8::Object>().Get(isolate_), class_id);
}
void V8EmbedderGraphBuilder::VisitTracedGlobalHandle(
const v8::TracedGlobal<v8::Value>&) {
CHECK(false) << "Blink does not use v8::TracedGlobal.";
}
void V8EmbedderGraphBuilder::VisitPersistentHandle(
v8::Persistent<v8::Value>* value,
uint16_t class_id) {
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId)
return;
v8::Local<v8::Object> v8_value = v8::Local<v8::Object>::New(
isolate_, v8::Persistent<v8::Object>::Cast(*value));
VisitPersistentHandleInternal(v8_value, class_id);
}
void V8EmbedderGraphBuilder::Visit(
const TraceWrapperV8Reference<v8::Value>& traced_wrapper) {
// Add an edge from the current parent to the V8 object.
v8::Local<v8::Value> v8_value = traced_wrapper.NewLocal(isolate_);
if (!v8_value.IsEmpty()) {
State* parent = GetStateNotNull(current_parent_);
graph_->AddEdge(parent->GetOrCreateNode(node_builder_),
node_builder_->GraphNode(v8_value));
}
}
void V8EmbedderGraphBuilder::VisitRoot(const void* object,
TraceDescriptor wrapper_descriptor,
const base::Location& location) {
// Extract edge name if |location| is set.
if (location.has_source_info()) {
const void* traceable = wrapper_descriptor.base_object_payload;
State* const parent = GetStateNotNull(current_parent_);
State* const current = GetOrCreateState(
traceable, HeapObjectHeader::FromPayload(traceable)->Name(),
Detachedness::kUnknown);
parent->AddRootEdgeName(current, location.ToString());
}
Visit(object, wrapper_descriptor);
}
void V8EmbedderGraphBuilder::AddEphemeronEdgeName(Traceable backing,
State* parent,
State* current) {
const GCInfo& backing_info = GCInfo::From(
HeapObjectHeader::FromPayload(current_ephemeron_backing_)->GcInfoIndex());
HeapObjectName backing_name = backing_info.name(current_ephemeron_backing_);
std::stringstream ss;
ss << "part of key -> value pair in ephemeron table";
if (!backing_name.name_is_hidden) {
const std::string backing_name_str(backing_name.value);
const auto kvp_pos = backing_name_str.find("WTF::KeyValuePair");
// Ephemerons are defined through WTF::KeyValuePair.
CHECK_NE(std::string::npos, kvp_pos);
// Extracting the pair TYPE from for WTF::KeyValuePair<TYPE>.
ss << " (<";
size_t current_pos = kvp_pos + sizeof("WTF::KeyValuePair");
CHECK_EQ('<', backing_name_str[current_pos - 1]);
size_t nesting = 0;
while (backing_name_str[current_pos] != '>' || (nesting > 0)) {
if (backing_name_str[current_pos] == '<')
nesting++;
if (backing_name_str[current_pos] == '>')
nesting--;
ss << backing_name_str[current_pos];
current_pos++;
}
ss << ">)";
}
parent->AddEdgeName(current, ss.str());
}
void V8EmbedderGraphBuilder::Visit(const void* object,
TraceDescriptor wrapper_descriptor) {
const void* traceable = wrapper_descriptor.base_object_payload;
const GCInfo& info =
GCInfo::From(HeapObjectHeader::FromPayload(traceable)->GcInfoIndex());
HeapObjectName name = info.name(traceable);
State* const parent = GetStateNotNull(current_parent_);
State* const current =
GetOrCreateState(traceable, name.value, Detachedness::kUnknown);
if (current_ephemeron_backing_) {
// Just records an edge name in case the state gets later on materialized.
AddEphemeronEdgeName(current_ephemeron_backing_, parent, current);
}
if (current->IsPending()) {
if (parent->HasNode()) {
// Backedge in currently processed graph.
AddEdge(parent, current);
}
return;
}
// Immediately materialize the node if it is not hidden.
if (!name.name_is_hidden) {
current->GetOrCreateNode(node_builder_);
}
if (!current->IsVisited()) {
CreateAndPushVisitationItem(parent, current, traceable, info.trace);
} else {
// Edge into an already processed subgraph.
if (current->HasNode()) {
// Create an edge in case the current node has already been visited.
AddEdge(parent, current);
}
}
}
void V8EmbedderGraphBuilder::AddEdge(State* parent, State* current) {
EmbedderNode* parent_node = parent->GetOrCreateNode(node_builder_);
EmbedderNode* current_node = current->GetOrCreateNode(node_builder_);
if (!parent->EdgeName(current).empty()) {
std::string edge_name = parent->EdgeName(current);
// V8's API is based on raw C strings. Allocate and temporarily keep the
// edge name alive from the corresponding node.
const size_t len = edge_name.length();
auto holder = std::make_unique<char[]>(len + 1);
strncpy(holder.get(), edge_name.c_str(), len);
holder[len] = 0;
graph_->AddEdge(parent_node, current_node, holder.get());
parent_node->AddEdgeName(std::move(holder));
return;
}
graph_->AddEdge(parent_node, current_node);
}
void V8EmbedderGraphBuilder::VisitWeakContainer(
const void* object,
const void* const* slot,
TraceDescriptor strong_desc,
TraceDescriptor ephemeron_iteration,
WeakCallback weak_callback,
const void* weak_callback_parameter) {
// Only ephemerons have weak callbacks.
if (ephemeron_iteration.callback) {
// Heap snapshot is always run after a GC so we know there are no dead
// entries in the backing store. Using the weak descriptor here ensures that
// the key is not held alive from the backing store but rather from the
// object. A named edge ensures that we make the fact that value was held
// alive via ephemeron visible.
if (object) {
ParentScope parent(this, ephemeron_iteration.base_object_payload);
ephemeron_iteration.callback(this,
ephemeron_iteration.base_object_payload);
}
}
}
void V8EmbedderGraphBuilder::VisitEphemeron(
const void* key,
TraceDescriptor value_trace_descriptor) {
// During regular visitation of ephemerons, current_parent_ refers to the
// backing store.
ephemeron_worklist_.push_back(std::make_unique<EphemeronItem>(
current_parent_, key, value_trace_descriptor.base_object_payload,
value_trace_descriptor.callback));
}
void V8EmbedderGraphBuilder::VisitPendingActivities() {
// Ownership of the new node is transferred to the graph_.
EmbedderNode* root =
static_cast<EmbedderNode*>(graph_->AddNode(std::unique_ptr<Graph::Node>(
new EmbedderRootNode("Pending activities"))));
EnsureRootState(root);
ParentScope parent(this, root);
V8PerIsolateData::From(isolate_)
->GetActiveScriptWrappableManager()
->IterateActiveScriptWrappables(this);
}
void V8EmbedderGraphBuilder::VisitBlinkRoots() {
{
EmbedderNode* root = static_cast<EmbedderNode*>(graph_->AddNode(
std::unique_ptr<Graph::Node>(new EmbedderRootNode("Blink roots"))));
EnsureRootState(root);
ParentScope parent(this, root);
ThreadState::Current()->GetPersistentRegion()->TraceNodes(this);
}
{
EmbedderNode* root =
static_cast<EmbedderNode*>(graph_->AddNode(std::unique_ptr<Graph::Node>(
new EmbedderRootNode("Blink cross-thread roots"))));
EnsureRootState(root);
ParentScope parent(this, root);
MutexLocker persistent_lock(ProcessHeap::CrossThreadPersistentMutex());
ProcessHeap::GetCrossThreadPersistentRegion().TraceNodes(this);
}
}
void V8EmbedderGraphBuilder::VisitTransitiveClosure() {
// The following loop process the worklist and ephemerons. Since regular
// tracing can record new ephemerons, and tracing an ephemeron can add
// items to the regular worklist, we need to repeatedly process the worklist
// until a fixed point is reached.
// Because snapshots are processed in stages, there may be ephemerons that
// where key's do not have yet a state associated with them which prohibits
// them from being processed. Such ephemerons are stashed for later
// processing.
bool processed_ephemerons;
do {
// Step 1: Go through all items in the worklist using depth-first search.
while (!worklist_.empty()) {
std::unique_ptr<WorklistItemBase> item = std::move(worklist_.back());
worklist_.pop_back();
item->Process(this);
}
// Step 2: Go through ephemeron items.
processed_ephemerons = false;
Deque<std::unique_ptr<EphemeronItem>> unprocessed_ephemerons_;
// Only process an ephemeron item if its key was already observed.
while (!ephemeron_worklist_.empty()) {
std::unique_ptr<EphemeronItem> item =
std::move(ephemeron_worklist_.front());
ephemeron_worklist_.pop_front();
if (!item->Process(this)) {
unprocessed_ephemerons_.push_back(std::move(item));
} else {
processed_ephemerons = true;
}
}
ephemeron_worklist_.Swap(unprocessed_ephemerons_);
} while (!worklist_.empty() || processed_ephemerons);
}
} // namespace
void EmbedderGraphBuilder::BuildEmbedderGraphCallback(v8::Isolate* isolate,
v8::EmbedderGraph* graph,
void*) {
// Synchronize with concurrent sweepers before taking a snapshot.
ThreadState::Current()->CompleteSweep();
NodeBuilder node_builder(graph);
V8EmbedderGraphBuilder builder(isolate, graph, &node_builder);
builder.BuildEmbedderGraph();
}
} // namespace blink
#endif // !USE_V8_OILPAN