diff --git a/BUILD.gn b/BUILD.gn index 0821e7e44a..3ceaee954d 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -342,6 +342,9 @@ declare_args() { # When it's disabled, the --turbo-allocation-folding runtime flag will be ignored. v8_enable_allocation_folding = true + # Enable runtime verification of heap snapshots produced for devtools. + v8_enable_heap_snapshot_verify = "" + # Enable global allocation site tracking. v8_allocation_site_tracking = true @@ -385,6 +388,10 @@ if (v8_enable_test_features == "") { if (v8_enable_v8_checks == "") { v8_enable_v8_checks = v8_enable_debugging_features } +if (v8_enable_heap_snapshot_verify == "") { + v8_enable_heap_snapshot_verify = + v8_enable_debugging_features || dcheck_always_on +} if (v8_enable_snapshot_code_comments) { assert(v8_code_comments == true || v8_code_comments == "", "v8_enable_snapshot_code_comments conflicts with v8_code_comments.") @@ -891,6 +898,9 @@ config("features") { if (v8_enable_debug_code) { defines += [ "V8_ENABLE_DEBUG_CODE" ] } + if (v8_enable_heap_snapshot_verify) { + defines += [ "V8_ENABLE_HEAP_SNAPSHOT_VERIFY" ] + } if (v8_enable_snapshot_native_code_counters) { defines += [ "V8_SNAPSHOT_NATIVE_CODE_COUNTERS" ] } @@ -3572,6 +3582,10 @@ v8_header_set("v8_internal_headers") { ] } + if (v8_enable_heap_snapshot_verify) { + sources += [ "src/heap/reference-summarizer.h" ] + } + if (v8_current_cpu == "x86") { sources += [ ### gcmole(arch:ia32) ### "src/baseline/ia32/baseline-assembler-ia32-inl.h", @@ -4513,6 +4527,10 @@ v8_source_set("v8_base_without_compiler") { ] } + if (v8_enable_heap_snapshot_verify) { + sources += [ "src/heap/reference-summarizer.cc" ] + } + if (v8_current_cpu == "x86") { sources += [ ### gcmole(arch:ia32) ### "src/codegen/ia32/assembler-ia32.cc", diff --git a/src/DEPS b/src/DEPS index 341435e28d..33c4430ef4 100644 --- a/src/DEPS +++ b/src/DEPS @@ -36,6 +36,7 @@ include_rules = [ "+src/heap/parked-scope.h", "+src/heap/read-only-heap-inl.h", "+src/heap/read-only-heap.h", + "+src/heap/reference-summarizer.h", "+src/heap/safepoint.h", "+src/heap/base/stack.h", "+src/heap/conservative-stack-visitor.h", diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h index 81d8a3cfa9..b2f7736158 100644 --- a/src/flags/flag-definitions.h +++ b/src/flags/flag-definitions.h @@ -1618,6 +1618,11 @@ DEFINE_INT(heap_snapshot_string_limit, 1024, "truncate strings to this length in the heap snapshot") DEFINE_BOOL(heap_profiler_show_hidden_objects, false, "use 'native' rather than 'hidden' node type in snapshot") +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY +DEFINE_BOOL(heap_snapshot_verify, false, + "verify that heap snapshot matches marking visitor behavior") +DEFINE_IMPLICATION(enable_slow_asserts, heap_snapshot_verify) +#endif // sampling-heap-profiler.cc DEFINE_BOOL(sampling_heap_profiler_suppress_randomness, false, diff --git a/src/heap/marking-visitor-inl.h b/src/heap/marking-visitor-inl.h index f249d2dbc3..1d21449d43 100644 --- a/src/heap/marking-visitor-inl.h +++ b/src/heap/marking-visitor-inl.h @@ -26,6 +26,7 @@ void MarkingVisitorBase::MarkObject( HeapObject host, HeapObject object) { DCHECK(ReadOnlyHeap::Contains(object) || heap_->Contains(object)); concrete_visitor()->SynchronizePageAccess(object); + AddStrongReferenceForReferenceSummarizer(host, object); if (concrete_visitor()->marking_state()->WhiteToGrey(object)) { local_marking_worklists_->Push(object); if (V8_UNLIKELY(concrete_visitor()->retaining_path_mode() == @@ -65,6 +66,7 @@ void MarkingVisitorBase::ProcessWeakHeapObject( // the reference when we know the liveness of the whole transitive // closure. local_weak_objects_->weak_references_local.Push(std::make_pair(host, slot)); + AddWeakReferenceForReferenceSummarizer(host, heap_object); } } @@ -117,6 +119,7 @@ void MarkingVisitorBase::VisitEmbeddedPointer( if (host.IsWeakObject(object)) { local_weak_objects_->weak_objects_in_code_local.Push( std::make_pair(object, host)); + AddWeakReferenceForReferenceSummarizer(host, object); } else { MarkObject(host, object); } @@ -244,7 +247,7 @@ int MarkingVisitorBase::VisitFixedArray( // in the large object space. ProgressBar& progress_bar = MemoryChunk::FromHeapObject(object)->ProgressBar(); - return progress_bar.IsEnabled() + return CanUpdateValuesInHeap() && progress_bar.IsEnabled() ? VisitFixedArrayWithProgressBar(map, object, progress_bar) : concrete_visitor()->VisitLeftTrimmableArray(map, object); } @@ -344,6 +347,7 @@ int MarkingVisitorBase::VisitEphemeronHashTable( concrete_visitor()->SynchronizePageAccess(key); concrete_visitor()->RecordSlot(table, key_slot, key); + AddWeakReferenceForReferenceSummarizer(table, key); ObjectSlot value_slot = table.RawFieldOfElementAt(EphemeronHashTable::EntryToValueIndex(i)); @@ -357,6 +361,7 @@ int MarkingVisitorBase::VisitEphemeronHashTable( HeapObject value = HeapObject::cast(value_obj); concrete_visitor()->SynchronizePageAccess(value); concrete_visitor()->RecordSlot(table, value_slot, value); + AddWeakReferenceForReferenceSummarizer(table, value); // Revisit ephemerons with both key and value unreachable at end // of concurrent marking cycle. @@ -387,6 +392,7 @@ int MarkingVisitorBase::VisitJSWeakRef( // JSWeakRef points to a potentially dead object. We have to process // them when we know the liveness of the whole transitive closure. local_weak_objects_->js_weak_refs_local.Push(weak_ref); + AddWeakReferenceForReferenceSummarizer(weak_ref, target); } } return size; @@ -417,6 +423,8 @@ int MarkingVisitorBase::VisitWeakCell( // token. We have to process them when we know the liveness of the whole // transitive closure. local_weak_objects_->weak_cells_local.Push(weak_cell); + AddWeakReferenceForReferenceSummarizer(weak_cell, target); + AddWeakReferenceForReferenceSummarizer(weak_cell, unregister_token); } return size; } @@ -443,8 +451,11 @@ template void MarkingVisitorBase::VisitDescriptors( DescriptorArray descriptor_array, int number_of_own_descriptors) { int16_t new_marked = static_cast(number_of_own_descriptors); - int16_t old_marked = descriptor_array.UpdateNumberOfMarkedDescriptors( - mark_compact_epoch_, new_marked); + int16_t old_marked = 0; + if (CanUpdateValuesInHeap()) { + old_marked = descriptor_array.UpdateNumberOfMarkedDescriptors( + mark_compact_epoch_, new_marked); + } if (old_marked < new_marked) { VisitPointers( descriptor_array, diff --git a/src/heap/marking-visitor.h b/src/heap/marking-visitor.h index dd02b19448..4cc11a528b 100644 --- a/src/heap/marking-visitor.h +++ b/src/heap/marking-visitor.h @@ -25,6 +25,11 @@ struct EphemeronMarking { template class MarkingStateBase { public: + // Declares that this marking state is not collecting retainers, so the + // marking visitor may update the heap state to store information about + // progress, and may avoid fully visiting an object if it is safe to do so. + static constexpr bool kCollectRetainers = false; + explicit MarkingStateBase(PtrComprCageBase cage_base) #if V8_COMPRESS_POINTERS : cage_base_(cage_base) @@ -102,6 +107,15 @@ class MarkingStateBase { static_cast(this)->SetLiveBytes(chunk, 0); } + void AddStrongReferenceForReferenceSummarizer(HeapObject host, + HeapObject obj) { + // This is not a reference summarizer, so there is nothing to do here. + } + + void AddWeakReferenceForReferenceSummarizer(HeapObject host, HeapObject obj) { + // This is not a reference summarizer, so there is nothing to do here. + } + private: #if V8_COMPRESS_POINTERS const PtrComprCageBase cage_base_; @@ -258,6 +272,23 @@ class MarkingVisitorBase : public HeapVisitor { // Marks the object grey and pushes it on the marking work list. V8_INLINE void MarkObject(HeapObject host, HeapObject obj); + V8_INLINE void AddStrongReferenceForReferenceSummarizer(HeapObject host, + HeapObject obj) { + concrete_visitor() + ->marking_state() + ->AddStrongReferenceForReferenceSummarizer(host, obj); + } + + V8_INLINE void AddWeakReferenceForReferenceSummarizer(HeapObject host, + HeapObject obj) { + concrete_visitor()->marking_state()->AddWeakReferenceForReferenceSummarizer( + host, obj); + } + + constexpr bool CanUpdateValuesInHeap() { + return !MarkingState::kCollectRetainers; + } + MarkingWorklists::Local* const local_marking_worklists_; WeakObjects::Local* const local_weak_objects_; Heap* const heap_; diff --git a/src/heap/reference-summarizer.cc b/src/heap/reference-summarizer.cc new file mode 100644 index 0000000000..fd2668e140 --- /dev/null +++ b/src/heap/reference-summarizer.cc @@ -0,0 +1,116 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/heap/reference-summarizer.h" + +#include "src/heap/mark-compact-inl.h" +#include "src/heap/marking-visitor-inl.h" +#include "src/objects/embedder-data-array-inl.h" +#include "src/objects/js-array-buffer-inl.h" + +namespace v8 { +namespace internal { + +namespace { + +// A class which acts as a MarkingState but does not actually update any marking +// bits. It reports all objects as white and all transitions as successful. It +// also tracks which objects are retained by the primary object according to the +// marking visitor. +class ReferenceSummarizerMarkingState final { + public: + // Declares that this marking state is collecting retainers, so the marking + // visitor must fully visit each object and can't update on-heap state. + static constexpr bool kCollectRetainers = true; + + explicit ReferenceSummarizerMarkingState(HeapObject object) + : primary_object_(object), + local_marking_worklists_(&marking_worklists_), + local_weak_objects_(&weak_objects_) {} + + ~ReferenceSummarizerMarkingState() { + // Clean up temporary state. + local_weak_objects_.Publish(); + weak_objects_.Clear(); + local_marking_worklists_.Publish(); + marking_worklists_.Clear(); + } + + // Retrieves the references that were collected by this marker. This operation + // transfers ownership of the set, so calling it again would yield an empty + // result. + ReferenceSummary DestructivelyRetrieveReferences() { + ReferenceSummary tmp = std::move(references_); + references_.Clear(); + return tmp; + } + + // Standard marking visitor functions: + + bool IsWhite(HeapObject obj) const { return true; } + + bool IsBlackOrGrey(HeapObject obj) const { return false; } + + bool WhiteToGrey(HeapObject obj) { return true; } + + bool GreyToBlack(HeapObject obj) { return true; } + + // Adds a retaining relationship found by the marking visitor. + void AddStrongReferenceForReferenceSummarizer(HeapObject host, + HeapObject obj) { + AddReference(host, obj, references_.strong_references()); + } + + // Adds a non-retaining weak reference found by the marking visitor. The value + // in an ephemeron hash table entry is also included here, since it is not + // known to be strong without further information about the key. + void AddWeakReferenceForReferenceSummarizer(HeapObject host, HeapObject obj) { + AddReference(host, obj, references_.weak_references()); + } + + // Other member functions, not part of the marking visitor contract: + + MarkingWorklists::Local* local_marking_worklists() { + return &local_marking_worklists_; + } + WeakObjects::Local* local_weak_objects() { return &local_weak_objects_; } + + private: + void AddReference( + HeapObject host, HeapObject obj, + std::unordered_set& references) { + // It's possible that the marking visitor handles multiple objects at once, + // such as a Map and its DescriptorArray, but we're only interested in + // references from the primary object. + if (host == primary_object_) { + references.insert(obj); + } + } + + ReferenceSummary references_; + HeapObject primary_object_; + MarkingWorklists marking_worklists_; + MarkingWorklists::Local local_marking_worklists_; + WeakObjects weak_objects_; + WeakObjects::Local local_weak_objects_; +}; + +} // namespace + +ReferenceSummary ReferenceSummary::SummarizeReferencesFrom(Heap* heap, + HeapObject obj) { + ReferenceSummarizerMarkingState marking_state(obj); + + MainMarkingVisitor visitor( + &marking_state, marking_state.local_marking_worklists(), + marking_state.local_weak_objects(), heap, 0 /*mark_compact_epoch*/, + {} /*code_flush_mode*/, false /*embedder_tracing_enabled*/, + true /*should_keep_ages_unchanged*/); + visitor.Visit(obj.map(heap->isolate()), obj); + + return marking_state.DestructivelyRetrieveReferences(); +} + +} // namespace internal +} // namespace v8 diff --git a/src/heap/reference-summarizer.h b/src/heap/reference-summarizer.h new file mode 100644 index 0000000000..a49ac597a1 --- /dev/null +++ b/src/heap/reference-summarizer.h @@ -0,0 +1,55 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_HEAP_REFERENCE_SUMMARIZER_H_ +#define V8_HEAP_REFERENCE_SUMMARIZER_H_ + +#include + +#include "src/objects/heap-object.h" + +namespace v8 { +namespace internal { + +class Heap; + +class ReferenceSummary { + public: + ReferenceSummary() = default; + ReferenceSummary(ReferenceSummary&& other) V8_NOEXCEPT + : strong_references_(std::move(other.strong_references_)), + weak_references_(std::move(other.weak_references_)) {} + + // Produces a set of objects referred to by the object. This function uses a + // realistic marking visitor, so its results are likely to match real GC + // behavior. Intended only for verification. + static ReferenceSummary SummarizeReferencesFrom(Heap* heap, HeapObject obj); + + // All objects which the chosen object has strong pointers to. + std::unordered_set& strong_references() { + return strong_references_; + } + + // All objects which the chosen object has weak pointers to. The values in + // ephemeron hash tables are also included here, even though they aren't + // normal weak pointers. + std::unordered_set& weak_references() { + return weak_references_; + } + + void Clear() { + strong_references_.clear(); + weak_references_.clear(); + } + + private: + std::unordered_set strong_references_; + std::unordered_set weak_references_; + DISALLOW_GARBAGE_COLLECTION(no_gc) +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_HEAP_REFERENCE_SUMMARIZER_H_ diff --git a/src/profiler/heap-snapshot-generator.cc b/src/profiler/heap-snapshot-generator.cc index c8abf12e8b..aaa70068f0 100644 --- a/src/profiler/heap-snapshot-generator.cc +++ b/src/profiler/heap-snapshot-generator.cc @@ -42,6 +42,152 @@ namespace v8 { namespace internal { +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY +class HeapEntryVerifier { + public: + HeapEntryVerifier(HeapSnapshotGenerator* generator, HeapObject obj) + : generator_(generator), + primary_object_(obj), + reference_summary_( + ReferenceSummary::SummarizeReferencesFrom(generator->heap(), obj)) { + generator->set_verifier(this); + } + ~HeapEntryVerifier() { + CheckAllReferencesWereChecked(); + generator_->set_verifier(nullptr); + } + + // Checks that `host` retains `target`, according to the marking visitor. This + // allows us to verify, when adding edges to the snapshot, that they + // correspond to real retaining relationships. + void CheckStrongReference(HeapObject host, HeapObject target) { + // All references should be from the current primary object. + CHECK_EQ(host, primary_object_); + + checked_objects_.insert(target); + + // Check whether there is a direct strong reference from host to target. + if (reference_summary_.strong_references().find(target) != + reference_summary_.strong_references().end()) { + return; + } + + // There is no direct reference from host to target, but sometimes heap + // snapshots include references that skip one, two, or three objects, such + // as __proto__ on a JSObject referring to its Map's prototype, or a + // property getter that bypasses the property array and accessor info. At + // this point, we must check for those indirect references. + for (size_t level = 0; level < 3; ++level) { + const std::unordered_set& indirect = + GetIndirectStrongReferences(level); + if (indirect.find(target) != indirect.end()) { + return; + } + } + + FATAL("Could not find any matching reference"); + } + + // Checks that `host` has a weak reference to `target`, according to the + // marking visitor. + void CheckWeakReference(HeapObject host, HeapObject target) { + // All references should be from the current primary object. + CHECK_EQ(host, primary_object_); + + checked_objects_.insert(target); + CHECK_NE(reference_summary_.weak_references().find(target), + reference_summary_.weak_references().end()); + } + + // Marks the relationship between `host` and `target` as checked, even if the + // marking visitor found no such relationship. This is necessary for + // ephemerons, where a pair of objects is required to retain the target. + // Use this function with care, since it bypasses verification. + void MarkReferenceCheckedWithoutChecking(HeapObject host, HeapObject target) { + if (host == primary_object_) { + checked_objects_.insert(target); + } + } + + // Verifies that all of the references found by the marking visitor were + // checked via a call to CheckStrongReference or CheckWeakReference, or + // deliberately skipped via a call to MarkReferenceCheckedWithoutChecking. + // This ensures that there aren't retaining relationships found by the marking + // visitor which were omitted from the heap snapshot. + void CheckAllReferencesWereChecked() { + // Both loops below skip pointers to read-only objects, because the heap + // snapshot deliberately omits many of those (see IsEssentialObject). + // Read-only objects can't ever retain normal read-write objects, so these + // are fine to skip. + for (HeapObject obj : reference_summary_.strong_references()) { + if (!BasicMemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) { + CHECK_NE(checked_objects_.find(obj), checked_objects_.end()); + } + } + for (HeapObject obj : reference_summary_.weak_references()) { + if (!BasicMemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) { + CHECK_NE(checked_objects_.find(obj), checked_objects_.end()); + } + } + } + + private: + const std::unordered_set& + GetIndirectStrongReferences(size_t level) { + CHECK_GE(indirect_strong_references_.size(), level); + + if (indirect_strong_references_.size() == level) { + // Expansion is needed. + indirect_strong_references_.resize(level + 1); + const std::unordered_set& previous = + level == 0 ? reference_summary_.strong_references() + : indirect_strong_references_[level - 1]; + for (HeapObject obj : previous) { + if (BasicMemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) { + // Marking visitors don't expect to visit objects in read-only space, + // and will fail DCHECKs if they are used on those objects. Read-only + // objects can never retain anything outside read-only space, so + // skipping those objects doesn't weaken verification. + continue; + } + + // Indirect references should only bypass internal structures, not + // user-visible objects or contexts. + if (obj.IsJSReceiver() || obj.IsString() || obj.IsContext()) { + continue; + } + + ReferenceSummary summary = + ReferenceSummary::SummarizeReferencesFrom(generator_->heap(), obj); + indirect_strong_references_[level].insert( + summary.strong_references().begin(), + summary.strong_references().end()); + } + } + + return indirect_strong_references_[level]; + } + + DISALLOW_GARBAGE_COLLECTION(no_gc) + HeapSnapshotGenerator* generator_; + HeapObject primary_object_; + + // All objects referred to by primary_object_, according to a marking visitor. + ReferenceSummary reference_summary_; + + // Objects that have been checked via a call to CheckStrongReference or + // CheckWeakReference, or deliberately skipped via a call to + // MarkReferenceCheckedWithoutChecking. + std::unordered_set checked_objects_; + + // Objects transitively retained by the primary object. The objects in the set + // at index i are retained by the primary object via a chain of i+1 + // intermediate objects. + std::vector> + indirect_strong_references_; +}; +#endif + HeapGraphEdge::HeapGraphEdge(Type type, const char* name, HeapEntry* from, HeapEntry* to) : bit_field_(TypeField::encode(type) | @@ -78,29 +224,84 @@ HeapEntry::HeapEntry(HeapSnapshot* snapshot, int index, Type type, DCHECK_GE(index, 0); } -void HeapEntry::SetNamedReference(HeapGraphEdge::Type type, - const char* name, - HeapEntry* entry) { - ++children_count_; - snapshot_->edges().emplace_back(type, name, this, entry); +void HeapEntry::VerifyReference(HeapGraphEdge::Type type, HeapEntry* entry, + HeapSnapshotGenerator* generator, + ReferenceVerification verification) { +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY + if (verification == kOffHeapPointer || generator->verifier() == nullptr) { + // Off-heap pointers are outside the scope of this verification; we just + // trust the embedder to provide accurate data. If the verifier is null, + // then verification is disabled. + return; + } + if (verification == kCustomWeakPointer) { + // The caller declared that this is a weak pointer ignored by the marking + // visitor. All we can verify at this point is that the edge type declares + // it to be weak. + CHECK_EQ(type, HeapGraphEdge::kWeak); + return; + } + Address from_address = + reinterpret_cast
(generator->FindHeapThingForHeapEntry(this)); + Address to_address = + reinterpret_cast
(generator->FindHeapThingForHeapEntry(entry)); + if (from_address == kNullAddress || to_address == kNullAddress) { + // One of these entries doesn't correspond to a real heap object. + // Verification is not possible. + return; + } + HeapObject from_obj = HeapObject::cast(Object(from_address)); + HeapObject to_obj = HeapObject::cast(Object(to_address)); + if (BasicMemoryChunk::FromHeapObject(to_obj)->InReadOnlySpace()) { + // We can't verify pointers into read-only space, because marking visitors + // might not mark those. For example, every Map has a pointer to the + // MetaMap, but marking visitors don't bother with following that link. + // Read-only objects are immortal and can never point to things outside of + // read-only space, so ignoring these objects is safe from the perspective + // of ensuring accurate retaining paths for normal read-write objects. + // Therefore, do nothing. + } else if (verification == kEphemeron) { + // Ephemerons can't be verified because they aren't marked directly by the + // marking visitor. + generator->verifier()->MarkReferenceCheckedWithoutChecking(from_obj, + to_obj); + } else if (type == HeapGraphEdge::kWeak) { + generator->verifier()->CheckWeakReference(from_obj, to_obj); + } else { + generator->verifier()->CheckStrongReference(from_obj, to_obj); + } +#endif } -void HeapEntry::SetIndexedReference(HeapGraphEdge::Type type, - int index, - HeapEntry* entry) { +void HeapEntry::SetNamedReference(HeapGraphEdge::Type type, const char* name, + HeapEntry* entry, + HeapSnapshotGenerator* generator, + ReferenceVerification verification) { + ++children_count_; + snapshot_->edges().emplace_back(type, name, this, entry); + VerifyReference(type, entry, generator, verification); +} + +void HeapEntry::SetIndexedReference(HeapGraphEdge::Type type, int index, + HeapEntry* entry, + HeapSnapshotGenerator* generator, + ReferenceVerification verification) { ++children_count_; snapshot_->edges().emplace_back(type, index, this, entry); + VerifyReference(type, entry, generator, verification); } void HeapEntry::SetNamedAutoIndexReference(HeapGraphEdge::Type type, const char* description, HeapEntry* child, - StringsStorage* names) { + StringsStorage* names, + HeapSnapshotGenerator* generator, + ReferenceVerification verification) { int index = children_count_ + 1; const char* name = description ? names->GetFormatted("%d / %s", index, description) : names->GetName(index); - SetNamedReference(type, name, child); + SetNamedReference(type, name, child, generator, verification); } void HeapEntry::Print(const char* prefix, const char* edge_name, int max_depth, @@ -1019,9 +1220,11 @@ void V8HeapExplorer::ExtractEphemeronHashTableReferences( key_entry->name(), key_entry->id(), value_entry->name(), value_entry->id(), table_entry->id()); key_entry->SetNamedAutoIndexReference(HeapGraphEdge::kInternal, edge_name, - value_entry, names_); - table_entry->SetNamedAutoIndexReference(HeapGraphEdge::kInternal, - edge_name, value_entry, names_); + value_entry, names_, generator_, + HeapEntry::kEphemeron); + table_entry->SetNamedAutoIndexReference( + HeapGraphEdge::kInternal, edge_name, value_entry, names_, generator_, + HeapEntry::kEphemeron); } } } @@ -1082,11 +1285,12 @@ void V8HeapExplorer::ExtractContextReferences(HeapEntry* entry, SetWeakReference(entry, "optimized_code_list", context.get(Context::OPTIMIZED_CODE_LIST), - Context::OffsetOfElementAt(Context::OPTIMIZED_CODE_LIST)); - SetWeakReference( - entry, "deoptimized_code_list", - context.get(Context::DEOPTIMIZED_CODE_LIST), - Context::OffsetOfElementAt(Context::DEOPTIMIZED_CODE_LIST)); + Context::OffsetOfElementAt(Context::OPTIMIZED_CODE_LIST), + HeapEntry::kCustomWeakPointer); + SetWeakReference(entry, "deoptimized_code_list", + context.get(Context::DEOPTIMIZED_CODE_LIST), + Context::OffsetOfElementAt(Context::DEOPTIMIZED_CODE_LIST), + HeapEntry::kCustomWeakPointer); STATIC_ASSERT(Context::OPTIMIZED_CODE_LIST == Context::FIRST_WEAK_SLOT); STATIC_ASSERT(Context::NEXT_CONTEXT_LINK + 1 == Context::NATIVE_CONTEXT_SLOTS); @@ -1334,7 +1538,7 @@ void V8HeapExplorer::ExtractJSArrayBufferReferences(HeapEntry* entry, HeapEntry* data_entry = generator_->FindOrAddEntry(buffer.backing_store(), &allocator); entry->SetNamedReference(HeapGraphEdge::kInternal, "backing_store", - data_entry); + data_entry, generator_, HeapEntry::kOffHeapPointer); } void V8HeapExplorer::ExtractJSPromiseReferences(HeapEntry* entry, @@ -1386,7 +1590,8 @@ void V8HeapExplorer::ExtractNumberReference(HeapEntry* entry, Object number) { SnapshotObjectId id = heap_object_map_->get_next_id(); HeapEntry* child_entry = snapshot_->AddEntry(HeapEntry::kString, name, id, 0, 0); - entry->SetNamedReference(HeapGraphEdge::kInternal, "value", child_entry); + entry->SetNamedReference(HeapGraphEdge::kInternal, "value", child_entry, + generator_); } void V8HeapExplorer::ExtractFeedbackVectorReferences( @@ -1694,6 +1899,18 @@ bool V8HeapExplorer::IterateAndExtractReferences( visited_fields_.resize(max_pointer, false); } +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY + std::unique_ptr verifier; + // MarkingVisitorBase doesn't expect that we will ever visit read-only + // objects, and fails DCHECKs if we attempt to. Read-only objects can + // never retain read-write objects, so there is no risk in skipping + // verification for them. + if (FLAG_heap_snapshot_verify && + !BasicMemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) { + verifier = std::make_unique(generator, obj); + } +#endif + HeapEntry* entry = GetEntry(obj); ExtractReferences(entry, obj); SetInternalReference(entry, "map", obj.map(cage_base), @@ -1757,7 +1974,8 @@ void V8HeapExplorer::SetContextReference(HeapEntry* parent_entry, HeapEntry* child_entry = GetEntry(child_obj); if (child_entry == nullptr) return; parent_entry->SetNamedReference(HeapGraphEdge::kContextVariable, - names_->GetName(reference_name), child_entry); + names_->GetName(reference_name), child_entry, + generator_); MarkVisitedField(field_offset); } @@ -1774,15 +1992,15 @@ void V8HeapExplorer::SetNativeBindReference(HeapEntry* parent_entry, HeapEntry* child_entry = GetEntry(child_obj); if (child_entry == nullptr) return; parent_entry->SetNamedReference(HeapGraphEdge::kShortcut, reference_name, - child_entry); + child_entry, generator_); } void V8HeapExplorer::SetElementReference(HeapEntry* parent_entry, int index, Object child_obj) { HeapEntry* child_entry = GetEntry(child_obj); if (child_entry == nullptr) return; - parent_entry->SetIndexedReference(HeapGraphEdge::kElement, index, - child_entry); + parent_entry->SetIndexedReference(HeapGraphEdge::kElement, index, child_entry, + generator_); } void V8HeapExplorer::SetInternalReference(HeapEntry* parent_entry, @@ -1794,7 +2012,7 @@ void V8HeapExplorer::SetInternalReference(HeapEntry* parent_entry, HeapEntry* child_entry = GetEntry(child_obj); DCHECK_NOT_NULL(child_entry); parent_entry->SetNamedReference(HeapGraphEdge::kInternal, reference_name, - child_entry); + child_entry, generator_); MarkVisitedField(field_offset); } @@ -1806,7 +2024,8 @@ void V8HeapExplorer::SetInternalReference(HeapEntry* parent_entry, int index, HeapEntry* child_entry = GetEntry(child_obj); DCHECK_NOT_NULL(child_entry); parent_entry->SetNamedReference(HeapGraphEdge::kInternal, - names_->GetName(index), child_entry); + names_->GetName(index), child_entry, + generator_); MarkVisitedField(field_offset); } @@ -1822,20 +2041,20 @@ void V8HeapExplorer::SetHiddenReference(HeapObject parent_obj, DCHECK_NOT_NULL(child_entry); if (IsEssentialHiddenReference(parent_obj, field_offset)) { parent_entry->SetIndexedReference(HeapGraphEdge::kHidden, index, - child_entry); + child_entry, generator_); } } -void V8HeapExplorer::SetWeakReference(HeapEntry* parent_entry, - const char* reference_name, - Object child_obj, int field_offset) { +void V8HeapExplorer::SetWeakReference( + HeapEntry* parent_entry, const char* reference_name, Object child_obj, + int field_offset, HeapEntry::ReferenceVerification verification) { if (!IsEssentialObject(child_obj)) { return; } HeapEntry* child_entry = GetEntry(child_obj); DCHECK_NOT_NULL(child_entry); parent_entry->SetNamedReference(HeapGraphEdge::kWeak, reference_name, - child_entry); + child_entry, generator_, verification); MarkVisitedField(field_offset); } @@ -1847,8 +2066,9 @@ void V8HeapExplorer::SetWeakReference(HeapEntry* parent_entry, int index, } HeapEntry* child_entry = GetEntry(child_obj); DCHECK_NOT_NULL(child_entry); - parent_entry->SetNamedReference( - HeapGraphEdge::kWeak, names_->GetFormatted("%d", index), child_entry); + parent_entry->SetNamedReference(HeapGraphEdge::kWeak, + names_->GetFormatted("%d", index), + child_entry, generator_); if (field_offset.has_value()) { MarkVisitedField(*field_offset); } @@ -1885,25 +2105,25 @@ void V8HeapExplorer::SetPropertyReference(HeapEntry* parent_entry, .get()) : names_->GetName(reference_name); - parent_entry->SetNamedReference(type, name, child_entry); + parent_entry->SetNamedReference(type, name, child_entry, generator_); MarkVisitedField(field_offset); } void V8HeapExplorer::SetRootGcRootsReference() { - snapshot_->root()->SetIndexedAutoIndexReference(HeapGraphEdge::kElement, - snapshot_->gc_roots()); + snapshot_->root()->SetIndexedAutoIndexReference( + HeapGraphEdge::kElement, snapshot_->gc_roots(), generator_); } void V8HeapExplorer::SetUserGlobalReference(Object child_obj) { HeapEntry* child_entry = GetEntry(child_obj); DCHECK_NOT_NULL(child_entry); - snapshot_->root()->SetNamedAutoIndexReference(HeapGraphEdge::kShortcut, - nullptr, child_entry, names_); + snapshot_->root()->SetNamedAutoIndexReference( + HeapGraphEdge::kShortcut, nullptr, child_entry, names_, generator_); } void V8HeapExplorer::SetGcRootsReference(Root root) { snapshot_->gc_roots()->SetIndexedAutoIndexReference( - HeapGraphEdge::kElement, snapshot_->gc_subroot(root)); + HeapGraphEdge::kElement, snapshot_->gc_subroot(root), generator_); } void V8HeapExplorer::SetGcSubrootReference(Root root, const char* description, @@ -1921,11 +2141,11 @@ void V8HeapExplorer::SetGcSubrootReference(Root root, const char* description, HeapGraphEdge::Type edge_type = is_weak ? HeapGraphEdge::kWeak : HeapGraphEdge::kInternal; if (name != nullptr) { - snapshot_->gc_subroot(root)->SetNamedReference(edge_type, name, - child_entry); + snapshot_->gc_subroot(root)->SetNamedReference(edge_type, name, child_entry, + generator_); } else { snapshot_->gc_subroot(root)->SetNamedAutoIndexReference( - edge_type, description, child_entry, names_); + edge_type, description, child_entry, names_, generator_); } // For full heap snapshots we do not emit user roots but rather rely on @@ -2222,7 +2442,8 @@ bool NativeObjectsExplorer::IterateAndExtractReferences( if (auto* entry = EntryForEmbedderGraphNode(node.get())) { if (node->IsRootNode()) { snapshot_->root()->SetIndexedAutoIndexReference( - HeapGraphEdge::kElement, entry); + HeapGraphEdge::kElement, entry, generator_, + HeapEntry::kOffHeapPointer); } if (node->WrapperNode()) { MergeNodeIntoEntry(entry, node.get(), node->WrapperNode()); @@ -2238,10 +2459,13 @@ bool NativeObjectsExplorer::IterateAndExtractReferences( HeapEntry* to = EntryForEmbedderGraphNode(edge.to); if (!to) continue; if (edge.name == nullptr) { - from->SetIndexedAutoIndexReference(HeapGraphEdge::kElement, to); + from->SetIndexedAutoIndexReference(HeapGraphEdge::kElement, to, + generator_, + HeapEntry::kOffHeapPointer); } else { from->SetNamedReference(HeapGraphEdge::kInternal, - names_->GetCopy(edge.name), to); + names_->GetCopy(edge.name), to, generator_, + HeapEntry::kOffHeapPointer); } } } @@ -2250,16 +2474,13 @@ bool NativeObjectsExplorer::IterateAndExtractReferences( } HeapSnapshotGenerator::HeapSnapshotGenerator( - HeapSnapshot* snapshot, - v8::ActivityControl* control, - v8::HeapProfiler::ObjectNameResolver* resolver, - Heap* heap) + HeapSnapshot* snapshot, v8::ActivityControl* control, + v8::HeapProfiler::ObjectNameResolver* resolver, Heap* heap) : snapshot_(snapshot), control_(control), v8_heap_explorer_(snapshot_, this, resolver), dom_explorer_(snapshot_, this), - heap_(heap) { -} + heap_(heap) {} namespace { class V8_NODISCARD NullContextForSnapshotScope { diff --git a/src/profiler/heap-snapshot-generator.h b/src/profiler/heap-snapshot-generator.h index 27bc7f324a..dcbfd2811d 100644 --- a/src/profiler/heap-snapshot-generator.h +++ b/src/profiler/heap-snapshot-generator.h @@ -24,6 +24,10 @@ #include "src/profiler/strings-storage.h" #include "src/strings/string-hasher.h" +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY +#include "src/heap/reference-summarizer.h" +#endif + namespace v8 { namespace internal { @@ -141,17 +145,40 @@ class HeapEntry { } uint8_t detachedness() const { return detachedness_; } - void SetIndexedReference( - HeapGraphEdge::Type type, int index, HeapEntry* entry); - void SetNamedReference( - HeapGraphEdge::Type type, const char* name, HeapEntry* entry); - void SetIndexedAutoIndexReference(HeapGraphEdge::Type type, - HeapEntry* child) { - SetIndexedReference(type, children_count_ + 1, child); + enum ReferenceVerification { + // Verify that the reference can be found via marking, if verification is + // enabled. + kVerify, + + // Skip verifying that the reference can be found via marking, for any of + // the following reasons: + + kEphemeron, + kOffHeapPointer, + kCustomWeakPointer, + }; + + void VerifyReference(HeapGraphEdge::Type type, HeapEntry* entry, + HeapSnapshotGenerator* generator, + ReferenceVerification verification); + void SetIndexedReference(HeapGraphEdge::Type type, int index, + HeapEntry* entry, HeapSnapshotGenerator* generator, + ReferenceVerification verification = kVerify); + void SetNamedReference(HeapGraphEdge::Type type, const char* name, + HeapEntry* entry, HeapSnapshotGenerator* generator, + ReferenceVerification verification = kVerify); + void SetIndexedAutoIndexReference( + HeapGraphEdge::Type type, HeapEntry* child, + HeapSnapshotGenerator* generator, + ReferenceVerification verification = kVerify) { + SetIndexedReference(type, children_count_ + 1, child, generator, + verification); } void SetNamedAutoIndexReference(HeapGraphEdge::Type type, const char* description, HeapEntry* child, - StringsStorage* strings); + StringsStorage* strings, + HeapSnapshotGenerator* generator, + ReferenceVerification verification = kVerify); V8_EXPORT_PRIVATE void Print(const char* prefix, const char* edge_name, int max_depth, int indent) const; @@ -438,8 +465,10 @@ class V8_EXPORT_PRIVATE V8HeapExplorer : public HeapEntriesAllocator { int field_offset = -1); void SetHiddenReference(HeapObject parent_obj, HeapEntry* parent_entry, int index, Object child, int field_offset); - void SetWeakReference(HeapEntry* parent_entry, const char* reference_name, - Object child_obj, int field_offset); + void SetWeakReference( + HeapEntry* parent_entry, const char* reference_name, Object child_obj, + int field_offset, + HeapEntry::ReferenceVerification verification = HeapEntry::kVerify); void SetWeakReference(HeapEntry* parent_entry, int index, Object child_obj, base::Optional field_offset); void SetPropertyReference(HeapEntry* parent_entry, Name reference_name, @@ -511,6 +540,8 @@ class NativeObjectsExplorer { friend class GlobalHandlesExtractor; }; +class HeapEntryVerifier; + class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface { public: // The HeapEntriesMap instance is used to track a mapping between @@ -539,10 +570,33 @@ class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface { } HeapEntry* AddEntry(HeapThing ptr, HeapEntriesAllocator* allocator) { - return entries_map_.emplace(ptr, allocator->AllocateEntry(ptr)) - .first->second; + HeapEntry* result = + entries_map_.emplace(ptr, allocator->AllocateEntry(ptr)).first->second; +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY + if (FLAG_heap_snapshot_verify) { + reverse_entries_map_.emplace(result, ptr); + } +#endif + return result; } +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY + HeapThing FindHeapThingForHeapEntry(HeapEntry* entry) { + // The reverse lookup map is only populated if the verification flag is + // enabled. + DCHECK(FLAG_heap_snapshot_verify); + + auto it = reverse_entries_map_.find(entry); + return it == reverse_entries_map_.end() ? nullptr : it->second; + } + + HeapEntryVerifier* verifier() const { return verifier_; } + void set_verifier(HeapEntryVerifier* verifier) { + DCHECK_IMPLIES(verifier_, !verifier); + verifier_ = verifier; + } +#endif + HeapEntry* AddEntry(Smi smi, HeapEntriesAllocator* allocator) { return smis_map_.emplace(smi.value(), allocator->AllocateEntry(smi)) .first->second; @@ -558,6 +612,8 @@ class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface { return entry != nullptr ? entry : AddEntry(smi, allocator); } + Heap* heap() const { return heap_; } + private: bool FillReferences(); void ProgressStep() override; @@ -575,6 +631,11 @@ class HeapSnapshotGenerator : public SnapshottingProgressReportingInterface { uint32_t progress_counter_; uint32_t progress_total_; Heap* heap_; + +#ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY + std::unordered_map reverse_entries_map_; + HeapEntryVerifier* verifier_ = nullptr; +#endif }; class OutputStreamWriter;