Commit 214f920a authored by Ulan Degenbaev's avatar Ulan Degenbaev Committed by Commit Bot

[heap] Support ephemeral paths with --track-retaining-path flag.

If there is a WeakMap in a retaining path of an object, then we have
two choices:
1) use the WeakMap backing store as the retainer.
2) use the key in the WeakMap as the retainer.
Both cases can be useful for debugging memory leaks.

This patch adds a second parameter to %DebugTrackRetainingPath().

If this parameter is "track-ephemeral-path", then the retaining path
printer will use the key in the WeakMap as a retainer. Otherwise,
the WeakMap backing store will be reported as the retainer.

Bug: v8:6987
Change-Id: I9feae25bc734c3abed501b9f901a36d2248a105c
Reviewed-on: https://chromium-review.googlesource.com/753343
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49324}
parent c44da6c2
...@@ -459,21 +459,30 @@ void Heap::ReportStatisticsAfterGC() { ...@@ -459,21 +459,30 @@ void Heap::ReportStatisticsAfterGC() {
} }
} }
void Heap::AddRetainingPathTarget(Handle<HeapObject> object) { void Heap::AddRetainingPathTarget(Handle<HeapObject> object,
RetainingPathOption option) {
if (!FLAG_track_retaining_path) { if (!FLAG_track_retaining_path) {
PrintF("Retaining path tracking requires --trace-retaining-path\n"); PrintF("Retaining path tracking requires --trace-retaining-path\n");
} else { } else {
int index = 0;
Handle<WeakFixedArray> array = WeakFixedArray::Add( Handle<WeakFixedArray> array = WeakFixedArray::Add(
handle(retaining_path_targets(), isolate()), object); handle(retaining_path_targets(), isolate()), object, &index);
set_retaining_path_targets(*array); set_retaining_path_targets(*array);
retaining_path_target_option_[index] = option;
} }
} }
bool Heap::IsRetainingPathTarget(HeapObject* object) { bool Heap::IsRetainingPathTarget(HeapObject* object,
WeakFixedArray::Iterator it(retaining_path_targets()); RetainingPathOption* option) {
HeapObject* target; if (!retaining_path_targets()->IsWeakFixedArray()) return false;
while ((target = it.Next<HeapObject>()) != nullptr) { WeakFixedArray* targets = WeakFixedArray::cast(retaining_path_targets());
if (target == object) return true; int length = targets->Length();
for (int i = 0; i < length; i++) {
if (targets->Get(i) == object) {
DCHECK(retaining_path_target_option_.count(i));
*option = retaining_path_target_option_[i];
return true;
}
} }
return false; return false;
} }
...@@ -502,17 +511,23 @@ const char* RootToString(Root root) { ...@@ -502,17 +511,23 @@ const char* RootToString(Root root) {
} }
} // namespace } // namespace
void Heap::PrintRetainingPath(HeapObject* target) { void Heap::PrintRetainingPath(HeapObject* target, RetainingPathOption option) {
PrintF("\n\n\n"); PrintF("\n\n\n");
PrintF("#################################################\n"); PrintF("#################################################\n");
PrintF("Retaining path for %p:\n", static_cast<void*>(target)); PrintF("Retaining path for %p:\n", static_cast<void*>(target));
HeapObject* object = target; HeapObject* object = target;
std::vector<HeapObject*> retaining_path; std::vector<std::pair<HeapObject*, bool>> retaining_path;
Root root = Root::kUnknown; Root root = Root::kUnknown;
bool ephemeral = false;
while (true) { while (true) {
retaining_path.push_back(object); retaining_path.push_back(std::make_pair(object, ephemeral));
if (retainer_.count(object)) { if (option == RetainingPathOption::kTrackEphemeralPath &&
ephemeral_retainer_.count(object)) {
object = ephemeral_retainer_[object];
ephemeral = true;
} else if (retainer_.count(object)) {
object = retainer_[object]; object = retainer_[object];
ephemeral = false;
} else { } else {
if (retaining_root_.count(object)) { if (retaining_root_.count(object)) {
root = retaining_root_[object]; root = retaining_root_[object];
...@@ -521,10 +536,13 @@ void Heap::PrintRetainingPath(HeapObject* target) { ...@@ -521,10 +536,13 @@ void Heap::PrintRetainingPath(HeapObject* target) {
} }
} }
int distance = static_cast<int>(retaining_path.size()); int distance = static_cast<int>(retaining_path.size());
for (auto object : retaining_path) { for (auto node : retaining_path) {
HeapObject* object = node.first;
bool ephemeral = node.second;
PrintF("\n"); PrintF("\n");
PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); PrintF("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
PrintF("Distance from root %d: ", distance); PrintF("Distance from root %d%s: ", distance,
ephemeral ? " (ephemeral)" : "");
object->ShortPrint(); object->ShortPrint();
PrintF("\n"); PrintF("\n");
#ifdef OBJECT_PRINT #ifdef OBJECT_PRINT
...@@ -540,16 +558,38 @@ void Heap::PrintRetainingPath(HeapObject* target) { ...@@ -540,16 +558,38 @@ void Heap::PrintRetainingPath(HeapObject* target) {
} }
void Heap::AddRetainer(HeapObject* retainer, HeapObject* object) { void Heap::AddRetainer(HeapObject* retainer, HeapObject* object) {
if (retainer_.count(object)) return;
retainer_[object] = retainer; retainer_[object] = retainer;
if (IsRetainingPathTarget(object)) { RetainingPathOption option = RetainingPathOption::kDefault;
PrintRetainingPath(object); if (IsRetainingPathTarget(object, &option)) {
// Check if the retaining path was already printed in
// AddEphemeralRetainer().
if (ephemeral_retainer_.count(object) == 0 ||
option == RetainingPathOption::kDefault) {
PrintRetainingPath(object, option);
}
}
}
void Heap::AddEphemeralRetainer(HeapObject* retainer, HeapObject* object) {
if (ephemeral_retainer_.count(object)) return;
ephemeral_retainer_[object] = retainer;
RetainingPathOption option = RetainingPathOption::kDefault;
if (IsRetainingPathTarget(object, &option) &&
option == RetainingPathOption::kTrackEphemeralPath) {
// Check if the retaining path was already printed in AddRetainer().
if (retainer_.count(object) == 0) {
PrintRetainingPath(object, option);
}
} }
} }
void Heap::AddRetainingRoot(Root root, HeapObject* object) { void Heap::AddRetainingRoot(Root root, HeapObject* object) {
if (retaining_root_.count(object)) return;
retaining_root_[object] = root; retaining_root_[object] = root;
if (IsRetainingPathTarget(object)) { RetainingPathOption option = RetainingPathOption::kDefault;
PrintRetainingPath(object); if (IsRetainingPathTarget(object, &option)) {
PrintRetainingPath(object, option);
} }
} }
...@@ -599,6 +639,7 @@ void Heap::GarbageCollectionPrologue() { ...@@ -599,6 +639,7 @@ void Heap::GarbageCollectionPrologue() {
UpdateNewSpaceAllocationCounter(); UpdateNewSpaceAllocationCounter();
if (FLAG_track_retaining_path) { if (FLAG_track_retaining_path) {
retainer_.clear(); retainer_.clear();
ephemeral_retainer_.clear();
retaining_root_.clear(); retaining_root_.clear();
} }
} }
......
...@@ -412,6 +412,8 @@ enum class FixedArrayVisitationMode { kRegular, kIncremental }; ...@@ -412,6 +412,8 @@ enum class FixedArrayVisitationMode { kRegular, kIncremental };
enum class TraceRetainingPathMode { kEnabled, kDisabled }; enum class TraceRetainingPathMode { kEnabled, kDisabled };
enum class RetainingPathOption { kDefault, kTrackEphemeralPath };
enum class GarbageCollectionReason { enum class GarbageCollectionReason {
kUnknown = 0, kUnknown = 0,
kAllocationFailure = 1, kAllocationFailure = 1,
...@@ -1520,7 +1522,8 @@ class Heap { ...@@ -1520,7 +1522,8 @@ class Heap {
// Adds the given object to the weak table of retaining path targets. // 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 // On each GC if the marker discovers the object, it will print the retaining
// path. This requires --track-retaining-path flag. // path. This requires --track-retaining-path flag.
void AddRetainingPathTarget(Handle<HeapObject> object); void AddRetainingPathTarget(Handle<HeapObject> object,
RetainingPathOption option);
// ============================================================================= // =============================================================================
#ifdef VERIFY_HEAP #ifdef VERIFY_HEAP
...@@ -2211,9 +2214,12 @@ class Heap { ...@@ -2211,9 +2214,12 @@ class Heap {
// =========================================================================== // ===========================================================================
void AddRetainer(HeapObject* retainer, HeapObject* object); void AddRetainer(HeapObject* retainer, HeapObject* object);
void AddEphemeralRetainer(HeapObject* retainer, HeapObject* object);
void AddRetainingRoot(Root root, HeapObject* object); void AddRetainingRoot(Root root, HeapObject* object);
bool IsRetainingPathTarget(HeapObject* object); // Returns true if the given object is a target of retaining path tracking.
void PrintRetainingPath(HeapObject* object); // Stores the option corresponding to the object in the provided *option.
bool IsRetainingPathTarget(HeapObject* object, RetainingPathOption* option);
void PrintRetainingPath(HeapObject* object, RetainingPathOption option);
// The amount of external memory registered through the API. // The amount of external memory registered through the API.
int64_t external_memory_; int64_t external_memory_;
...@@ -2452,6 +2458,12 @@ class Heap { ...@@ -2452,6 +2458,12 @@ class Heap {
std::map<HeapObject*, HeapObject*> retainer_; std::map<HeapObject*, HeapObject*> retainer_;
std::map<HeapObject*, Root> retaining_root_; std::map<HeapObject*, Root> retaining_root_;
// If an object is retained by an ephemeron, then the retaining key of the
// ephemeron is stored in this map.
std::map<HeapObject*, HeapObject*> ephemeral_retainer_;
// For each index inthe retaining_path_targets_ array this map
// stores the option of the corresponding target.
std::map<int, RetainingPathOption> retaining_path_target_option_;
// Classes in "heap" can be friends. // Classes in "heap" can be friends.
friend class AlwaysAllocateScope; friend class AlwaysAllocateScope;
......
...@@ -2889,6 +2889,11 @@ void MarkCompactCollector::ProcessWeakCollections() { ...@@ -2889,6 +2889,11 @@ void MarkCompactCollector::ProcessWeakCollections() {
RecordSlot(table, key_slot, *key_slot); RecordSlot(table, key_slot, *key_slot);
Object** value_slot = Object** value_slot =
table->RawFieldOfElementAt(ObjectHashTable::EntryToValueIndex(i)); table->RawFieldOfElementAt(ObjectHashTable::EntryToValueIndex(i));
if (V8_UNLIKELY(FLAG_track_retaining_path) &&
(*value_slot)->IsHeapObject()) {
heap()->AddEphemeralRetainer(heap_object,
HeapObject::cast(*value_slot));
}
visitor.VisitPointer(table, value_slot); visitor.VisitPointer(table, value_slot);
} }
} }
......
...@@ -650,12 +650,25 @@ RUNTIME_FUNCTION(Runtime_DebugTrace) { ...@@ -650,12 +650,25 @@ RUNTIME_FUNCTION(Runtime_DebugTrace) {
RUNTIME_FUNCTION(Runtime_DebugTrackRetainingPath) { RUNTIME_FUNCTION(Runtime_DebugTrackRetainingPath) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK_EQ(1, args.length()); DCHECK_LE(1, args.length());
DCHECK_GE(2, args.length());
if (!FLAG_track_retaining_path) { if (!FLAG_track_retaining_path) {
PrintF("DebugTrackRetainingPath requires --track-retaining-path flag.\n"); PrintF("DebugTrackRetainingPath requires --track-retaining-path flag.\n");
} else { } else {
CONVERT_ARG_HANDLE_CHECKED(HeapObject, object, 0); CONVERT_ARG_HANDLE_CHECKED(HeapObject, object, 0);
isolate->heap()->AddRetainingPathTarget(object); RetainingPathOption option = RetainingPathOption::kDefault;
if (args.length() == 2) {
CONVERT_ARG_HANDLE_CHECKED(String, str, 1);
const char track_ephemeral_path[] = "track-ephemeral-path";
if (str->IsOneByteEqualTo(STATIC_CHAR_VECTOR(track_ephemeral_path))) {
option = RetainingPathOption::kTrackEphemeralPath;
} else if (str->length() != 0) {
PrintF("Unexpected second argument of DebugTrackRetainingPath.\n");
PrintF("Expected an empty string or '%s', got '%s'.\n",
track_ephemeral_path, str->ToCString().get());
}
}
isolate->heap()->AddRetainingPathTarget(object, option);
} }
return isolate->heap()->undefined_value(); return isolate->heap()->undefined_value();
} }
......
...@@ -583,7 +583,7 @@ namespace internal { ...@@ -583,7 +583,7 @@ namespace internal {
F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \ F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \
F(DebugPrint, 1, 1) \ F(DebugPrint, 1, 1) \
F(DebugTrace, 0, 1) \ F(DebugTrace, 0, 1) \
F(DebugTrackRetainingPath, 1, 1) \ F(DebugTrackRetainingPath, -1, 1) \
F(PrintWithNameForAssert, 2, 1) \ F(PrintWithNameForAssert, 2, 1) \
F(GetExceptionDetails, 1, 1) \ F(GetExceptionDetails, 1, 1) \
F(GlobalPrint, 1, 1) \ F(GlobalPrint, 1, 1) \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment