mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
2d6037a144
TEST=ci Change-Id: Iadb3bb749da0d065c46bc723c7f5a8a8951b68bd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/265562 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
285 lines
8.5 KiB
C++
285 lines
8.5 KiB
C++
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#ifndef RUNTIME_VM_OBJECT_GRAPH_H_
|
|
#define RUNTIME_VM_OBJECT_GRAPH_H_
|
|
|
|
#include <memory>
|
|
|
|
#include "vm/allocation.h"
|
|
#include "vm/dart_api_state.h"
|
|
#include "vm/thread_stack_resource.h"
|
|
|
|
namespace dart {
|
|
|
|
class Array;
|
|
class Object;
|
|
class CountingPage;
|
|
|
|
#if !defined(PRODUCT)
|
|
|
|
// Utility to traverse the object graph in an ordered fashion.
|
|
// Example uses:
|
|
// - find a retaining path from the isolate roots to a particular object, or
|
|
// - determine how much memory is retained by some particular object(s).
|
|
class ObjectGraph : public ThreadStackResource {
|
|
public:
|
|
class Stack;
|
|
|
|
// Allows climbing the search tree all the way to the root.
|
|
class StackIterator {
|
|
public:
|
|
// The object this iterator currently points to.
|
|
ObjectPtr Get() const;
|
|
// Returns false if there is no parent.
|
|
bool MoveToParent();
|
|
// Offset into parent for the pointer to current object. -1 if no parent.
|
|
intptr_t OffsetFromParent() const;
|
|
|
|
private:
|
|
StackIterator(const Stack* stack, intptr_t index)
|
|
: stack_(stack), index_(index) {}
|
|
const Stack* stack_;
|
|
intptr_t index_;
|
|
friend class ObjectGraph::Stack;
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(StackIterator);
|
|
};
|
|
|
|
class Visitor {
|
|
public:
|
|
// Directs how the search should continue after visiting an object.
|
|
enum Direction {
|
|
kProceed, // Recurse on this object's pointers.
|
|
kBacktrack, // Ignore this object's pointers.
|
|
kAbort, // Terminate the entire search immediately.
|
|
};
|
|
virtual ~Visitor() {}
|
|
// Visits the object pointed to by *it. The iterator is only valid
|
|
// during this call. This method must not allocate from the heap or
|
|
// trigger GC in any way.
|
|
virtual Direction VisitObject(StackIterator* it) = 0;
|
|
|
|
virtual bool visit_weak_persistent_handles() const { return false; }
|
|
|
|
const char* gc_root_type = NULL;
|
|
bool is_traversing = false;
|
|
};
|
|
|
|
typedef struct {
|
|
intptr_t length;
|
|
const char* gc_root_type;
|
|
} RetainingPathResult;
|
|
|
|
explicit ObjectGraph(Thread* thread);
|
|
~ObjectGraph();
|
|
|
|
// Visits all strongly reachable objects in the isolate's heap, in a
|
|
// pre-order, depth first traversal.
|
|
void IterateObjects(Visitor* visitor);
|
|
void IterateUserObjects(Visitor* visitor);
|
|
|
|
// Like 'IterateObjects', but restricted to objects reachable from 'root'
|
|
// (including 'root' itself).
|
|
void IterateObjectsFrom(const Object& root, Visitor* visitor);
|
|
void IterateObjectsFrom(intptr_t class_id,
|
|
HeapIterationScope* iteration,
|
|
Visitor* visitor);
|
|
|
|
// The number of bytes retained by 'obj'.
|
|
intptr_t SizeRetainedByInstance(const Object& obj);
|
|
intptr_t SizeReachableByInstance(const Object& obj);
|
|
|
|
// The number of bytes retained by the set of all objects of the given class.
|
|
intptr_t SizeRetainedByClass(intptr_t class_id);
|
|
intptr_t SizeReachableByClass(intptr_t class_id);
|
|
|
|
// Finds some retaining path from the isolate roots to 'obj'. Populates the
|
|
// provided array with pairs of (object, offset from parent in words),
|
|
// starting with 'obj' itself, as far as there is room. Returns the number
|
|
// of objects on the full path. A null input array behaves like a zero-length
|
|
// input array. The 'offset' of a root is -1.
|
|
//
|
|
// To break the trivial path, the handle 'obj' is temporarily cleared during
|
|
// the search, but restored before returning. If no path is found (i.e., the
|
|
// provided handle was the only way to reach the object), zero is returned.
|
|
RetainingPathResult RetainingPath(Object* obj, const Array& path);
|
|
|
|
// Find the objects that reference 'obj'. Populates the provided array with
|
|
// pairs of (object pointing to 'obj', offset of pointer in words), as far as
|
|
// there is room. Returns the number of objects found.
|
|
//
|
|
// An object for which this function answers no inbound references might still
|
|
// be live due to references from the stack or embedder handles.
|
|
intptr_t InboundReferences(Object* obj, const Array& references);
|
|
|
|
private:
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(ObjectGraph);
|
|
};
|
|
|
|
class ChunkedWriter : public ThreadStackResource {
|
|
public:
|
|
explicit ChunkedWriter(Thread* thread) : ThreadStackResource(thread) {}
|
|
|
|
virtual intptr_t ReserveChunkPrefixSize() { return 0; }
|
|
|
|
// Takes ownership of [buffer], must be freed with [malloc].
|
|
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last) = 0;
|
|
};
|
|
|
|
class FileHeapSnapshotWriter : public ChunkedWriter {
|
|
public:
|
|
FileHeapSnapshotWriter(Thread* thread, const char* filename);
|
|
~FileHeapSnapshotWriter();
|
|
|
|
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last);
|
|
|
|
private:
|
|
void* file_ = nullptr;
|
|
};
|
|
|
|
class VmServiceHeapSnapshotChunkedWriter : public ChunkedWriter {
|
|
public:
|
|
explicit VmServiceHeapSnapshotChunkedWriter(Thread* thread)
|
|
: ChunkedWriter(thread) {}
|
|
|
|
virtual intptr_t ReserveChunkPrefixSize() { return kMetadataReservation; }
|
|
virtual void WriteChunk(uint8_t* buffer, intptr_t size, bool last);
|
|
|
|
private:
|
|
static const intptr_t kMetadataReservation = 512;
|
|
};
|
|
|
|
// Generates a dump of the heap, whose format is described in
|
|
// runtime/vm/service/heap_snapshot.md.
|
|
class HeapSnapshotWriter : public ThreadStackResource {
|
|
public:
|
|
HeapSnapshotWriter(Thread* thread, ChunkedWriter* writer)
|
|
: ThreadStackResource(thread), writer_(writer) {}
|
|
|
|
void WriteSigned(int64_t value) {
|
|
EnsureAvailable((sizeof(value) * kBitsPerByte) / 7 + 1);
|
|
|
|
bool is_last_part = false;
|
|
while (!is_last_part) {
|
|
uint8_t part = value & 0x7F;
|
|
value >>= 7;
|
|
if ((value == 0 && (part & 0x40) == 0) ||
|
|
(value == static_cast<intptr_t>(-1) && (part & 0x40) != 0)) {
|
|
is_last_part = true;
|
|
} else {
|
|
part |= 0x80;
|
|
}
|
|
buffer_[size_++] = part;
|
|
}
|
|
}
|
|
|
|
void WriteUnsigned(uintptr_t value) {
|
|
EnsureAvailable((sizeof(value) * kBitsPerByte) / 7 + 1);
|
|
|
|
bool is_last_part = false;
|
|
while (!is_last_part) {
|
|
uint8_t part = value & 0x7F;
|
|
value >>= 7;
|
|
if (value == 0) {
|
|
is_last_part = true;
|
|
} else {
|
|
part |= 0x80;
|
|
}
|
|
buffer_[size_++] = part;
|
|
}
|
|
}
|
|
|
|
void WriteBytes(const void* bytes, intptr_t len) {
|
|
EnsureAvailable(len);
|
|
memmove(&buffer_[size_], bytes, len);
|
|
size_ += len;
|
|
}
|
|
|
|
void ScrubAndWriteUtf8(char* value) {
|
|
intptr_t len = strlen(value);
|
|
for (intptr_t i = len - 1; i >= 0; i--) {
|
|
if (value[i] == '@') {
|
|
value[i] = '\0';
|
|
}
|
|
}
|
|
WriteUtf8(value);
|
|
}
|
|
|
|
void WriteUtf8(const char* value) {
|
|
intptr_t len = strlen(value);
|
|
WriteUnsigned(len);
|
|
WriteBytes(value, len);
|
|
}
|
|
|
|
void AssignObjectId(ObjectPtr obj);
|
|
intptr_t GetObjectId(ObjectPtr obj) const;
|
|
void ClearObjectIds();
|
|
void CountReferences(intptr_t count);
|
|
void CountExternalProperty();
|
|
void AddSmi(SmiPtr smi);
|
|
|
|
void Write();
|
|
|
|
static uint32_t GetHeapSnapshotIdentityHash(Thread* thread, ObjectPtr obj);
|
|
|
|
private:
|
|
static uint32_t GetHashHelper(Thread* thread, ObjectPtr obj);
|
|
|
|
static const intptr_t kPreferredChunkSize = MB;
|
|
|
|
void SetupCountingPages();
|
|
bool OnImagePage(ObjectPtr obj) const;
|
|
CountingPage* FindCountingPage(ObjectPtr obj) const;
|
|
|
|
void EnsureAvailable(intptr_t needed);
|
|
void Flush(bool last = false);
|
|
|
|
ChunkedWriter* writer_ = nullptr;
|
|
|
|
uint8_t* buffer_ = nullptr;
|
|
intptr_t size_ = 0;
|
|
intptr_t capacity_ = 0;
|
|
|
|
intptr_t class_count_ = 0;
|
|
intptr_t object_count_ = 0;
|
|
intptr_t reference_count_ = 0;
|
|
intptr_t external_property_count_ = 0;
|
|
|
|
struct ImagePageRange {
|
|
uword base;
|
|
uword size;
|
|
};
|
|
// There are up to 4 images to consider:
|
|
// {instructions, data} x {vm isolate, current isolate}
|
|
static const intptr_t kMaxImagePages = 4;
|
|
ImagePageRange image_page_ranges_[kMaxImagePages];
|
|
|
|
MallocGrowableArray<SmiPtr> smis_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(HeapSnapshotWriter);
|
|
};
|
|
|
|
class CountObjectsVisitor : public ObjectVisitor, public HandleVisitor {
|
|
public:
|
|
CountObjectsVisitor(Thread* thread, intptr_t class_count);
|
|
~CountObjectsVisitor() {}
|
|
|
|
void VisitObject(ObjectPtr obj);
|
|
void VisitHandle(uword addr);
|
|
|
|
std::unique_ptr<intptr_t[]> new_count_;
|
|
std::unique_ptr<intptr_t[]> new_size_;
|
|
std::unique_ptr<intptr_t[]> new_external_size_;
|
|
std::unique_ptr<intptr_t[]> old_count_;
|
|
std::unique_ptr<intptr_t[]> old_size_;
|
|
std::unique_ptr<intptr_t[]> old_external_size_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(CountObjectsVisitor);
|
|
};
|
|
|
|
#endif // !defined(PRODUCT)
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_OBJECT_GRAPH_H_
|