diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 1e80dd4cf6..dae42235ad 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -650,11 +650,15 @@ DEFINE_BOOL(track_gc_object_stats, false, "track object counts and memory usage") DEFINE_BOOL(trace_gc_object_stats, false, "trace object counts and memory usage") +DEFINE_BOOL(track_retaining_path, false, + "enable support for tracking retaining path") DEFINE_INT(gc_stats, 0, "Used by tracing internally to enable gc statistics") DEFINE_IMPLICATION(trace_gc_object_stats, track_gc_object_stats) DEFINE_VALUE_IMPLICATION(track_gc_object_stats, gc_stats, 1) DEFINE_VALUE_IMPLICATION(trace_gc_object_stats, gc_stats, 1) DEFINE_NEG_IMPLICATION(trace_gc_object_stats, incremental_marking) +DEFINE_NEG_IMPLICATION(track_retaining_path, incremental_marking) +DEFINE_NEG_IMPLICATION(track_retaining_path, concurrent_marking) DEFINE_BOOL(track_detached_contexts, true, "track native contexts that are expected to be garbage collected") DEFINE_BOOL(trace_detached_contexts, false, diff --git a/src/heap/heap.cc b/src/heap/heap.cc index 6d264879a5..56895e1a1d 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -407,6 +407,99 @@ void Heap::ReportStatisticsAfterGC() { } } +void Heap::AddRetainingPathTarget(Handle object) { + if (!FLAG_track_retaining_path) { + PrintF("Retaining path tracking requires --trace-retaining-path\n"); + } else { + Handle array = WeakFixedArray::Add( + handle(retaining_path_targets(), isolate()), object); + set_retaining_path_targets(*array); + } +} + +bool Heap::IsRetainingPathTarget(HeapObject* object) { + WeakFixedArray::Iterator it(retaining_path_targets()); + HeapObject* target; + while ((target = it.Next()) != nullptr) { + if (target == object) return true; + } + return false; +} + +namespace { +const char* RootToString(Root root) { + switch (root) { +#define ROOT_CASE(root_id, ignore, description) \ + case Root::root_id: \ + return description; + ROOT_ID_LIST(ROOT_CASE) +#undef ROOT_CASE + case Root::kCodeFlusher: + return "(Code flusher)"; + case Root::kPartialSnapshotCache: + return "(Partial snapshot cache)"; + case Root::kWeakCollections: + return "(Weak collections)"; + case Root::kWrapperTracing: + return "(Wrapper tracing)"; + case Root::kUnknown: + return "(Unknown)"; + } + UNREACHABLE(); + return nullptr; +} +} // namespace + +void Heap::PrintRetainingPath(HeapObject* target) { + PrintF("\n\n\n"); + PrintF("#################################################\n"); + PrintF("Retaining path for %p:\n", static_cast(target)); + HeapObject* object = target; + std::vector retaining_path; + Root root = Root::kUnknown; + while (true) { + retaining_path.push_back(object); + if (retainer_.count(object)) { + object = retainer_[object]; + } else { + if (retaining_root_.count(object)) { + root = retaining_root_[object]; + } + break; + } + } + int distance = static_cast(retaining_path.size()); + for (auto object : retaining_path) { + PrintF("\n"); + PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + PrintF("Distance from root %d: ", distance); + object->ShortPrint(); + PrintF("\n"); +#ifdef OBJECT_PRINT + object->Print(); + PrintF("\n"); +#endif + --distance; + } + PrintF("\n"); + PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + PrintF("Root: %s\n", RootToString(root)); + PrintF("-------------------------------------------------\n"); +} + +void Heap::AddRetainer(HeapObject* retainer, HeapObject* object) { + retainer_[object] = retainer; + if (IsRetainingPathTarget(object)) { + PrintRetainingPath(object); + } +} + +void Heap::AddRetainingRoot(Root root, HeapObject* object) { + retaining_root_[object] = root; + if (IsRetainingPathTarget(object)) { + PrintRetainingPath(object); + } +} void Heap::IncrementDeferredCount(v8::Isolate::UseCounterFeature feature) { deferred_counters_[feature]++; @@ -452,6 +545,10 @@ void Heap::GarbageCollectionPrologue() { } CheckNewSpaceExpansionCriteria(); UpdateNewSpaceAllocationCounter(); + if (FLAG_track_retaining_path) { + retainer_.clear(); + retaining_root_.clear(); + } } size_t Heap::SizeOfObjects() { @@ -966,6 +1063,7 @@ void Heap::CollectAllAvailableGarbage(GarbageCollectionReason gc_reason) { break; } } + set_current_gc_flags(kNoGCFlags); new_space_->Shrink(); UncommitFromSpace(); @@ -2859,6 +2957,7 @@ void Heap::CreateInitialObjects() { set_detached_contexts(empty_fixed_array()); set_retained_maps(ArrayList::cast(empty_fixed_array())); + set_retaining_path_targets(undefined_value()); set_weak_object_to_code_table(*WeakHashTable::New(isolate(), 16, TENURED)); @@ -2994,6 +3093,7 @@ bool Heap::RootCanBeWrittenAfterInitialization(Heap::RootListIndex root_index) { case kWeakObjectToCodeTableRootIndex: case kWeakNewSpaceObjectToCodeListRootIndex: case kRetainedMapsRootIndex: + case kRetainingPathTargetsRootIndex: case kCodeCoverageListRootIndex: case kNoScriptSharedFunctionInfosRootIndex: case kWeakStackTraceListRootIndex: @@ -4508,7 +4608,6 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action, return result; } - void Heap::IdleNotificationEpilogue(GCIdleTimeAction action, GCIdleTimeHeapState heap_state, double start_ms, double deadline_in_ms) { @@ -5908,7 +6007,7 @@ void Heap::RegisterExternallyReferencedObject(Object** object) { incremental_marking()->WhiteToGreyAndPush(heap_object); } else { DCHECK(mark_compact_collector()->in_use()); - mark_compact_collector()->MarkObject(heap_object); + mark_compact_collector()->MarkExternallyReferencedObject(heap_object); } } diff --git a/src/heap/heap.h b/src/heap/heap.h index 0c2e55d669..fd77d46483 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -203,6 +203,7 @@ using v8::MemoryPressureLevel; V(FixedArray, materialized_objects, MaterializedObjects) \ V(FixedArray, microtask_queue, MicrotaskQueue) \ V(FixedArray, detached_contexts, DetachedContexts) \ + V(HeapObject, retaining_path_targets, RetainingPathTargets) \ V(ArrayList, retained_maps, RetainedMaps) \ V(WeakHashTable, weak_object_to_code_table, WeakObjectToCodeTable) \ /* weak_new_space_object_to_code_list is an array of weak cells, where */ \ @@ -1480,8 +1481,16 @@ class Heap { void MergeAllocationSitePretenuringFeedback( const base::HashMap& local_pretenuring_feedback); -// ============================================================================= + // =========================================================================== + // Retaining path tracking. ================================================== + // =========================================================================== + // Adds the given object to the weak table of retaining path targets. + // On each GC if the marker discovers the object, it will print the retaining + // path. This requires --track-retaining-path flag. + void AddRetainingPathTarget(Handle object); + +// ============================================================================= #ifdef VERIFY_HEAP // Verify the heap is in its normal state before or after a GC. void Verify(); @@ -2142,9 +2151,16 @@ class Heap { MUST_USE_RESULT AllocationResult AllocateCode(int object_size, bool immovable); + void set_force_oom(bool value) { force_oom_ = value; } + + // =========================================================================== + // Retaining path tracing ==================================================== // =========================================================================== - void set_force_oom(bool value) { force_oom_ = value; } + void AddRetainer(HeapObject* retainer, HeapObject* object); + void AddRetainingRoot(Root root, HeapObject* object); + bool IsRetainingPathTarget(HeapObject* object); + void PrintRetainingPath(HeapObject* object); // The amount of external memory registered through the API. int64_t external_memory_; @@ -2382,12 +2398,17 @@ class Heap { HeapObject* pending_layout_change_object_; + std::map retainer_; + std::map retaining_root_; + // Classes in "heap" can be friends. friend class AlwaysAllocateScope; friend class ConcurrentMarking; friend class GCCallbacksScope; friend class GCTracer; friend class HeapIterator; + template + friend class MarkingVisitor; friend class IdleScavengeObserver; friend class IncrementalMarking; friend class IncrementalMarkingJob; diff --git a/src/heap/incremental-marking.cc b/src/heap/incremental-marking.cc index cdc8881f88..da82078043 100644 --- a/src/heap/incremental-marking.cc +++ b/src/heap/incremental-marking.cc @@ -294,7 +294,7 @@ class IncrementalMarkingMarkingVisitor final Object* target = *p; if (target->IsHeapObject()) { collector_->RecordSlot(host, p, target); - MarkObject(target); + MarkObject(host, target); } } @@ -304,13 +304,13 @@ class IncrementalMarkingMarkingVisitor final Object* target = *p; if (target->IsHeapObject()) { collector_->RecordSlot(host, p, target); - MarkObject(target); + MarkObject(host, target); } } } // Marks the object grey and pushes it on the marking stack. - V8_INLINE void MarkObject(Object* obj) { + V8_INLINE void MarkObject(HeapObject* host, Object* obj) { incremental_marking_->WhiteToGreyAndPush(HeapObject::cast(obj)); } diff --git a/src/heap/mark-compact-inl.h b/src/heap/mark-compact-inl.h index 8873d213c2..df3d173185 100644 --- a/src/heap/mark-compact-inl.h +++ b/src/heap/mark-compact-inl.h @@ -21,10 +21,23 @@ void MarkCompactCollector::PushBlack(HeapObject* obj) { } } -void MarkCompactCollector::MarkObject(HeapObject* obj) { +void MarkCompactCollector::MarkObject(HeapObject* host, HeapObject* obj) { if (ObjectMarking::WhiteToBlack( obj, MarkingState::Internal(obj))) { PushBlack(obj); + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + heap_->AddRetainer(host, obj); + } + } +} + +void MarkCompactCollector::MarkExternallyReferencedObject(HeapObject* obj) { + if (ObjectMarking::WhiteToBlack( + obj, MarkingState::Internal(obj))) { + PushBlack(obj); + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + heap_->AddRetainingRoot(Root::kWrapperTracing, obj); + } } } diff --git a/src/heap/mark-compact.cc b/src/heap/mark-compact.cc index 055b11082f..a11dc69752 100644 --- a/src/heap/mark-compact.cc +++ b/src/heap/mark-compact.cc @@ -1052,7 +1052,8 @@ class MarkCompactMarkingVisitor final Object** end) final { // Mark all objects pointed to in [start, end). const int kMinRangeForMarkingRecursion = 64; - if (end - start >= kMinRangeForMarkingRecursion) { + if (end - start >= kMinRangeForMarkingRecursion && + V8_LIKELY(!FLAG_track_retaining_path)) { if (VisitUnmarkedObjects(host, start, end)) return; // We are close to a stack overflow, so just mark the objects. } @@ -1062,8 +1063,8 @@ class MarkCompactMarkingVisitor final } // Marks the object black and pushes it on the marking stack. - V8_INLINE void MarkObject(HeapObject* object) { - collector_->MarkObject(object); + V8_INLINE void MarkObject(HeapObject* host, HeapObject* object) { + collector_->MarkObject(host, object); } // Marks the object black without pushing it on the marking stack. Returns @@ -1076,7 +1077,7 @@ class MarkCompactMarkingVisitor final if (!(*p)->IsHeapObject()) return; HeapObject* target_object = HeapObject::cast(*p); collector_->RecordSlot(host, p, target_object); - collector_->MarkObject(target_object); + collector_->MarkObject(host, target_object); } protected: @@ -1106,7 +1107,7 @@ class MarkCompactMarkingVisitor final Map* map = obj->map(); ObjectMarking::WhiteToBlack(obj, MarkingState::Internal(obj)); // Mark the map pointer and the body. - collector_->MarkObject(map); + collector_->MarkObject(obj, map); Visit(map, obj); } } @@ -1132,19 +1133,20 @@ class MarkCompactCollector::RootMarkingVisitor : public ObjectVisitor, : collector_(heap->mark_compact_collector()), visitor_(collector_) {} void VisitPointer(HeapObject* host, Object** p) override { - MarkObjectByPointer(p); + MarkObjectByPointer(host, p); } void VisitPointers(HeapObject* host, Object** start, Object** end) override { - for (Object** p = start; p < end; p++) MarkObjectByPointer(p); + for (Object** p = start; p < end; p++) MarkObjectByPointer(host, p); } void VisitRootPointer(Root root, Object** p) override { - MarkObjectByPointer(p); + MarkObjectByPointer(nullptr, p, root); } void VisitRootPointers(Root root, Object** start, Object** end) override { - for (Object** p = start; p < end; p++) MarkObjectByPointer(p); + for (Object** p = start; p < end; p++) + MarkObjectByPointer(nullptr, p, root); } // Skip the weak next code link in a code object, which is visited in @@ -1152,16 +1154,24 @@ class MarkCompactCollector::RootMarkingVisitor : public ObjectVisitor, void VisitNextCodeLink(Code* host, Object** p) override {} private: - void MarkObjectByPointer(Object** p) { + void MarkObjectByPointer(HeapObject* host, Object** p, + Root root = Root::kUnknown) { if (!(*p)->IsHeapObject()) return; HeapObject* object = HeapObject::cast(*p); if (ObjectMarking::WhiteToBlack( object, MarkingState::Internal(object))) { + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + if (host) { + object->GetHeap()->AddRetainer(host, object); + } else { + object->GetHeap()->AddRetainingRoot(root, object); + } + } Map* map = object->map(); // Mark the map pointer and body, and push them on the marking stack. - collector_->MarkObject(map); + collector_->MarkObject(object, map); visitor_.Visit(map, object); // Mark all the objects reachable from the map and body. May leave // overflowed objects in the heap. @@ -1961,7 +1971,7 @@ void MarkCompactCollector::EmptyMarkingWorklist() { object, MarkingState::Internal(object)))); Map* map = object->map(); - MarkObject(map); + MarkObject(object, map); visitor.Visit(map, object); } DCHECK(marking_worklist()->IsEmpty()); diff --git a/src/heap/mark-compact.h b/src/heap/mark-compact.h index 937dad1a91..8894a0ead8 100644 --- a/src/heap/mark-compact.h +++ b/src/heap/mark-compact.h @@ -681,7 +681,10 @@ class MarkCompactCollector final : public MarkCompactCollectorBase { // Marks the object black and pushes it on the marking stack. // This is for non-incremental marking only. - V8_INLINE void MarkObject(HeapObject* obj); + V8_INLINE void MarkObject(HeapObject* host, HeapObject* obj); + + // Used by wrapper tracing. + V8_INLINE void MarkExternallyReferencedObject(HeapObject* obj); // Mark the heap roots and all objects reachable from them. void MarkRoots(RootMarkingVisitor* visitor); diff --git a/src/heap/objects-visiting-inl.h b/src/heap/objects-visiting-inl.h index ad3ddbc52c..097c8579a3 100644 --- a/src/heap/objects-visiting-inl.h +++ b/src/heap/objects-visiting-inl.h @@ -321,6 +321,9 @@ void MarkingVisitor::MarkMapContents(Map* map) { // just mark the entire descriptor array. if (!map->is_prototype_map()) { DescriptorArray* descriptors = map->instance_descriptors(); + if (V8_UNLIKELY(FLAG_track_retaining_path)) { + heap_->AddRetainer(map, descriptors); + } if (visitor->MarkObjectWithoutPush(descriptors) && descriptors->length() > 0) { visitor->VisitPointers(descriptors, descriptors->GetFirstElementAddress(), @@ -392,7 +395,7 @@ void MarkingVisitor::VisitCodeEntry(JSFunction* host, ConcreteVisitor* visitor = static_cast(this); Code* code = Code::cast(Code::GetObjectFromEntryAddress(entry_address)); collector_->RecordCodeEntrySlot(host, entry_address, code); - visitor->MarkObject(code); + visitor->MarkObject(host, code); } template @@ -403,7 +406,7 @@ void MarkingVisitor::VisitEmbeddedPointer(Code* host, HeapObject* object = HeapObject::cast(rinfo->target_object()); collector_->RecordRelocSlot(host, rinfo, object); if (!host->IsWeakObject(object)) { - visitor->MarkObject(object); + visitor->MarkObject(host, object); } } @@ -415,7 +418,7 @@ void MarkingVisitor::VisitCellPointer(Code* host, Cell* cell = rinfo->target_cell(); collector_->RecordRelocSlot(host, rinfo, cell); if (!host->IsWeakObject(cell)) { - visitor->MarkObject(cell); + visitor->MarkObject(host, cell); } } @@ -427,7 +430,7 @@ void MarkingVisitor::VisitDebugTarget(Code* host, rinfo->IsPatchedDebugBreakSlotSequence()); Code* target = Code::GetCodeFromTargetAddress(rinfo->debug_call_address()); collector_->RecordRelocSlot(host, rinfo, target); - visitor->MarkObject(target); + visitor->MarkObject(host, target); } template @@ -437,7 +440,7 @@ void MarkingVisitor::VisitCodeTarget(Code* host, DCHECK(RelocInfo::IsCodeTarget(rinfo->rmode())); Code* target = Code::GetCodeFromTargetAddress(rinfo->target_address()); collector_->RecordRelocSlot(host, rinfo, target); - visitor->MarkObject(target); + visitor->MarkObject(host, target); } template @@ -448,7 +451,7 @@ void MarkingVisitor::VisitCodeAgeSequence(Code* host, Code* target = rinfo->code_age_stub(); DCHECK_NOT_NULL(target); collector_->RecordRelocSlot(host, rinfo, target); - visitor->MarkObject(target); + visitor->MarkObject(host, target); } } // namespace internal diff --git a/src/objects-printer.cc b/src/objects-printer.cc index c4f8291f81..832cef30f2 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -269,6 +269,7 @@ void ByteArray::ByteArrayPrint(std::ostream& os) { // NOLINT void BytecodeArray::BytecodeArrayPrint(std::ostream& os) { // NOLINT + HeapObject::PrintHeader(os, "BytecodeArray"); Disassemble(os); } @@ -1090,9 +1091,22 @@ void JSFunction::JSFunctionPrint(std::ostream& os) { // NOLINT os << "\n - bytecode = " << shared()->bytecode_array(); } } + shared()->PrintSourceCode(os); JSObjectPrintBody(os, this); } +void SharedFunctionInfo::PrintSourceCode(std::ostream& os) { + if (HasSourceCode()) { + os << "\n - source code = "; + String* source = String::cast(Script::cast(script())->source()); + int start = start_position(); + int length = end_position() - start; + std::unique_ptr source_string = source->ToCString( + DISALLOW_NULLS, FAST_STRING_TRAVERSAL, start, length, NULL); + os << source_string.get(); + } +} + void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT HeapObject::PrintHeader(os, "SharedFunctionInfo"); os << "\n - name = "; @@ -1113,15 +1127,7 @@ void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT if (HasBytecodeArray()) { os << "\n - bytecode_array = " << bytecode_array(); } - if (HasSourceCode()) { - os << "\n - source code = "; - String* source = String::cast(Script::cast(script())->source()); - int start = start_position(); - int length = end_position() - start; - std::unique_ptr source_string = source->ToCString( - DISALLOW_NULLS, FAST_STRING_TRAVERSAL, start, length, NULL); - os << source_string.get(); - } + PrintSourceCode(os); // Script files are often large, hard to read. // os << "\n - script ="; // script()->Print(os); diff --git a/src/objects/shared-function-info.h b/src/objects/shared-function-info.h index 03cae0aede..74aee72ad7 100644 --- a/src/objects/shared-function-info.h +++ b/src/objects/shared-function-info.h @@ -391,6 +391,9 @@ class SharedFunctionInfo : public HeapObject { // Dispatched behavior. DECL_PRINTER(SharedFunctionInfo) DECL_VERIFIER(SharedFunctionInfo) +#ifdef OBJECT_PRINT + void PrintSourceCode(std::ostream& os); +#endif void ResetForNewContext(int new_ic_age); diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 10deb67216..474adf8b95 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -612,6 +612,17 @@ RUNTIME_FUNCTION(Runtime_DebugTrace) { return isolate->heap()->undefined_value(); } +RUNTIME_FUNCTION(Runtime_DebugTrackRetainingPath) { + HandleScope scope(isolate); + DCHECK_EQ(1, args.length()); + if (!FLAG_track_retaining_path) { + PrintF("DebugTrackRetainingPath requires --track-retaining-path flag.\n"); + } else { + CONVERT_ARG_HANDLE_CHECKED(HeapObject, object, 0); + isolate->heap()->AddRetainingPathTarget(object); + } + return isolate->heap()->undefined_value(); +} // This will not allocate (flatten the string), but it may run // very slowly for very deeply nested ConsStrings. For debugging use only. diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 9d852b3e1f..9b2db8a54d 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -569,6 +569,7 @@ namespace internal { F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \ F(DebugPrint, 1, 1) \ F(DebugTrace, 0, 1) \ + F(DebugTrackRetainingPath, 1, 1) \ F(GetExceptionDetails, 1, 1) \ F(GlobalPrint, 1, 1) \ F(SystemBreak, 0, 1) \ diff --git a/src/visitors.h b/src/visitors.h index 0822d91690..af150fcee7 100644 --- a/src/visitors.h +++ b/src/visitors.h @@ -48,7 +48,9 @@ enum class Root { // TODO(ulan): Merge with the ROOT_ID_LIST. kCodeFlusher, kPartialSnapshotCache, - kWeakCollections + kWeakCollections, + kWrapperTracing, + kUnknown }; // Abstract base class for visiting, and optionally modifying, the