1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/heap/cppgc-js/cpp-snapshot.h"
6 
7 #include <memory>
8 
9 #include "include/cppgc/internal/name-trait.h"
10 #include "include/cppgc/trace-trait.h"
11 #include "include/v8-cppgc.h"
12 #include "include/v8-profiler.h"
13 #include "src/api/api-inl.h"
14 #include "src/base/logging.h"
15 #include "src/execution/isolate.h"
16 #include "src/heap/cppgc-js/cpp-heap.h"
17 #include "src/heap/cppgc/heap-object-header.h"
18 #include "src/heap/cppgc/heap-visitor.h"
19 #include "src/heap/embedder-tracing.h"
20 #include "src/heap/mark-compact.h"
21 #include "src/objects/js-objects.h"
22 #include "src/profiler/heap-profiler.h"
23 
24 namespace v8 {
25 namespace internal {
26 
27 class CppGraphBuilderImpl;
28 class StateStorage;
29 class State;
30 
31 using cppgc::internal::GCInfo;
32 using cppgc::internal::GlobalGCInfoTable;
33 using cppgc::internal::HeapObjectHeader;
34 
35 // Node representing a C++ object on the heap.
36 class EmbedderNode : public v8::EmbedderGraph::Node {
37  public:
EmbedderNode(cppgc::internal::HeapObjectName name, size_t size)38   EmbedderNode(cppgc::internal::HeapObjectName name, size_t size)
39       : name_(name), size_(size) {
40     USE(size_);
41   }
42   ~EmbedderNode() override = default;
43 
44   const char* Name() final { return name_.value; }
45   size_t SizeInBytes() final { return name_.name_was_hidden ? 0 : size_; }
46 
SetWrapperNode(v8::EmbedderGraph::Node* wrapper_node)47   void SetWrapperNode(v8::EmbedderGraph::Node* wrapper_node) {
48     // An embedder node may only be merged with a single wrapper node, as
49     // consumers of the graph may merge a node and its wrapper node.
50     //
51     // TODO(chromium:1218404): Add a DCHECK() to avoid overriding an already
52     // set `wrapper_node_`. This can currently happen with global proxies that
53     // are rewired (and still kept alive) after reloading a page, see
54     // `AddEdge`. We accept overriding the wrapper node in such cases,
55     // leading to a random merged node and separated nodes for all other
56     // proxies.
57     wrapper_node_ = wrapper_node;
58   }
59   Node* WrapperNode() final { return wrapper_node_; }
60 
SetDetachedness(Detachedness detachedness)61   void SetDetachedness(Detachedness detachedness) {
62     detachedness_ = detachedness;
63   }
64   Detachedness GetDetachedness() final { return detachedness_; }
65 
66   // Edge names are passed to V8 but are required to be held alive from the
67   // embedder until the snapshot is compiled.
InternalizeEdgeName(std::string edge_name)68   const char* InternalizeEdgeName(std::string edge_name) {
69     const size_t edge_name_len = edge_name.length();
70     named_edges_.emplace_back(std::make_unique<char[]>(edge_name_len + 1));
71     char* named_edge_str = named_edges_.back().get();
72     snprintf(named_edge_str, edge_name_len + 1, "%s", edge_name.c_str());
73     return named_edge_str;
74   }
75 
76  private:
77   cppgc::internal::HeapObjectName name_;
78   size_t size_;
79   Node* wrapper_node_ = nullptr;
80   Detachedness detachedness_ = Detachedness::kUnknown;
81   std::vector<std::unique_ptr<char[]>> named_edges_;
82 };
83 
84 // Node representing an artificial root group, e.g., set of Persistent handles.
85 class EmbedderRootNode final : public EmbedderNode {
86  public:
EmbedderRootNode(const char* name)87   explicit EmbedderRootNode(const char* name)
88       : EmbedderNode({name, false}, 0) {}
89   ~EmbedderRootNode() final = default;
90 
91   bool IsRootNode() final { return true; }
92 };
93 
94 // Canonical state representing real and artificial (e.g. root) objects.
95 class StateBase {
96  public:
97   // Objects can either be hidden/visible, or depend on some other nodes while
98   // traversing the same SCC.
99   enum class Visibility {
100     kHidden,
101     kDependentVisibility,
102     kVisible,
103   };
104 
StateBase(const void* key, size_t state_count, Visibility visibility, EmbedderNode* node, bool visited)105   StateBase(const void* key, size_t state_count, Visibility visibility,
106             EmbedderNode* node, bool visited)
107       : key_(key),
108         state_count_(state_count),
109         visibility_(visibility),
110         node_(node),
111         visited_(visited) {
112     DCHECK_NE(Visibility::kDependentVisibility, visibility);
113   }
114   virtual ~StateBase() = default;
115 
116   // Visited objects have already been processed or are currently being
117   // processed, see also IsPending() below.
IsVisited() const118   bool IsVisited() const { return visited_; }
119 
120   // Pending objects are currently being processed as part of the same SCC.
IsPending() const121   bool IsPending() const { return pending_; }
122 
IsVisibleNotDependent()123   bool IsVisibleNotDependent() {
124     auto v = GetVisibility();
125     CHECK_NE(Visibility::kDependentVisibility, v);
126     return v == Visibility::kVisible;
127   }
128 
set_node(EmbedderNode* node)129   void set_node(EmbedderNode* node) {
130     CHECK_EQ(Visibility::kVisible, GetVisibility());
131     DCHECK_NULL(node_);
132     node_ = node;
133   }
134 
get_node()135   EmbedderNode* get_node() {
136     CHECK_EQ(Visibility::kVisible, GetVisibility());
137     return node_;
138   }
139 
140  protected:
141   const void* key_;
142   // State count keeps track of node processing order. It is used to create only
143   // dependencies on ancestors in the sub graph which ensures that there will be
144   // no cycles in dependencies.
145   const size_t state_count_;
146 
147   Visibility visibility_;
148   StateBase* visibility_dependency_ = nullptr;
149   EmbedderNode* node_;
150   bool visited_;
151   bool pending_ = false;
152 
GetVisibility()153   Visibility GetVisibility() {
154     FollowDependencies();
155     return visibility_;
156   }
157 
FollowDependencies()158   StateBase* FollowDependencies() {
159     if (visibility_ != Visibility::kDependentVisibility) {
160       CHECK_NULL(visibility_dependency_);
161       return this;
162     }
163     StateBase* current = this;
164     std::vector<StateBase*> dependencies;
165     while (current->visibility_dependency_ &&
166            current->visibility_dependency_ != current) {
167       DCHECK_EQ(Visibility::kDependentVisibility, current->visibility_);
168       dependencies.push_back(current);
169       current = current->visibility_dependency_;
170     }
171     auto new_visibility = Visibility::kDependentVisibility;
172     auto* new_visibility_dependency = current;
173     if (current->visibility_ == Visibility::kVisible) {
174       new_visibility = Visibility::kVisible;
175       new_visibility_dependency = nullptr;
176     } else if (!IsPending()) {
177       DCHECK(IsVisited());
178       // The object was not visible (above case). Having a dependency on itself
179       // or null means no visible object was found.
180       new_visibility = Visibility::kHidden;
181       new_visibility_dependency = nullptr;
182     }
183     current->visibility_ = new_visibility;
184     current->visibility_dependency_ = new_visibility_dependency;
185     for (auto* state : dependencies) {
186       state->visibility_ = new_visibility;
187       state->visibility_dependency_ = new_visibility_dependency;
188     }
189     return current;
190   }
191 
192   friend class State;
193 };
194 
195 class State final : public StateBase {
196  public:
State(const HeapObjectHeader& header, size_t state_count)197   State(const HeapObjectHeader& header, size_t state_count)
198       : StateBase(&header, state_count, Visibility::kHidden, nullptr, false) {}
199   ~State() final = default;
200 
header() const201   const HeapObjectHeader* header() const {
202     return static_cast<const HeapObjectHeader*>(key_);
203   }
204 
MarkVisited()205   void MarkVisited() { visited_ = true; }
206 
MarkPending()207   void MarkPending() { pending_ = true; }
UnmarkPending()208   void UnmarkPending() { pending_ = false; }
209 
MarkVisible()210   void MarkVisible() {
211     visibility_ = Visibility::kVisible;
212     visibility_dependency_ = nullptr;
213   }
214 
MarkDependentVisibility(StateBase* dependency)215   void MarkDependentVisibility(StateBase* dependency) {
216     // Follow and update dependencies as much as possible.
217     dependency = dependency->FollowDependencies();
218     DCHECK(dependency->IsVisited());
219     if (visibility_ == StateBase::Visibility::kVisible) {
220       // Already visible, no dependency needed.
221       DCHECK_NULL(visibility_dependency_);
222       return;
223     }
224     if (dependency->visibility_ == Visibility::kVisible) {
225       // Simple case: Dependency is visible.
226       visibility_ = Visibility::kVisible;
227       visibility_dependency_ = nullptr;
228       return;
229     }
230     if ((visibility_dependency_ &&
231          (visibility_dependency_->state_count_ > dependency->state_count_)) ||
232         (!visibility_dependency_ &&
233          (state_count_ > dependency->state_count_))) {
234       // Only update when new state_count_ < original state_count_. This
235       // ensures that we pick an ancestor as dependency and not a child which
236       // is guaranteed to converge to an answer.
237       //
238       // Dependency is now
239       // a) either pending with unknown visibility (same call chain), or
240       // b) not pending and has defined visibility.
241       //
242       // It's not possible to point to a state that is not pending but has
243       // dependent visibility because dependencies are updated to the top-most
244       // dependency at the beginning of method.
245       if (dependency->IsPending()) {
246         visibility_ = Visibility::kDependentVisibility;
247         visibility_dependency_ = dependency;
248       } else {
249         CHECK_NE(Visibility::kDependentVisibility, dependency->visibility_);
250         if (dependency->visibility_ == Visibility::kVisible) {
251           visibility_ = Visibility::kVisible;
252           visibility_dependency_ = nullptr;
253         }
254       }
255     }
256   }
257 
MarkAsWeakContainer()258   void MarkAsWeakContainer() { is_weak_container_ = true; }
IsWeakContainer() const259   bool IsWeakContainer() const { return is_weak_container_; }
260 
AddEphemeronEdge(const HeapObjectHeader& value)261   void AddEphemeronEdge(const HeapObjectHeader& value) {
262     // This ignores duplicate entries (in different containers) for the same
263     // Key->Value pairs. Only one edge will be emitted in this case.
264     ephemeron_edges_.insert(&value);
265   }
266 
AddEagerEphemeronEdge(const void* value, cppgc::TraceCallback callback)267   void AddEagerEphemeronEdge(const void* value, cppgc::TraceCallback callback) {
268     eager_ephemeron_edges_.insert({value, callback});
269   }
270 
271   template <typename Callback>
ForAllEphemeronEdges(Callback callback)272   void ForAllEphemeronEdges(Callback callback) {
273     for (const HeapObjectHeader* value : ephemeron_edges_) {
274       callback(*value);
275     }
276   }
277 
278   template <typename Callback>
ForAllEagerEphemeronEdges(Callback callback)279   void ForAllEagerEphemeronEdges(Callback callback) {
280     for (const auto& pair : eager_ephemeron_edges_) {
281       callback(pair.first, pair.second);
282     }
283   }
284 
285  private:
286   bool is_weak_container_ = false;
287   // Values that are held alive through ephemerons by this particular key.
288   std::unordered_set<const HeapObjectHeader*> ephemeron_edges_;
289   // Values that are eagerly traced and held alive through ephemerons by this
290   // particular key.
291   std::unordered_map<const void*, cppgc::TraceCallback> eager_ephemeron_edges_;
292 };
293 
294 // Root states are similar to regular states with the difference that they are
295 // always visible.
296 class RootState final : public StateBase {
297  public:
RootState(EmbedderRootNode* node, size_t state_count)298   RootState(EmbedderRootNode* node, size_t state_count)
299       // Root states are always visited, visible, and have a node attached.
300       : StateBase(node, state_count, Visibility::kVisible, node, true) {}
301   ~RootState() final = default;
302 };
303 
304 // Abstraction for storing states. Storage allows for creation and lookup of
305 // different state objects.
306 class StateStorage final {
307  public:
StateExists(const void* key) const308   bool StateExists(const void* key) const {
309     return states_.find(key) != states_.end();
310   }
311 
GetExistingState(const void* key) const312   StateBase& GetExistingState(const void* key) const {
313     CHECK(StateExists(key));
314     return *states_.at(key).get();
315   }
316 
GetExistingState(const HeapObjectHeader& header) const317   State& GetExistingState(const HeapObjectHeader& header) const {
318     return static_cast<State&>(GetExistingState(&header));
319   }
320 
GetOrCreateState(const HeapObjectHeader& header)321   State& GetOrCreateState(const HeapObjectHeader& header) {
322     if (!StateExists(&header)) {
323       auto it = states_.insert(std::make_pair(
324           &header, std::make_unique<State>(header, ++state_count_)));
325       DCHECK(it.second);
326       USE(it);
327     }
328     return GetExistingState(header);
329   }
330 
CreateRootState(EmbedderRootNode* root_node)331   RootState& CreateRootState(EmbedderRootNode* root_node) {
332     CHECK(!StateExists(root_node));
333     auto it = states_.insert(std::make_pair(
334         root_node, std::make_unique<RootState>(root_node, ++state_count_)));
335     DCHECK(it.second);
336     USE(it);
337     return static_cast<RootState&>(*it.first->second.get());
338   }
339 
340   template <typename Callback>
ForAllVisibleStates(Callback callback)341   void ForAllVisibleStates(Callback callback) {
342     for (auto& state : states_) {
343       if (state.second->IsVisibleNotDependent()) {
344         callback(state.second.get());
345       }
346     }
347   }
348 
349  private:
350   std::unordered_map<const void*, std::unique_ptr<StateBase>> states_;
351   size_t state_count_ = 0;
352 };
353 
ExtractEmbedderDataBackref(Isolate* isolate, v8::Local<v8::Value> v8_value)354 void* ExtractEmbedderDataBackref(Isolate* isolate,
355                                  v8::Local<v8::Value> v8_value) {
356   // See LocalEmbedderHeapTracer::VerboseWrapperTypeInfo for details on how
357   // wrapper objects are set up.
358   if (!v8_value->IsObject()) return nullptr;
359 
360   Handle<Object> v8_object = Utils::OpenHandle(*v8_value);
361   if (!v8_object->IsJSObject() ||
362       !JSObject::cast(*v8_object).MayHaveEmbedderFields())
363     return nullptr;
364 
365   JSObject js_object = JSObject::cast(*v8_object);
366   return LocalEmbedderHeapTracer::VerboseWrapperInfo(
367              isolate->heap()->local_embedder_heap_tracer()->ExtractWrapperInfo(
368                  isolate, js_object))
369       .instance();
370 }
371 
372 // The following implements a snapshotting algorithm for C++ objects that also
373 // filters strongly-connected components (SCCs) of only "hidden" objects that
374 // are not (transitively) referencing any non-hidden objects.
375 //
376 // C++ objects come in two versions.
377 // a. Named objects that have been assigned a name through NameProvider.
378 // b. Unnamed objects, that are potentially hidden if the build configuration
379 //    requires Oilpan to hide such names. Hidden objects have their name
380 //    set to NameProvider::kHiddenName.
381 //
382 // The main challenge for the algorithm is to avoid blowing up the final object
383 // graph with hidden nodes that do not carry information. For that reason, the
384 // algorithm filters SCCs of only hidden objects, e.g.:
385 //   ... -> (object) -> (object) -> (hidden) -> (hidden)
386 // In this case the (hidden) objects are filtered from the graph. The trickiest
387 // part is maintaining visibility state for objects referencing other objects
388 // that are currently being processed.
389 //
390 // Main algorithm idea (two passes):
391 // 1. First pass marks all non-hidden objects and those that transitively reach
392 //    non-hidden objects as visible. Details:
393 //    - Iterate over all objects.
394 //    - If object is non-hidden mark it as visible and also mark parent as
395 //      visible if needed.
396 //    - If object is hidden, traverse children as DFS to find non-hidden
397 //      objects. Post-order process the objects and mark those objects as
398 //      visible that have child nodes that are visible themselves.
399 //    - Maintain an epoch counter (StateStorage::state_count_) to allow
400 //      deferring the visibility decision to other objects in the same SCC. This
401 //      is similar to the "lowlink" value in Tarjan's algorithm for SCC.
402 //    - After the first pass it is guaranteed that all deferred visibility
403 //      decisions can be resolved.
404 // 2. Second pass adds nodes and edges for all visible objects.
405 //    - Upon first checking the visibility state of an object, all deferred
406 //      visibility states are resolved.
407 //
408 // For practical reasons, the recursion is transformed into an iteration. We do
409 // do not use plain Tarjan's algorithm to avoid another pass over all nodes to
410 // create SCCs.
411 class CppGraphBuilderImpl final {
412  public:
CppGraphBuilderImpl(CppHeap& cpp_heap, v8::EmbedderGraph& graph)413   CppGraphBuilderImpl(CppHeap& cpp_heap, v8::EmbedderGraph& graph)
414       : cpp_heap_(cpp_heap), graph_(graph) {}
415 
416   void Run();
417 
418   void VisitForVisibility(State* parent, const HeapObjectHeader&);
419   void VisitForVisibility(State& parent, const TracedReferenceBase&);
420   void VisitEphemeronForVisibility(const HeapObjectHeader& key,
421                                    const HeapObjectHeader& value);
422   void VisitEphemeronWithNonGarbageCollectedValueForVisibility(
423       const HeapObjectHeader& key, const void* value,
424       cppgc::TraceDescriptor value_desc);
425   void VisitWeakContainerForVisibility(const HeapObjectHeader&);
426   void VisitRootForGraphBuilding(RootState&, const HeapObjectHeader&,
427                                  const cppgc::SourceLocation&);
428   void ProcessPendingObjects();
429 
AddRootNode(const char* name)430   EmbedderRootNode* AddRootNode(const char* name) {
431     return static_cast<EmbedderRootNode*>(graph_.AddNode(
432         std::unique_ptr<v8::EmbedderGraph::Node>{new EmbedderRootNode(name)}));
433   }
434 
AddNode(const HeapObjectHeader& header)435   EmbedderNode* AddNode(const HeapObjectHeader& header) {
436     return static_cast<EmbedderNode*>(
437         graph_.AddNode(std::unique_ptr<v8::EmbedderGraph::Node>{
438             new EmbedderNode(header.GetName(), header.AllocatedSize())}));
439   }
440 
AddEdge(State& parent, const HeapObjectHeader& header, const std::string& edge_name)441   void AddEdge(State& parent, const HeapObjectHeader& header,
442                const std::string& edge_name) {
443     DCHECK(parent.IsVisibleNotDependent());
444     auto& current = states_.GetExistingState(header);
445     if (!current.IsVisibleNotDependent()) return;
446 
447     // Both states are visible. Create nodes in case this is the first edge
448     // created for any of them.
449     if (!parent.get_node()) {
450       parent.set_node(AddNode(*parent.header()));
451     }
452     if (!current.get_node()) {
453       current.set_node(AddNode(header));
454     }
455 
456     if (!edge_name.empty()) {
457       graph_.AddEdge(parent.get_node(), current.get_node(),
458                      parent.get_node()->InternalizeEdgeName(edge_name));
459     } else {
460       graph_.AddEdge(parent.get_node(), current.get_node());
461     }
462   }
463 
AddEdge(State& parent, const TracedReferenceBase& ref, const std::string& edge_name)464   void AddEdge(State& parent, const TracedReferenceBase& ref,
465                const std::string& edge_name) {
466     DCHECK(parent.IsVisibleNotDependent());
467     v8::Local<v8::Value> v8_value =
468         ref.Get(reinterpret_cast<v8::Isolate*>(cpp_heap_.isolate()));
469     if (!v8_value.IsEmpty()) {
470       if (!parent.get_node()) {
471         parent.set_node(AddNode(*parent.header()));
472       }
473       auto* v8_node = graph_.V8Node(v8_value);
474       if (!edge_name.empty()) {
475         graph_.AddEdge(parent.get_node(), v8_node,
476                        parent.get_node()->InternalizeEdgeName(edge_name));
477       } else {
478         graph_.AddEdge(parent.get_node(), v8_node);
479       }
480 
481       // References that have a class id set may have their internal fields
482       // pointing back to the object. Set up a wrapper node for the graph so
483       // that the snapshot generator  can merge the nodes appropriately.
484       // Even with a set class id, do not set up a wrapper node when the edge
485       // has a specific name.
486       if (!ref.WrapperClassId() || !edge_name.empty()) return;
487 
488       void* back_reference_object = ExtractEmbedderDataBackref(
489           reinterpret_cast<v8::internal::Isolate*>(cpp_heap_.isolate()),
490           v8_value);
491       if (back_reference_object) {
492         auto& back_header = HeapObjectHeader::FromObject(back_reference_object);
493         auto& back_state = states_.GetExistingState(back_header);
494 
495         // Generally the back reference will point to `parent.header()`. In the
496         // case of global proxy set up the backreference will point to a
497         // different object, which may not have a node at t his point. Merge the
498         // nodes nevertheless as Window objects need to be able to query their
499         // detachedness state.
500         //
501         // TODO(chromium:1218404): See bug description on how to fix this
502         // inconsistency and only merge states when the backref points back
503         // to the same object.
504         if (!back_state.get_node()) {
505           back_state.set_node(AddNode(back_header));
506         }
507         back_state.get_node()->SetWrapperNode(v8_node);
508 
509         auto* profiler =
510             reinterpret_cast<Isolate*>(cpp_heap_.isolate())->heap_profiler();
511         if (profiler->HasGetDetachednessCallback()) {
512           back_state.get_node()->SetDetachedness(
513               profiler->GetDetachedness(v8_value, ref.WrapperClassId()));
514         }
515       }
516     }
517   }
518 
AddRootEdge(RootState& root, State& child, std::string edge_name)519   void AddRootEdge(RootState& root, State& child, std::string edge_name) {
520     DCHECK(root.IsVisibleNotDependent());
521     if (!child.IsVisibleNotDependent()) return;
522 
523     // Root states always have a node set.
524     DCHECK_NOT_NULL(root.get_node());
525     if (!child.get_node()) {
526       child.set_node(AddNode(*child.header()));
527     }
528 
529     if (!edge_name.empty()) {
530       graph_.AddEdge(root.get_node(), child.get_node(),
531                      root.get_node()->InternalizeEdgeName(edge_name));
532       return;
533     }
534     graph_.AddEdge(root.get_node(), child.get_node());
535   }
536 
537  private:
538   class WorkstackItemBase;
539   class VisitationItem;
540   class VisitationDoneItem;
541 
542   struct MergedNodeItem {
543     EmbedderGraph::Node* node_;
544     v8::Local<v8::Value> value_;
545     uint16_t wrapper_class_id_;
546   };
547 
548   CppHeap& cpp_heap_;
549   v8::EmbedderGraph& graph_;
550   StateStorage states_;
551   std::vector<std::unique_ptr<WorkstackItemBase>> workstack_;
552 };
553 
554 // Iterating live objects to mark them as visible if needed.
555 class LiveObjectsForVisibilityIterator final
556     : public cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator> {
557   friend class cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator>;
558 
559  public:
LiveObjectsForVisibilityIterator(CppGraphBuilderImpl& graph_builder)560   explicit LiveObjectsForVisibilityIterator(CppGraphBuilderImpl& graph_builder)
561       : graph_builder_(graph_builder) {}
562 
563  private:
VisitHeapObjectHeader(HeapObjectHeader& header)564   bool VisitHeapObjectHeader(HeapObjectHeader& header) {
565     if (header.IsFree()) return true;
566     graph_builder_.VisitForVisibility(nullptr, header);
567     graph_builder_.ProcessPendingObjects();
568     return true;
569   }
570 
571   CppGraphBuilderImpl& graph_builder_;
572 };
573 
574 class ParentScope final {
575  public:
ParentScope(StateBase& parent)576   explicit ParentScope(StateBase& parent) : parent_(parent) {}
577 
ParentAsRootState() const578   RootState& ParentAsRootState() const {
579     return static_cast<RootState&>(parent_);
580   }
ParentAsRegularState() const581   State& ParentAsRegularState() const { return static_cast<State&>(parent_); }
582 
583  private:
584   StateBase& parent_;
585 };
586 
587 // This visitor can be used stand-alone to handle fully weak and ephemeron
588 // containers or as part of the VisibilityVisitor that recursively traverses
589 // the object graph.
590 class WeakVisitor : public JSVisitor {
591  public:
WeakVisitor(CppGraphBuilderImpl& graph_builder)592   explicit WeakVisitor(CppGraphBuilderImpl& graph_builder)
593       : JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
594         graph_builder_(graph_builder) {}
595 
596   void VisitWeakContainer(const void* object,
597                           cppgc::TraceDescriptor strong_desc,
598                           cppgc::TraceDescriptor weak_desc, cppgc::WeakCallback,
599                           const void*) final {
600     const auto& container_header =
601         HeapObjectHeader::FromObject(strong_desc.base_object_payload);
602 
603     graph_builder_.VisitWeakContainerForVisibility(container_header);
604 
605     if (!weak_desc.callback) {
606       // Weak container does not contribute to liveness.
607       return;
608     }
609     // Heap snapshot is always run after a GC so we know there are no dead
610     // entries in the container.
611     if (object) {
612       // The container will itself be traced strongly via the regular Visit()
613       // handling that iterates over all live objects. The visibility visitor
614       // will thus see (because of strongly treating the container):
615       // 1. the container itself;
616       // 2. for each {key} in container: container->key;
617       // 3. for each {key, value} in container: key->value;
618       //
619       // In case the visitor is used stand-alone, we trace through the container
620       // here to create the same state as we would when the container is traced
621       // separately.
622       container_header.Trace(this);
623     }
624   }
625   void VisitEphemeron(const void* key, const void* value,
626                       cppgc::TraceDescriptor value_desc) final {
627     // For ephemerons, the key retains the value.
628     // Key always must be a GarbageCollected object.
629     auto& key_header = HeapObjectHeader::FromObject(key);
630     if (!value_desc.base_object_payload) {
631       // Value does not represent an actual GarbageCollected object but rather
632       // should be traced eagerly.
633       graph_builder_.VisitEphemeronWithNonGarbageCollectedValueForVisibility(
634           key_header, value, value_desc);
635       return;
636     }
637     // Regular path where both key and value are GarbageCollected objects.
638     graph_builder_.VisitEphemeronForVisibility(
639         key_header, HeapObjectHeader::FromObject(value));
640   }
641 
642  protected:
643   CppGraphBuilderImpl& graph_builder_;
644 };
645 
646 class VisiblityVisitor final : public WeakVisitor {
647  public:
VisiblityVisitor(CppGraphBuilderImpl& graph_builder, const ParentScope& parent_scope)648   VisiblityVisitor(CppGraphBuilderImpl& graph_builder,
649                    const ParentScope& parent_scope)
650       : WeakVisitor(graph_builder), parent_scope_(parent_scope) {}
651 
652   // C++ handling.
653   void Visit(const void*, cppgc::TraceDescriptor desc) final {
654     graph_builder_.VisitForVisibility(
655         &parent_scope_.ParentAsRegularState(),
656         HeapObjectHeader::FromObject(desc.base_object_payload));
657   }
658   void VisitRoot(const void*, cppgc::TraceDescriptor,
659                  const cppgc::SourceLocation&) final {}
660   void VisitWeakRoot(const void*, cppgc::TraceDescriptor, cppgc::WeakCallback,
661                      const void*, const cppgc::SourceLocation&) final {}
662 
663   // JS handling.
664   void Visit(const TracedReferenceBase& ref) final {
665     graph_builder_.VisitForVisibility(parent_scope_.ParentAsRegularState(),
666                                       ref);
667   }
668 
669  private:
670   const ParentScope& parent_scope_;
671 };
672 
673 class GraphBuildingVisitor final : public JSVisitor {
674  public:
GraphBuildingVisitor(CppGraphBuilderImpl& graph_builder, const ParentScope& parent_scope)675   GraphBuildingVisitor(CppGraphBuilderImpl& graph_builder,
676                        const ParentScope& parent_scope)
677       : JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
678         graph_builder_(graph_builder),
679         parent_scope_(parent_scope) {}
680 
681   // C++ handling.
682   void Visit(const void*, cppgc::TraceDescriptor desc) final {
683     graph_builder_.AddEdge(
684         parent_scope_.ParentAsRegularState(),
685         HeapObjectHeader::FromObject(desc.base_object_payload), edge_name_);
686   }
687   void VisitWeakContainer(const void* object,
688                           cppgc::TraceDescriptor strong_desc,
689                           cppgc::TraceDescriptor weak_desc, cppgc::WeakCallback,
690                           const void*) final {
691     // Add an edge from the object holding the weak container to the weak
692     // container itself.
693     graph_builder_.AddEdge(
694         parent_scope_.ParentAsRegularState(),
695         HeapObjectHeader::FromObject(strong_desc.base_object_payload),
696         edge_name_);
697   }
698   void VisitRoot(const void*, cppgc::TraceDescriptor desc,
699                  const cppgc::SourceLocation& loc) final {
700     graph_builder_.VisitRootForGraphBuilding(
701         parent_scope_.ParentAsRootState(),
702         HeapObjectHeader::FromObject(desc.base_object_payload), loc);
703   }
704   void VisitWeakRoot(const void*, cppgc::TraceDescriptor, cppgc::WeakCallback,
705                      const void*, const cppgc::SourceLocation&) final {}
706   // JS handling.
707   void Visit(const TracedReferenceBase& ref) final {
708     graph_builder_.AddEdge(parent_scope_.ParentAsRegularState(), ref,
709                            edge_name_);
710   }
711 
set_edge_name(std::string edge_name)712   void set_edge_name(std::string edge_name) {
713     edge_name_ = std::move(edge_name);
714   }
715 
716  private:
717   CppGraphBuilderImpl& graph_builder_;
718   const ParentScope& parent_scope_;
719   std::string edge_name_;
720 };
721 
722 // Base class for transforming recursion into iteration. Items are processed
723 // in stack fashion.
724 class CppGraphBuilderImpl::WorkstackItemBase {
725  public:
WorkstackItemBase(State* parent, State& current)726   WorkstackItemBase(State* parent, State& current)
727       : parent_(parent), current_(current) {}
728 
729   virtual ~WorkstackItemBase() = default;
730   virtual void Process(CppGraphBuilderImpl&) = 0;
731 
732  protected:
733   State* parent_;
734   State& current_;
735 };
736 
ProcessPendingObjects()737 void CppGraphBuilderImpl::ProcessPendingObjects() {
738   while (!workstack_.empty()) {
739     std::unique_ptr<WorkstackItemBase> item = std::move(workstack_.back());
740     workstack_.pop_back();
741     item->Process(*this);
742   }
743 }
744 
745 // Post-order processing of an object. It's guaranteed that all children have
746 // been processed first.
747 class CppGraphBuilderImpl::VisitationDoneItem final : public WorkstackItemBase {
748  public:
VisitationDoneItem(State* parent, State& current)749   VisitationDoneItem(State* parent, State& current)
750       : WorkstackItemBase(parent, current) {}
751 
752   void Process(CppGraphBuilderImpl& graph_builder) final {
753     CHECK(parent_);
754     parent_->MarkDependentVisibility(&current_);
755     current_.UnmarkPending();
756   }
757 };
758 
759 class CppGraphBuilderImpl::VisitationItem final : public WorkstackItemBase {
760  public:
VisitationItem(State* parent, State& current)761   VisitationItem(State* parent, State& current)
762       : WorkstackItemBase(parent, current) {}
763 
764   void Process(CppGraphBuilderImpl& graph_builder) final {
765     if (parent_) {
766       // Re-add the same object for post-order processing. This must happen
767       // lazily, as the parent's visibility depends on its children.
768       graph_builder.workstack_.push_back(std::unique_ptr<WorkstackItemBase>{
769           new VisitationDoneItem(parent_, current_)});
770     }
771     ParentScope parent_scope(current_);
772     VisiblityVisitor object_visitor(graph_builder, parent_scope);
773     current_.header()->Trace(&object_visitor);
774     if (!parent_) {
775       current_.UnmarkPending();
776     }
777   }
778 };
779 
VisitForVisibility(State* parent, const HeapObjectHeader& header)780 void CppGraphBuilderImpl::VisitForVisibility(State* parent,
781                                              const HeapObjectHeader& header) {
782   auto& current = states_.GetOrCreateState(header);
783 
784   if (current.IsVisited()) {
785     // Avoid traversing into already visited subgraphs and just update the state
786     // based on a previous result.
787     if (parent) {
788       parent->MarkDependentVisibility(&current);
789     }
790     return;
791   }
792 
793   current.MarkVisited();
794   if (header.GetName().name_was_hidden) {
795     current.MarkPending();
796     workstack_.push_back(std::unique_ptr<WorkstackItemBase>{
797         new VisitationItem(parent, current)});
798   } else {
799     // No need to mark/unmark pending as the node is immediately processed.
800     current.MarkVisible();
801     // In case the names are visible, the graph is not traversed in this phase.
802     // Explicitly trace one level to handle weak containers.
803     WeakVisitor weak_visitor(*this);
804     header.Trace(&weak_visitor);
805     if (parent) {
806       // Eagerly update a parent object as its visibility state is now fixed.
807       parent->MarkVisible();
808     }
809   }
810 }
811 
812 void CppGraphBuilderImpl::
VisitEphemeronWithNonGarbageCollectedValueForVisibility( const HeapObjectHeader& key, const void* value, cppgc::TraceDescriptor value_desc)813     VisitEphemeronWithNonGarbageCollectedValueForVisibility(
814         const HeapObjectHeader& key, const void* value,
815         cppgc::TraceDescriptor value_desc) {
816   auto& key_state = states_.GetOrCreateState(key);
817   // Eagerly trace the value here, effectively marking key as visible and
818   // queuing processing for all reachable values.
819   ParentScope parent_scope(key_state);
820   VisiblityVisitor visitor(*this, parent_scope);
821   value_desc.callback(&visitor, value);
822   key_state.AddEagerEphemeronEdge(value, value_desc.callback);
823 }
824 
VisitEphemeronForVisibility( const HeapObjectHeader& key, const HeapObjectHeader& value)825 void CppGraphBuilderImpl::VisitEphemeronForVisibility(
826     const HeapObjectHeader& key, const HeapObjectHeader& value) {
827   auto& key_state = states_.GetOrCreateState(key);
828   VisitForVisibility(&key_state, value);
829   key_state.AddEphemeronEdge(value);
830 }
831 
VisitWeakContainerForVisibility( const HeapObjectHeader& container_header)832 void CppGraphBuilderImpl::VisitWeakContainerForVisibility(
833     const HeapObjectHeader& container_header) {
834   // Mark the container here as weak container to avoid creating any
835   // outgoing edges in the second phase.
836   states_.GetOrCreateState(container_header).MarkAsWeakContainer();
837 }
838 
VisitForVisibility(State& parent, const TracedReferenceBase& ref)839 void CppGraphBuilderImpl::VisitForVisibility(State& parent,
840                                              const TracedReferenceBase& ref) {
841   v8::Local<v8::Value> v8_value =
842       ref.Get(reinterpret_cast<v8::Isolate*>(cpp_heap_.isolate()));
843   if (!v8_value.IsEmpty()) {
844     parent.MarkVisible();
845   }
846 }
847 
VisitRootForGraphBuilding( RootState& root, const HeapObjectHeader& header, const cppgc::SourceLocation& loc)848 void CppGraphBuilderImpl::VisitRootForGraphBuilding(
849     RootState& root, const HeapObjectHeader& header,
850     const cppgc::SourceLocation& loc) {
851   State& current = states_.GetExistingState(header);
852   if (!current.IsVisibleNotDependent()) return;
853 
854   AddRootEdge(root, current, loc.ToString());
855 }
856 
Run()857 void CppGraphBuilderImpl::Run() {
858   // Sweeping from a previous GC might still be running, in which case not all
859   // pages have been returned to spaces yet.
860   cpp_heap_.sweeper().FinishIfRunning();
861   // First pass: Figure out which objects should be included in the graph -- see
862   // class-level comment on CppGraphBuilder.
863   LiveObjectsForVisibilityIterator visitor(*this);
864   visitor.Traverse(cpp_heap_.raw_heap());
865   // Second pass: Add graph nodes for objects that must be shown.
866   states_.ForAllVisibleStates([this](StateBase* state_base) {
867     // No roots have been created so far, so all StateBase objects are State.
868     State& state = *static_cast<State*>(state_base);
869 
870     // Emit no edges for the contents of the weak containers. For both, fully
871     // weak and ephemeron containers, the contents should be retained from
872     // somewhere else.
873     if (state.IsWeakContainer()) return;
874 
875     ParentScope parent_scope(state);
876     GraphBuildingVisitor object_visitor(*this, parent_scope);
877     state.header()->Trace(&object_visitor);
878     state.ForAllEphemeronEdges([this, &state](const HeapObjectHeader& value) {
879       AddEdge(state, value, "part of key -> value pair in ephemeron table");
880     });
881     object_visitor.set_edge_name(
882         "part of key -> value pair in ephemeron table");
883     state.ForAllEagerEphemeronEdges(
884         [&object_visitor](const void* value, cppgc::TraceCallback callback) {
885           callback(&object_visitor, value);
886         });
887   });
888   // Add roots.
889   {
890     ParentScope parent_scope(states_.CreateRootState(AddRootNode("C++ roots")));
891     GraphBuildingVisitor object_visitor(*this, parent_scope);
892     cpp_heap_.GetStrongPersistentRegion().Trace(&object_visitor);
893   }
894   {
895     ParentScope parent_scope(
896         states_.CreateRootState(AddRootNode("C++ cross-thread roots")));
897     GraphBuildingVisitor object_visitor(*this, parent_scope);
898     cppgc::internal::PersistentRegionLock guard;
899     cpp_heap_.GetStrongCrossThreadPersistentRegion().Trace(&object_visitor);
900   }
901 }
902 
903 // static
Run(v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data)904 void CppGraphBuilder::Run(v8::Isolate* isolate, v8::EmbedderGraph* graph,
905                           void* data) {
906   CppHeap* cpp_heap = static_cast<CppHeap*>(data);
907   CHECK_NOT_NULL(cpp_heap);
908   CHECK_NOT_NULL(graph);
909   CppGraphBuilderImpl graph_builder(*cpp_heap, *graph);
910   graph_builder.Run();
911 }
912 
913 }  // namespace internal
914 }  // namespace v8
915