v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
cpp-snapshot.cc
Go to the documentation of this file.
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
6
7#include <memory>
8
13#include "include/v8-cppgc.h"
14#include "include/v8-internal.h"
15#include "include/v8-profiler.h"
16#include "src/api/api-inl.h"
17#include "src/base/logging.h"
27
28namespace v8 {
29namespace internal {
30
31class CppGraphBuilderImpl;
32class StateStorage;
33class State;
34
36
37// Node representing a C++ object on the heap.
39 public:
40 EmbedderNode(const HeapObjectHeader* header_address,
41 cppgc::internal::HeapObjectName name, size_t size)
42 : header_address_(header_address),
43 name_(name.value),
44 size_(name.name_was_hidden ? 0 : size) {}
45 ~EmbedderNode() override = default;
46
47 const char* Name() final { return name_; }
48 size_t SizeInBytes() final { return size_; }
49
51 // An embedder node may only be merged with a single wrapper node, as
52 // consumers of the graph may merge a node and its wrapper node.
53 //
54 // TODO(chromium:1218404): Add a DCHECK() to avoid overriding an already
55 // set `wrapper_node_`. This can currently happen with global proxies that
56 // are rewired (and still kept alive) after reloading a page, see
57 // `AddEdge`. We accept overriding the wrapper node in such cases,
58 // leading to a random merged node and separated nodes for all other
59 // proxies.
60 wrapper_node_ = wrapper_node;
61 }
62 Node* WrapperNode() final { return wrapper_node_; }
63
64 void SetDetachedness(Detachedness detachedness) {
65 detachedness_ = detachedness;
66 }
68
69 // Edge names are passed to V8 but are required to be held alive from the
70 // embedder until the snapshot is compiled.
71 const char* InternalizeEdgeName(std::string edge_name) {
72 const size_t edge_name_len = edge_name.length();
73 named_edges_.emplace_back(std::make_unique<char[]>(edge_name_len + 1));
74 char* named_edge_str = named_edges_.back().get();
75 snprintf(named_edge_str, edge_name_len + 1, "%s", edge_name.c_str());
76 return named_edge_str;
77 }
78
79 const void* GetAddress() override { return header_address_; }
80
81 private:
82 const void* header_address_;
83 const char* name_;
84 size_t size_;
85 Node* wrapper_node_ = nullptr;
87 std::vector<std::unique_ptr<char[]>> named_edges_;
88};
89
91
92// Node representing an artificial root group, e.g., set of Persistent handles.
93class EmbedderRootNode final : public EmbedderNode {
94 public:
95 explicit EmbedderRootNode(const char* name)
96 : EmbedderNode(kNoNativeAddress, {name, false}, 0) {}
97 ~EmbedderRootNode() final = default;
98
99 bool IsRootNode() final { return true; }
100};
101
102// Canonical state representing real and artificial (e.g. root) objects.
104 public:
105 // Objects can either be hidden/visible, or depend on some other nodes while
106 // traversing the same SCC.
107 enum class Visibility {
108 kHidden,
110 kVisible,
111 };
112
113 StateBase(const void* key, size_t state_count, Visibility visibility,
114 EmbedderNode* node, bool visited)
115 : key_(key),
116 state_count_(state_count),
117 visibility_(visibility),
118 node_(node),
119 visited_(visited) {
121 }
122 virtual ~StateBase() = default;
123
124 // Visited objects have already been processed or are currently being
125 // processed, see also IsPending() below.
126 bool IsVisited() const { return visited_; }
127
128 // Pending objects are currently being processed as part of the same SCC.
129 bool IsPending() const { return pending_; }
130
136
142
147
148 protected:
149 const void* key_;
150 // State count keeps track of node processing order. It is used to create only
151 // dependencies on ancestors in the sub graph which ensures that there will be
152 // no cycles in dependencies.
153 const size_t state_count_;
154
159 bool pending_ = false;
160
165
169 return this;
170 }
171 StateBase* current = this;
172 std::vector<StateBase*> dependencies;
173 while (current->visibility_dependency_ &&
174 current->visibility_dependency_ != current) {
175 DCHECK_EQ(Visibility::kDependentVisibility, current->visibility_);
176 dependencies.push_back(current);
177 current = current->visibility_dependency_;
178 }
179 auto new_visibility = Visibility::kDependentVisibility;
180 auto* new_visibility_dependency = current;
181 if (current->visibility_ == Visibility::kVisible) {
182 new_visibility = Visibility::kVisible;
183 new_visibility_dependency = nullptr;
184 } else if (!IsPending()) {
185 DCHECK(IsVisited());
186 // The object was not visible (above case). Having a dependency on itself
187 // or null means no visible object was found.
188 new_visibility = Visibility::kHidden;
189 new_visibility_dependency = nullptr;
190 }
191 current->visibility_ = new_visibility;
192 current->visibility_dependency_ = new_visibility_dependency;
193 for (auto* state : dependencies) {
194 state->visibility_ = new_visibility;
195 state->visibility_dependency_ = new_visibility_dependency;
196 }
197 return current;
198 }
199
200 friend class State;
201};
202
203class State final : public StateBase {
204 public:
205 State(const HeapObjectHeader& header, size_t state_count)
206 : StateBase(&header, state_count, Visibility::kHidden, nullptr, false) {}
207 ~State() final = default;
208
209 const HeapObjectHeader* header() const {
210 return static_cast<const HeapObjectHeader*>(key_);
211 }
212
213 void MarkVisited() { visited_ = true; }
214
215 void MarkPending() { pending_ = true; }
216 void UnmarkPending() { pending_ = false; }
217
222
224 // Follow and update dependencies as much as possible.
225 dependency = dependency->FollowDependencies();
226 DCHECK(dependency->IsVisited());
228 // Already visible, no dependency needed.
230 return;
231 }
232 if (dependency->visibility_ == Visibility::kVisible) {
233 // Simple case: Dependency is visible.
235 visibility_dependency_ = nullptr;
236 return;
237 }
239 (visibility_dependency_->state_count_ > dependency->state_count_)) ||
241 (state_count_ > dependency->state_count_))) {
242 // Only update when new state_count_ < original state_count_. This
243 // ensures that we pick an ancestor as dependency and not a child which
244 // is guaranteed to converge to an answer.
245 //
246 // Dependency is now
247 // a) either pending with unknown visibility (same call chain), or
248 // b) not pending and has defined visibility.
249 //
250 // It's not possible to point to a state that is not pending but has
251 // dependent visibility because dependencies are updated to the top-most
252 // dependency at the beginning of method.
253 if (dependency->IsPending()) {
255 visibility_dependency_ = dependency;
256 } else {
257 CHECK_NE(Visibility::kDependentVisibility, dependency->visibility_);
258 if (dependency->visibility_ == Visibility::kVisible) {
260 visibility_dependency_ = nullptr;
261 }
262 }
263 }
264 }
265
267 bool IsWeakContainer() const { return is_weak_container_; }
268
271
273 // This ignores duplicate entries (in different containers) for the same
274 // Key->Value pairs. Only one edge will be emitted in this case.
275 ephemeron_keys_.insert(&key);
276 }
277
279 // This ignores duplicate entries (in different containers) for the same
280 // Key->Value pairs. Only one edge will be emitted in this case.
281 ephemeron_edges_.insert(&value);
282 }
283
287
288 template <typename Callback>
290 for (const HeapObjectHeader* value : ephemeron_keys_) {
291 callback(*value);
292 }
293 }
294
295 template <typename Callback>
297 for (const HeapObjectHeader* value : ephemeron_edges_) {
298 callback(*value);
299 }
300 }
301
302 template <typename Callback>
304 for (const auto& pair : eager_ephemeron_edges_) {
305 callback(pair.first, pair.second);
306 }
307 }
308
309 private:
310 bool is_weak_container_ = false;
312 // Ephemeron keys that will be strongified if a weak container is reachable
313 // from stack.
314 std::unordered_set<const HeapObjectHeader*> ephemeron_keys_;
315 // Values that are held alive through ephemerons by this particular key.
316 std::unordered_set<const HeapObjectHeader*> ephemeron_edges_;
317 // Values that are eagerly traced and held alive through ephemerons by this
318 // particular key.
319 std::unordered_map<const void*, cppgc::TraceCallback> eager_ephemeron_edges_;
320};
321
322// Root states are similar to regular states with the difference that they are
323// always visible.
324class RootState final : public StateBase {
325 public:
326 RootState(EmbedderRootNode* node, size_t state_count)
327 // Root states are always visited, visible, and have a node attached.
328 : StateBase(node, state_count, Visibility::kVisible, node, true) {}
329 ~RootState() final = default;
330};
331
332// Abstraction for storing states. Storage allows for creation and lookup of
333// different state objects.
334class StateStorage final {
335 public:
336 bool StateExists(const void* key) const {
337 return states_.find(key) != states_.end();
338 }
339
340 StateBase& GetExistingState(const void* key) const {
341 CHECK(StateExists(key));
342 return *states_.at(key).get();
343 }
344
346 return static_cast<State&>(GetExistingState(&header));
347 }
348
350 if (!StateExists(&header)) {
351 auto it = states_.insert(std::make_pair(
352 &header, std::make_unique<State>(header, ++state_count_)));
353 DCHECK(it.second);
354 USE(it);
355 }
356 return GetExistingState(header);
357 }
358
360 CHECK(!StateExists(root_node));
361 auto it = states_.insert(std::make_pair(
362 root_node, std::make_unique<RootState>(root_node, ++state_count_)));
363 DCHECK(it.second);
364 USE(it);
365 return static_cast<RootState&>(*it.first->second);
366 }
367
368 template <typename Callback>
369 void ForAllStates(Callback callback) {
370 for (auto& state : states_) {
371 callback(state.second.get());
372 }
373 }
374
375 private:
376 std::unordered_map<const void*, std::unique_ptr<StateBase>> states_;
377 size_t state_count_ = 0;
378};
379
381 v8::Local<v8::Data> v8_value) {
382 if (!(v8_value->IsValue() && v8_value.As<v8::Value>()->IsObject()))
383 return nullptr;
384
385 DirectHandle<Object> v8_object = Utils::OpenDirectHandle(*v8_value);
386 if (!IsJSObject(*v8_object) ||
387 !Cast<JSObject>(*v8_object)->MayHaveEmbedderFields()) {
388 return nullptr;
389 }
390
391 Tagged<JSObject> js_object = Cast<JSObject>(*v8_object);
392 // Not every object that can have embedder fields is actually a JSApiWrapper.
393 if (!IsJSApiWrapperObject(*js_object)) {
394 return nullptr;
395 }
396 // Wrapper using cpp_heap_wrappable field.
397 return JSApiWrapper(*js_object)
399}
400
401// The following implements a snapshotting algorithm for C++ objects that also
402// filters strongly-connected components (SCCs) of only "hidden" objects that
403// are not (transitively) referencing any non-hidden objects.
404//
405// C++ objects come in two versions.
406// a. Named objects that have been assigned a name through NameProvider.
407// b. Unnamed objects, that are potentially hidden if the build configuration
408// requires Oilpan to hide such names. Hidden objects have their name
409// set to NameProvider::kHiddenName.
410//
411// The main challenge for the algorithm is to avoid blowing up the final object
412// graph with hidden nodes that do not carry information. For that reason, the
413// algorithm filters SCCs of only hidden objects, e.g.:
414// ... -> (object) -> (object) -> (hidden) -> (hidden)
415// In this case the (hidden) objects are filtered from the graph. The trickiest
416// part is maintaining visibility state for objects referencing other objects
417// that are currently being processed.
418//
419// Main algorithm idea (two passes):
420// 1. First pass marks all non-hidden objects and those that transitively reach
421// non-hidden objects as visible. Details:
422// - Iterate over all objects.
423// - If object is non-hidden mark it as visible and also mark parent as
424// visible if needed.
425// - If object is hidden, traverse children as DFS to find non-hidden
426// objects. Post-order process the objects and mark those objects as
427// visible that have child nodes that are visible themselves.
428// - Maintain an epoch counter (StateStorage::state_count_) to allow
429// deferring the visibility decision to other objects in the same SCC. This
430// is similar to the "lowlink" value in Tarjan's algorithm for SCC.
431// - After the first pass it is guaranteed that all deferred visibility
432// decisions can be resolved.
433// 2. Second pass adds nodes and edges for all visible objects.
434// - Upon first checking the visibility state of an object, all deferred
435// visibility states are resolved.
436//
437// For practical reasons, the recursion is transformed into an iteration. We do
438// do not use plain Tarjan's algorithm to avoid another pass over all nodes to
439// create SCCs.
441 public:
443 : cpp_heap_(cpp_heap), graph_(graph) {}
444
445 void Run();
446
447 void VisitForVisibility(State* parent, const HeapObjectHeader&);
448 void VisitForVisibility(State& parent, const TracedReferenceBase&);
449 void VisitEphemeronForVisibility(const HeapObjectHeader& key,
450 const HeapObjectHeader& value);
451 void VisitEphemeronWithNonGarbageCollectedValueForVisibility(
452 const HeapObjectHeader& key, const void* value,
453 cppgc::TraceDescriptor value_desc);
454 void VisitWeakContainerForVisibility(const HeapObjectHeader&);
455 void VisitRootForGraphBuilding(RootState&, const HeapObjectHeader&,
456 const cppgc::SourceLocation&);
457 void ProcessPendingObjects();
458
459 void RecordEphemeronKey(const HeapObjectHeader&, const HeapObjectHeader&);
460 void AddConservativeEphemeronKeyEdgesIfNeeded(const HeapObjectHeader&);
461
462 EmbedderRootNode* AddRootNode(const char* name) {
463 return static_cast<EmbedderRootNode*>(graph_.AddNode(
464 std::unique_ptr<v8::EmbedderGraph::Node>{new EmbedderRootNode(name)}));
465 }
466
468 size_t size = header.AllocatedSize();
469 EmbedderNode* node = static_cast<EmbedderNode*>(
470 graph_.AddNode(std::unique_ptr<v8::EmbedderGraph::Node>{
471 new EmbedderNode(&header, header.GetName(), size)}));
472 size_t node_size = node->SizeInBytes();
473 if (size > node_size) {
474 graph_.AddNativeSize(size - node_size);
475 }
476 return node;
477 }
478
479 void AddEdge(State& parent, const HeapObjectHeader& header,
480 const std::string& edge_name) {
482 auto& current = states_.GetExistingState(header);
483 if (!current.IsVisibleNotDependent()) return;
484
485 // Both states are visible. Create nodes in case this is the first edge
486 // created for any of them.
487 if (!parent.get_node()) {
488 parent.set_node(AddNode(*parent.header()));
489 }
490 if (!current.get_node()) {
491 current.set_node(AddNode(header));
492 }
493
494 if (!edge_name.empty()) {
495 graph_.AddEdge(parent.get_node(), current.get_node(),
496 parent.get_node()->InternalizeEdgeName(edge_name));
497 } else {
498 graph_.AddEdge(parent.get_node(), current.get_node());
499 }
500 }
501
502 void AddEdge(State& parent, const TracedReferenceBase& ref,
503 const std::string& edge_name) {
505 v8::Local<v8::Data> v8_data =
506 ref.Get(reinterpret_cast<v8::Isolate*>(cpp_heap_.isolate()));
507 if (v8_data.IsEmpty()) return;
508
509 if (!parent.get_node()) {
510 parent.set_node(AddNode(*parent.header()));
511 }
512 auto* v8_node = graph_.V8Node(v8_data);
513 if (!edge_name.empty()) {
514 graph_.AddEdge(parent.get_node(), v8_node,
515 parent.get_node()->InternalizeEdgeName(edge_name));
516 } else {
517 graph_.AddEdge(parent.get_node(), v8_node);
518 }
519
520 // Don't merge nodes if edges have a name.
521 if (!edge_name.empty()) return;
522
523 // Try to extract the back reference. If the back reference matches
524 // `parent`, then the nodes are merged.
525 void* back_reference_object = ExtractEmbedderDataBackref(
526 reinterpret_cast<v8::internal::Isolate*>(cpp_heap_.isolate()),
527 cpp_heap_, v8_data);
528 if (!back_reference_object) return;
529 // Only JS objects have back references.
530 DCHECK(v8_data->IsValue() && v8_data.As<v8::Value>()->IsObject());
531
532 auto& back_header = HeapObjectHeader::FromObject(back_reference_object);
533 auto& back_state = states_.GetExistingState(back_header);
534
535 // If the back reference doesn't point to the same header, just return. In
536 // such a case we have stand-alone references to a wrapper.
537 if (parent.header() != back_state.header()) return;
538
539 // Back reference points to parents header. In this case, the nodes should
540 // be merged and query the detachedness state of the embedder.
541 if (!back_state.get_node()) {
542 back_state.set_node(AddNode(back_header));
543 }
544 back_state.get_node()->SetWrapperNode(v8_node);
545
546 auto* profiler = reinterpret_cast<Isolate*>(cpp_heap_.isolate())
547 ->heap()
548 ->heap_profiler();
549 if (profiler->HasGetDetachednessCallback()) {
550 back_state.get_node()->SetDetachedness(
551 profiler->GetDetachedness(v8_data.As<v8::Value>(), 0));
552 }
553 }
554
555 void AddRootEdge(RootState& root, State& child, std::string edge_name) {
557 if (!child.IsVisibleNotDependent()) return;
558
559 // Root states always have a node set.
561 if (!child.get_node()) {
562 child.set_node(AddNode(*child.header()));
563 }
564
565 if (!edge_name.empty()) {
566 graph_.AddEdge(root.get_node(), child.get_node(),
567 root.get_node()->InternalizeEdgeName(edge_name));
568 return;
569 }
570 graph_.AddEdge(root.get_node(), child.get_node());
571 }
572
573 private:
574 class WorkstackItemBase;
575 class VisitationItem;
576 class VisitationDoneItem;
577
582
586 std::vector<std::unique_ptr<WorkstackItemBase>> workstack_;
587};
588
589// Iterating live objects to mark them as visible if needed.
591 : public cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator> {
593
594 public:
596 : graph_builder_(graph_builder) {}
597
598 private:
600 if (header.IsFree()) return true;
601 graph_builder_.VisitForVisibility(nullptr, header);
602 graph_builder_.ProcessPendingObjects();
603 return true;
604 }
605
607};
608
609class ParentScope final {
610 public:
611 explicit ParentScope(StateBase& parent) : parent_(parent) {}
612
614 return static_cast<RootState&>(parent_);
615 }
616 State& ParentAsRegularState() const { return static_cast<State&>(parent_); }
617
618 private:
620};
621
622// This visitor can be used stand-alone to handle fully weak and ephemeron
623// containers or as part of the VisibilityVisitor that recursively traverses
624// the object graph.
625class WeakVisitor : public JSVisitor {
626 public:
627 explicit WeakVisitor(CppGraphBuilderImpl& graph_builder)
628 : JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
629 graph_builder_(graph_builder) {}
630
631 void VisitWeakContainer(const void* object,
632 cppgc::TraceDescriptor strong_desc,
634 const void*) final {
635 const auto& container_header =
636 HeapObjectHeader::FromObject(strong_desc.base_object_payload);
637 WeakContainerScope weak_container_scope(*this, container_header);
638
639 graph_builder_.VisitWeakContainerForVisibility(container_header);
640
641 if (!weak_desc.callback) {
642 // Weak container does not contribute to liveness.
643 return;
644 }
645 // Heap snapshot is always run after a GC so we know there are no dead
646 // entries in the container.
647 if (object) {
648 // The container will itself be traced strongly via the regular Visit()
649 // handling that iterates over all live objects. The visibility visitor
650 // will thus see (because of strongly treating the container):
651 // 1. the container itself;
652 // 2. for each {key} in container: container->key;
653 // 3. for each {key, value} in container: key->value;
654 //
655 // In case the visitor is used stand-alone, we trace through the container
656 // here to create the same state as we would when the container is traced
657 // separately.
658 container_header.TraceImpl(this);
659 }
660 }
661 void VisitEphemeron(const void* key, const void* value,
662 cppgc::TraceDescriptor value_desc) final {
663 // For ephemerons, the key retains the value.
664 // Key always must be a GarbageCollected object.
665 auto& key_header = HeapObjectHeader::FromObject(key);
666 if (current_weak_container_header_) {
667 graph_builder_.RecordEphemeronKey(*current_weak_container_header_,
668 key_header);
669 }
670 if (!value_desc.base_object_payload) {
671 // Value does not represent an actual GarbageCollected object but rather
672 // should be traced eagerly.
673 graph_builder_.VisitEphemeronWithNonGarbageCollectedValueForVisibility(
674 key_header, value, value_desc);
675 return;
676 }
677 // Regular path where both key and value are GarbageCollected objects.
678 graph_builder_.VisitEphemeronForVisibility(
679 key_header, HeapObjectHeader::FromObject(value));
680 }
681
682 protected:
684 public:
685 explicit WeakContainerScope(WeakVisitor& weak_visitor,
686 const HeapObjectHeader& weak_container_header)
687 : weak_visitor_(weak_visitor),
688 prev_weak_container_header_(
689 weak_visitor_.current_weak_container_header_) {
690 weak_visitor_.current_weak_container_header_ = &weak_container_header;
691 }
693 weak_visitor_.current_weak_container_header_ =
694 prev_weak_container_header_;
695 }
696
697 private:
700 };
701
703 const HeapObjectHeader* current_weak_container_header_ = nullptr;
704};
705
706class VisiblityVisitor final : public WeakVisitor {
707 public:
709 const ParentScope& parent_scope)
710 : WeakVisitor(graph_builder), parent_scope_(parent_scope) {}
711
712 // C++ handling.
713 void Visit(const void*, cppgc::TraceDescriptor desc) final {
714 graph_builder_.VisitForVisibility(
715 &parent_scope_.ParentAsRegularState(),
716 HeapObjectHeader::FromObject(desc.base_object_payload));
717 }
718
719 // JS handling.
720 void Visit(const TracedReferenceBase& ref) final {
721 graph_builder_.VisitForVisibility(parent_scope_.ParentAsRegularState(),
722 ref);
723 }
724
725 private:
727};
728
730 public:
732 const ParentScope& parent_scope)
733 : graph_builder_(graph_builder), parent_scope_(parent_scope) {}
734
735 void VisitRoot(const void*, cppgc::TraceDescriptor desc,
736 const cppgc::SourceLocation& loc) final {
737 graph_builder_.VisitRootForGraphBuilding(
738 parent_scope_.ParentAsRootState(),
739 HeapObjectHeader::FromObject(desc.base_object_payload), loc);
740 }
741
742 private:
745};
746
747class GraphBuildingVisitor final : public JSVisitor {
748 public:
750 const ParentScope& parent_scope)
751 : JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
752 graph_builder_(graph_builder),
753 parent_scope_(parent_scope) {}
754
755 // C++ handling.
756 void Visit(const void*, cppgc::TraceDescriptor desc) final {
757 graph_builder_.AddEdge(
758 parent_scope_.ParentAsRegularState(),
759 HeapObjectHeader::FromObject(desc.base_object_payload), edge_name_);
760 }
761 void VisitWeakContainer(const void* object,
762 cppgc::TraceDescriptor strong_desc,
764 const void*) final {
765 // Add an edge from the object holding the weak container to the weak
766 // container itself.
767 graph_builder_.AddEdge(
768 parent_scope_.ParentAsRegularState(),
769 HeapObjectHeader::FromObject(strong_desc.base_object_payload),
770 edge_name_);
771 }
772
773 // JS handling.
774 void Visit(const TracedReferenceBase& ref) final {
775 graph_builder_.AddEdge(parent_scope_.ParentAsRegularState(), ref,
776 edge_name_);
777 }
778
779 void set_edge_name(std::string edge_name) {
780 edge_name_ = std::move(edge_name);
781 }
782
783 private:
786 std::string edge_name_;
787};
788
789// Base class for transforming recursion into iteration. Items are processed
790// in stack fashion.
792 public:
793 WorkstackItemBase(State* parent, State& current)
794 : parent_(parent), current_(current) {}
795
796 virtual ~WorkstackItemBase() = default;
797 virtual void Process(CppGraphBuilderImpl&) = 0;
798
799 protected:
802};
803
805 while (!workstack_.empty()) {
806 std::unique_ptr<WorkstackItemBase> item = std::move(workstack_.back());
807 workstack_.pop_back();
808 item->Process(*this);
809 }
810}
811
812// Post-order processing of an object. It's guaranteed that all children have
813// been processed first.
815 public:
816 VisitationDoneItem(State* parent, State& current)
817 : WorkstackItemBase(parent, current) {}
818
819 void Process(CppGraphBuilderImpl& graph_builder) final {
820 CHECK(parent_);
821 parent_->MarkDependentVisibility(&current_);
822 current_.UnmarkPending();
823 }
824};
825
827 public:
828 VisitationItem(State* parent, State& current)
829 : WorkstackItemBase(parent, current) {}
830
831 void Process(CppGraphBuilderImpl& graph_builder) final {
832 if (parent_) {
833 // Re-add the same object for post-order processing. This must happen
834 // lazily, as the parent's visibility depends on its children.
835 graph_builder.workstack_.push_back(std::unique_ptr<WorkstackItemBase>{
837 }
838 ParentScope parent_scope(current_);
839 VisiblityVisitor object_visitor(graph_builder, parent_scope);
840 if (!current_.header()->IsInConstruction()) {
841 // TODO(mlippautz): Handle in construction objects.
842 current_.header()->TraceImpl(&object_visitor);
843 }
844 if (!parent_) {
845 current_.UnmarkPending();
846 }
847 }
848};
849
851 const HeapObjectHeader& header) {
852 auto& current = states_.GetOrCreateState(header);
853
854 if (current.IsVisited()) {
855 // Avoid traversing into already visited subgraphs and just update the state
856 // based on a previous result.
857 if (parent) {
858 parent->MarkDependentVisibility(&current);
859 }
860 return;
861 }
862
863 current.MarkVisited();
864 if (header.GetName().name_was_hidden) {
865 current.MarkPending();
866 workstack_.push_back(std::unique_ptr<WorkstackItemBase>{
867 new VisitationItem(parent, current)});
868 } else {
869 // No need to mark/unmark pending as the node is immediately processed.
870 current.MarkVisible();
871 // In case the names are visible, the graph is not traversed in this phase.
872 // Explicitly trace one level to handle weak containers.
873 WeakVisitor weak_visitor(*this);
874 header.TraceImpl(&weak_visitor);
875 if (parent) {
876 // Eagerly update a parent object as its visibility state is now fixed.
877 parent->MarkVisible();
878 }
879 }
880}
881
884 const HeapObjectHeader& key, const void* value,
885 cppgc::TraceDescriptor value_desc) {
886 auto& key_state = states_.GetOrCreateState(key);
887 // Eagerly trace the value here, effectively marking key as visible and
888 // queuing processing for all reachable values.
889 ParentScope parent_scope(key_state);
890 VisiblityVisitor visitor(*this, parent_scope);
891 value_desc.callback(&visitor, value);
892 key_state.AddEagerEphemeronEdge(value, value_desc.callback);
893}
894
896 const HeapObjectHeader& key, const HeapObjectHeader& value) {
897 auto& key_state = states_.GetOrCreateState(key);
898 VisitForVisibility(&key_state, value);
899 key_state.AddEphemeronEdge(value);
900}
901
903 const HeapObjectHeader& container_header) {
904 // Mark the container here as weak container to avoid creating any
905 // outgoing edges in the second phase.
906 states_.GetOrCreateState(container_header).MarkAsWeakContainer();
907}
908
910 const HeapObjectHeader& key) {
911 auto& container_state = states_.GetOrCreateState(container);
912 DCHECK(container_state.IsWeakContainer());
913 container_state.RecordEphemeronKey(key);
914}
915
917 const HeapObjectHeader& header) {
918 auto& state = states_.GetExistingState(header);
919 if (state.WasVisitedFromStack()) {
920 return;
921 }
922 state.MarkVisitedFromStack();
923 state.ForAllEphemeronKeys([this, &state](const HeapObjectHeader& key) {
924 DCHECK(state.IsWeakContainer());
925 AddEdge(state, key, "");
926 });
927}
928
930 const TracedReferenceBase& ref) {
931 v8::Local<v8::Data> v8_value =
932 ref.Get(reinterpret_cast<v8::Isolate*>(cpp_heap_.isolate()));
933 if (!v8_value.IsEmpty()) {
934 parent.MarkVisible();
935 }
936}
937
939 RootState& root, const HeapObjectHeader& header,
940 const cppgc::SourceLocation& loc) {
941 State& current = states_.GetExistingState(header);
942 if (!current.IsVisibleNotDependent()) return;
943
944 AddRootEdge(root, current, loc.ToString());
945}
946
947namespace {
948
949// Visitor adds edges from native stack roots to objects.
950class GraphBuildingStackVisitor
953 public cppgc::Visitor {
954 public:
955 GraphBuildingStackVisitor(CppGraphBuilderImpl& graph_builder, CppHeap& heap,
956 GraphBuildingRootVisitor& root_visitor)
957 : cppgc::internal::ConservativeTracingVisitor(heap, *heap.page_backend(),
958 *this),
959 cppgc::Visitor(cppgc::internal::VisitorFactory::CreateKey()),
960 graph_builder_(graph_builder),
961 root_visitor_(root_visitor) {}
962
963 void VisitPointer(const void* address) final {
964 // Entry point for stack walk. The conservative visitor dispatches as
965 // follows:
966 // - Fully constructed objects: VisitFullyConstructedConservatively()
967 // - Objects in construction: VisitInConstructionConservatively()
968 TraceConservativelyIfNeeded(address);
969 }
970
971 void VisitFullyConstructedConservatively(HeapObjectHeader& header) final {
972 VisitConservatively(header);
973 }
974
975 void VisitInConstructionConservatively(HeapObjectHeader& header,
976 TraceConservativelyCallback) final {
977 VisitConservatively(header);
978 }
979
980 private:
981 void VisitConservatively(const HeapObjectHeader& header) {
982 root_visitor_.VisitRoot(header.ObjectStart(),
983 {header.ObjectStart(), nullptr},
985 graph_builder_.AddConservativeEphemeronKeyEdgesIfNeeded(header);
986 }
987
988 CppGraphBuilderImpl& graph_builder_;
989 GraphBuildingRootVisitor& root_visitor_;
990};
991
992} // namespace
993
995 // Sweeping from a previous GC might still be running, in which case not all
996 // pages have been returned to spaces yet.
997 cpp_heap_.sweeper().FinishIfRunning();
999 cpp_heap_.GetHeapHandle());
1000 // First pass: Figure out which objects should be included in the graph -- see
1001 // class-level comment on CppGraphBuilder.
1002 LiveObjectsForVisibilityIterator visitor(*this);
1003 visitor.Traverse(cpp_heap_.raw_heap());
1004 // Second pass: Add graph nodes for objects that must be shown.
1005 states_.ForAllStates([this](StateBase* state_base) {
1006 // No roots have been created so far, so all StateBase objects are State.
1007 State& state = *static_cast<State*>(state_base);
1008
1009 if (!state.IsVisibleNotDependent()) {
1010 graph_.AddNativeSize(state.header()->AllocatedSize());
1011 return;
1012 }
1013
1014 // Emit no edges for the contents of the weak containers. For both, fully
1015 // weak and ephemeron containers, the contents should be retained from
1016 // somewhere else.
1017 if (state.IsWeakContainer()) return;
1018
1019 ParentScope parent_scope(state);
1020 GraphBuildingVisitor object_visitor(*this, parent_scope);
1021 if (!state.header()->IsInConstruction()) {
1022 // TODO(mlippautz): Handle in-construction objects.
1023 state.header()->TraceImpl(&object_visitor);
1024 }
1025 state.ForAllEphemeronEdges([this, &state](const HeapObjectHeader& value) {
1026 AddEdge(state, value, "part of key -> value pair in ephemeron table");
1027 });
1028 object_visitor.set_edge_name(
1029 "part of key -> value pair in ephemeron table");
1030 state.ForAllEagerEphemeronEdges(
1031 [&object_visitor](const void* value, cppgc::TraceCallback callback) {
1032 callback(&object_visitor, value);
1033 });
1034 });
1035 // Add roots.
1036 {
1037 ParentScope parent_scope(
1038 states_.CreateRootState(AddRootNode("C++ Persistent roots")));
1039 GraphBuildingRootVisitor root_object_visitor(*this, parent_scope);
1040 cpp_heap_.GetStrongPersistentRegion().Iterate(root_object_visitor);
1041 }
1042 {
1043 ParentScope parent_scope(states_.CreateRootState(
1044 AddRootNode("C++ CrossThreadPersistent roots")));
1045 GraphBuildingRootVisitor root_object_visitor(*this, parent_scope);
1047 cpp_heap_.GetStrongCrossThreadPersistentRegion().Iterate(
1048 root_object_visitor);
1049 }
1050 // Only add stack roots in case the callback is not run from generating a
1051 // snapshot without stack. This avoids adding false-positive edges when
1052 // conservatively scanning the stack.
1053 if (cpp_heap_.isolate()->heap()->IsGCWithMainThreadStack()) {
1054 ParentScope parent_scope(
1055 states_.CreateRootState(AddRootNode("C++ native stack roots")));
1056 GraphBuildingRootVisitor root_object_visitor(*this, parent_scope);
1057 GraphBuildingStackVisitor stack_visitor(*this, cpp_heap_,
1058 root_object_visitor);
1059 cpp_heap_.stack()->IteratePointersUntilMarker(&stack_visitor);
1060 }
1061}
1062
1063// static
1065 void* data) {
1066 CppHeap* cpp_heap = static_cast<CppHeap*>(data);
1067 CHECK_NOT_NULL(cpp_heap);
1068 CHECK_NOT_NULL(graph);
1069 CppGraphBuilderImpl graph_builder(*cpp_heap, *graph);
1070 graph_builder.Run();
1071}
1072
1073} // namespace internal
1074} // namespace v8
union v8::internal::@341::BuiltinMetadata::KindSpecificData data
V8_EXPORT_PRIVATE HeapObjectName GetName() const
void Traverse(RawHeap &heap)
V8_INLINE Local< S > As() const
std::string ToString() const
V8_INLINE Local< Data > Get(Isolate *isolate) const
static v8::internal::DirectHandle< To > OpenDirectHandle(v8::Local< From > handle)
Definition api.h:279
bool IsObject() const
Definition api.cc:3572
void Process(CppGraphBuilderImpl &graph_builder) final
VisitationItem(State *parent, State &current)
void Process(CppGraphBuilderImpl &graph_builder) final
virtual void Process(CppGraphBuilderImpl &)=0
void VisitWeakContainerForVisibility(const HeapObjectHeader &)
void VisitEphemeronWithNonGarbageCollectedValueForVisibility(const HeapObjectHeader &key, const void *value, cppgc::TraceDescriptor value_desc)
void VisitRootForGraphBuilding(RootState &, const HeapObjectHeader &, const cppgc::SourceLocation &)
void AddEdge(State &parent, const TracedReferenceBase &ref, const std::string &edge_name)
EmbedderNode * AddNode(const HeapObjectHeader &header)
std::vector< std::unique_ptr< WorkstackItemBase > > workstack_
void VisitEphemeronForVisibility(const HeapObjectHeader &key, const HeapObjectHeader &value)
void AddConservativeEphemeronKeyEdgesIfNeeded(const HeapObjectHeader &)
CppGraphBuilderImpl(CppHeap &cpp_heap, v8::EmbedderGraph &graph)
EmbedderRootNode * AddRootNode(const char *name)
void VisitForVisibility(State *parent, const HeapObjectHeader &)
void AddEdge(State &parent, const HeapObjectHeader &header, const std::string &edge_name)
void RecordEphemeronKey(const HeapObjectHeader &, const HeapObjectHeader &)
void AddRootEdge(RootState &root, State &child, std::string edge_name)
static void Run(v8::Isolate *isolate, v8::EmbedderGraph *graph, void *data)
const char * Name() final
~EmbedderNode() override=default
const char * InternalizeEdgeName(std::string edge_name)
void SetWrapperNode(v8::EmbedderGraph::Node *wrapper_node)
std::vector< std::unique_ptr< char[]> > named_edges_
Detachedness GetDetachedness() final
EmbedderNode(const HeapObjectHeader *header_address, cppgc::internal::HeapObjectName name, size_t size)
void SetDetachedness(Detachedness detachedness)
const void * GetAddress() override
EmbedderRootNode(const char *name)
~EmbedderRootNode() final=default
GraphBuildingRootVisitor(CppGraphBuilderImpl &graph_builder, const ParentScope &parent_scope)
void VisitRoot(const void *, cppgc::TraceDescriptor desc, const cppgc::SourceLocation &loc) final
void set_edge_name(std::string edge_name)
CppGraphBuilderImpl & graph_builder_
void Visit(const TracedReferenceBase &ref) final
void Visit(const void *, cppgc::TraceDescriptor desc) final
GraphBuildingVisitor(CppGraphBuilderImpl &graph_builder, const ParentScope &parent_scope)
void VisitWeakContainer(const void *object, cppgc::TraceDescriptor strong_desc, cppgc::TraceDescriptor weak_desc, cppgc::WeakCallback, const void *) final
HeapProfiler * heap_profiler() const
Definition heap.h:366
V8_INLINE void * GetCppHeapWrappable(IsolateForPointerCompression isolate) const
bool VisitHeapObjectHeader(HeapObjectHeader &header)
LiveObjectsForVisibilityIterator(CppGraphBuilderImpl &graph_builder)
RootState & ParentAsRootState() const
ParentScope(StateBase &parent)
State & ParentAsRegularState() const
~RootState() final=default
RootState(EmbedderRootNode *node, size_t state_count)
StateBase * FollowDependencies()
void set_node(EmbedderNode *node)
virtual ~StateBase()=default
EmbedderNode * get_node()
StateBase * visibility_dependency_
StateBase(const void *key, size_t state_count, Visibility visibility, EmbedderNode *node, bool visited)
std::unordered_map< const void *, std::unique_ptr< StateBase > > states_
RootState & CreateRootState(EmbedderRootNode *root_node)
bool StateExists(const void *key) const
State & GetOrCreateState(const HeapObjectHeader &header)
void ForAllStates(Callback callback)
State & GetExistingState(const HeapObjectHeader &header) const
StateBase & GetExistingState(const void *key) const
void AddEagerEphemeronEdge(const void *value, cppgc::TraceCallback callback)
bool IsWeakContainer() const
void RecordEphemeronKey(const HeapObjectHeader &key)
std::unordered_set< const HeapObjectHeader * > ephemeron_edges_
bool WasVisitedFromStack() const
~State() final=default
std::unordered_map< const void *, cppgc::TraceCallback > eager_ephemeron_edges_
void ForAllEagerEphemeronEdges(Callback callback)
State(const HeapObjectHeader &header, size_t state_count)
const HeapObjectHeader * header() const
void ForAllEphemeronEdges(Callback callback)
void ForAllEphemeronKeys(Callback callback)
void MarkDependentVisibility(StateBase *dependency)
std::unordered_set< const HeapObjectHeader * > ephemeron_keys_
void AddEphemeronEdge(const HeapObjectHeader &value)
void Visit(const TracedReferenceBase &ref) final
void Visit(const void *, cppgc::TraceDescriptor desc) final
const ParentScope & parent_scope_
VisiblityVisitor(CppGraphBuilderImpl &graph_builder, const ParentScope &parent_scope)
WeakContainerScope(WeakVisitor &weak_visitor, const HeapObjectHeader &weak_container_header)
const HeapObjectHeader * prev_weak_container_header_
void VisitEphemeron(const void *key, const void *value, cppgc::TraceDescriptor value_desc) final
void VisitWeakContainer(const void *object, cppgc::TraceDescriptor strong_desc, cppgc::TraceDescriptor weak_desc, cppgc::WeakCallback, const void *) final
CppGraphBuilderImpl & graph_builder_
WeakVisitor(CppGraphBuilderImpl &graph_builder)
Register const value_
GraphBuildingRootVisitor & root_visitor_
CppGraphBuilderImpl & graph_builder_
LineAndColumn current
TNode< Object > callback
Node * node
void(*)(const LivenessBroker &, const void *) WeakCallback
Definition visitor.h:37
void(*)(Visitor *visitor, const void *object) TraceCallback
Definition trace-trait.h:38
void * ExtractEmbedderDataBackref(Isolate *isolate, CppHeap &cpp_heap, v8::Local< v8::Data > v8_value)
constexpr HeapObjectHeader * kNoNativeAddress
too high values may cause the compiler to set high thresholds for inlining to as much as possible avoid inlined allocation of objects that cannot escape trace load stores from virtual maglev objects use TurboFan fast string builder analyze liveness of environment slots and zap dead values trace TurboFan load elimination emit data about basic block usage in builtins to this enable builtin reordering when run mksnapshot flag for emit warnings when applying builtin profile data verify register allocation in TurboFan randomly schedule instructions to stress dependency tracking enable store store elimination in TurboFan rewrite far to near simulate GC compiler thread race related to allow float parameters to be passed in simulator mode JS Wasm Run additional turbo_optimize_inlined_js_wasm_wrappers enable experimental feedback collection in generic lowering enable Turboshaft s WasmLoadElimination enable Turboshaft s low level load elimination for JS enable Turboshaft s escape analysis for string concatenation use enable Turbolev features that we want to ship in the not too far future trace individual Turboshaft reduction steps trace intermediate Turboshaft reduction steps invocation count threshold for early optimization Enables optimizations which favor memory size over execution speed Enables sampling allocation profiler with X as a sample interval min size of a semi the new space consists of two semi spaces max size of the Collect garbage after Collect garbage after keeps maps alive for< n > old space garbage collections print one detailed trace line in name
Definition flags.cc:2086
return value
Definition map-inl.h:893
bool IsJSApiWrapperObject(Tagged< Map > map)
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
constexpr CppHeapPointerTagRange kAnyCppHeapPointer(CppHeapPointerTag::kFirstTag, CppHeapPointerTag::kLastTag)
BytecodeSequenceNode * parent_
base::uc32 current_
#define DCHECK_NULL(val)
Definition logging.h:491
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define CHECK_NULL(val)
#define CHECK_NOT_NULL(val)
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define CHECK_NE(lhs, rhs)
#define CHECK_EQ(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define USE(...)
Definition macros.h:293
TraceCallback callback
Definition trace-trait.h:53
std::unique_ptr< ValueMirror > key
TFGraph * graph_