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(¤t_);
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(¤t);
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