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 24namespace v8 { 25namespace internal { 26 27class CppGraphBuilderImpl; 28class StateStorage; 29class State; 30 31using cppgc::internal::GCInfo; 32using cppgc::internal::GlobalGCInfoTable; 33using cppgc::internal::HeapObjectHeader; 34 35// Node representing a C++ object on the heap. 36class EmbedderNode : public v8::EmbedderGraph::Node { 37 public: 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 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 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. 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. 85class EmbedderRootNode final : public EmbedderNode { 86 public: 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. 95class 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 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. 118 bool IsVisited() const { return visited_; } 119 120 // Pending objects are currently being processed as part of the same SCC. 121 bool IsPending() const { return pending_; } 122 123 bool IsVisibleNotDependent() { 124 auto v = GetVisibility(); 125 CHECK_NE(Visibility::kDependentVisibility, v); 126 return v == Visibility::kVisible; 127 } 128 129 void set_node(EmbedderNode* node) { 130 CHECK_EQ(Visibility::kVisible, GetVisibility()); 131 DCHECK_NULL(node_); 132 node_ = node; 133 } 134 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 153 Visibility GetVisibility() { 154 FollowDependencies(); 155 return visibility_; 156 } 157 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 195class State final : public StateBase { 196 public: 197 State(const HeapObjectHeader& header, size_t state_count) 198 : StateBase(&header, state_count, Visibility::kHidden, nullptr, false) {} 199 ~State() final = default; 200 201 const HeapObjectHeader* header() const { 202 return static_cast<const HeapObjectHeader*>(key_); 203 } 204 205 void MarkVisited() { visited_ = true; } 206 207 void MarkPending() { pending_ = true; } 208 void UnmarkPending() { pending_ = false; } 209 210 void MarkVisible() { 211 visibility_ = Visibility::kVisible; 212 visibility_dependency_ = nullptr; 213 } 214 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 258 void MarkAsWeakContainer() { is_weak_container_ = true; } 259 bool IsWeakContainer() const { return is_weak_container_; } 260 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 267 void AddEagerEphemeronEdge(const void* value, cppgc::TraceCallback callback) { 268 eager_ephemeron_edges_.insert({value, callback}); 269 } 270 271 template <typename Callback> 272 void ForAllEphemeronEdges(Callback callback) { 273 for (const HeapObjectHeader* value : ephemeron_edges_) { 274 callback(*value); 275 } 276 } 277 278 template <typename 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. 296class RootState final : public StateBase { 297 public: 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. 306class StateStorage final { 307 public: 308 bool StateExists(const void* key) const { 309 return states_.find(key) != states_.end(); 310 } 311 312 StateBase& GetExistingState(const void* key) const { 313 CHECK(StateExists(key)); 314 return *states_.at(key).get(); 315 } 316 317 State& GetExistingState(const HeapObjectHeader& header) const { 318 return static_cast<State&>(GetExistingState(&header)); 319 } 320 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 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> 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 354void* 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. 411class CppGraphBuilderImpl final { 412 public: 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 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 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 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 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 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. 555class LiveObjectsForVisibilityIterator final 556 : public cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator> { 557 friend class cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator>; 558 559 public: 560 explicit LiveObjectsForVisibilityIterator(CppGraphBuilderImpl& graph_builder) 561 : graph_builder_(graph_builder) {} 562 563 private: 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 574class ParentScope final { 575 public: 576 explicit ParentScope(StateBase& parent) : parent_(parent) {} 577 578 RootState& ParentAsRootState() const { 579 return static_cast<RootState&>(parent_); 580 } 581 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. 590class WeakVisitor : public JSVisitor { 591 public: 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 646class VisiblityVisitor final : public WeakVisitor { 647 public: 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 673class GraphBuildingVisitor final : public JSVisitor { 674 public: 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 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. 724class CppGraphBuilderImpl::WorkstackItemBase { 725 public: 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 737void 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. 747class CppGraphBuilderImpl::VisitationDoneItem final : public WorkstackItemBase { 748 public: 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 759class CppGraphBuilderImpl::VisitationItem final : public WorkstackItemBase { 760 public: 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 780void 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 812void CppGraphBuilderImpl:: 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 825void 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 832void 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 839void 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 848void 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 857void 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 904void 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