| // 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 |