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() {
}
}
void Heap::AddRetainingPathTarget(Handle<HeapObject> object) {
void Heap::AddRetainingPathTarget(Handle<HeapObject> object,
RetainingPathOption option) {
if (!FLAG_track_retaining_path) {
PrintF("Retaining path tracking requires --trace-retaining-path\n");
} else {
int index = 0;
Handle<WeakFixedArray> array = WeakFixedArray::Add(
handle(retaining_path_targets(), isolate()), object);
handle(retaining_path_targets(), isolate()), object, &index);
set_retaining_path_targets(*array);
retaining_path_target_option_[index] = option;
}
}
bool Heap::IsRetainingPathTarget(HeapObject* object) {
WeakFixedArray::Iterator it(retaining_path_targets());
HeapObject* target;
while ((target = it.Next<HeapObject>()) != nullptr) {
if (target == object) return true;
bool Heap::IsRetainingPathTarget(HeapObject* object,
RetainingPathOption* option) {
if (!retaining_path_targets()->IsWeakFixedArray()) return false;
WeakFixedArray* targets = WeakFixedArray::cast(retaining_path_targets());
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;
}
......@@ -502,17 +511,23 @@ const char* RootToString(Root root) {
}
} // namespace
void Heap::PrintRetainingPath(HeapObject* target) {
void Heap::PrintRetainingPath(HeapObject* target, RetainingPathOption option) {
PrintF("\n\n\n");
PrintF("#################################################\n");
PrintF("Retaining path for %p:\n", static_cast<void*>(target));
HeapObject* object = target;
std::vector<HeapObject*> retaining_path;
std::vector<std::pair<HeapObject*, bool>> retaining_path;
Root root = Root::kUnknown;
bool ephemeral = false;
while (true) {
retaining_path.push_back(object);
if (retainer_.count(object)) {
retaining_path.push_back(std::make_pair(object, ephemeral));
if (option == RetainingPathOption::kTrackEphemeralPath &&
ephemeral_retainer_.count(object)) {
object = ephemeral_retainer_[object];
ephemeral = true;
} else if (retainer_.count(object)) {
object = retainer_[object];
ephemeral = false;
} else {
if (retaining_root_.count(object)) {
root = retaining_root_[object];
......@@ -521,10 +536,13 @@ void Heap::PrintRetainingPath(HeapObject* target) {
}
}
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("Distance from root %d: ", distance);
PrintF("Distance from root %d%s: ", distance,
ephemeral ? " (ephemeral)" : "");
object->ShortPrint();
PrintF("\n");
#ifdef OBJECT_PRINT
......@@ -540,16 +558,38 @@ void Heap::PrintRetainingPath(HeapObject* target) {
}
void Heap::AddRetainer(HeapObject* retainer, HeapObject* object) {
if (retainer_.count(object)) return;
retainer_[object] = retainer;
if (IsRetainingPathTarget(object)) {
PrintRetainingPath(object);
RetainingPathOption option = RetainingPathOption::kDefault;
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) {
if (retaining_root_.count(object)) return;
retaining_root_[object] = root;
if (IsRetainingPathTarget(object)) {
PrintRetainingPath(object);
RetainingPathOption option = RetainingPathOption::kDefault;
if (IsRetainingPathTarget(object, &option)) {
PrintRetainingPath(object, option);
}
}
......@@ -599,6 +639,7 @@ void Heap::GarbageCollectionPrologue() {
UpdateNewSpaceAllocationCounter();
if (FLAG_track_retaining_path) {
retainer_.clear();
ephemeral_retainer_.clear();
retaining_root_.clear();
}
}
......
......@@ -412,6 +412,8 @@ enum class FixedArrayVisitationMode { kRegular, kIncremental };
enum class TraceRetainingPathMode { kEnabled, kDisabled };
enum class RetainingPathOption { kDefault, kTrackEphemeralPath };
enum class GarbageCollectionReason {
kUnknown = 0,
kAllocationFailure = 1,
......@@ -1520,7 +1522,8 @@ class Heap {
// 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<HeapObject> object);
void AddRetainingPathTarget(Handle<HeapObject> object,
RetainingPathOption option);
// =============================================================================
#ifdef VERIFY_HEAP
......@@ -2211,9 +2214,12 @@ class Heap {
// ===========================================================================
void AddRetainer(HeapObject* retainer, HeapObject* object);
void AddEphemeralRetainer(HeapObject* retainer, HeapObject* object);
void AddRetainingRoot(Root root, HeapObject* object);
bool IsRetainingPathTarget(HeapObject* object);
void PrintRetainingPath(HeapObject* object);
// Returns true if the given object is a target of retaining path tracking.
// 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.
int64_t external_memory_;
......@@ -2452,6 +2458,12 @@ class Heap {
std::map<HeapObject*, HeapObject*> retainer_;
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.
friend class AlwaysAllocateScope;
......
......@@ -2889,6 +2889,11 @@ void MarkCompactCollector::ProcessWeakCollections() {
RecordSlot(table, key_slot, *key_slot);
Object** value_slot =
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);
}
}
......
......@@ -650,12 +650,25 @@ RUNTIME_FUNCTION(Runtime_DebugTrace) {
RUNTIME_FUNCTION(Runtime_DebugTrackRetainingPath) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DCHECK_LE(1, args.length());
DCHECK_GE(2, 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);
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();
}
......
......@@ -583,7 +583,7 @@ namespace internal {
F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \
F(DebugPrint, 1, 1) \
F(DebugTrace, 0, 1) \
F(DebugTrackRetainingPath, 1, 1) \
F(DebugTrackRetainingPath, -1, 1) \
F(PrintWithNameForAssert, 2, 1) \
F(GetExceptionDetails, 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