dart-sdk/runtime/vm/class_table.cc
Alexander Markov 591b2bd724 [vm] Add --print-object-layout-to option to gen_snapshot
This option dumps offsets of Dart fields and sizes of Dart instances
into the specified file in JSON format.
Could be useful for debugging or memory usage investigations.

Typical output:

{"class":"_JsonUtf8Parser","size":80,"fields":[{"field":"emptyChunk","static":true},{"field":"decoder","offset":56},{"field":"chunk","offset":64},{"field":"chunkEnd","offset":72}]}

TEST=runtime/tests/vm/dart/print_object_layout_test.dart
Fixes https://github.com/dart-lang/sdk/issues/47892

Change-Id: I0b2f7ed9e4991e5fcd18517d5a06aa826c6d7125
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/223662
Reviewed-by: Dan Field <dnfield@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2021-12-20 21:40:33 +00:00

698 lines
22 KiB
C++

// Copyright (c) 2012, 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.
#include "vm/class_table.h"
#include <limits>
#include <memory>
#include "platform/atomic.h"
#include "vm/flags.h"
#include "vm/growable_array.h"
#include "vm/heap/heap.h"
#include "vm/object.h"
#include "vm/object_graph.h"
#include "vm/raw_object.h"
#include "vm/visitor.h"
namespace dart {
DEFINE_FLAG(bool, print_class_table, false, "Print initial class table.");
SharedClassTable::SharedClassTable()
: top_(kNumPredefinedCids),
capacity_(0),
old_tables_(new MallocGrowableArray<void*>()) {
if (Dart::vm_isolate() == NULL) {
ASSERT(kInitialCapacity >= kNumPredefinedCids);
capacity_ = kInitialCapacity;
// Note that [calloc] will zero-initialize the memory.
table_.store(reinterpret_cast<RelaxedAtomic<intptr_t>*>(
calloc(capacity_, sizeof(RelaxedAtomic<intptr_t>))));
} else {
// Duplicate the class table from the VM isolate.
auto vm_shared_class_table = Dart::vm_isolate_group()->shared_class_table();
capacity_ = vm_shared_class_table->capacity_;
// Note that [calloc] will zero-initialize the memory.
RelaxedAtomic<intptr_t>* table = reinterpret_cast<RelaxedAtomic<intptr_t>*>(
calloc(capacity_, sizeof(RelaxedAtomic<intptr_t>)));
// The following cids don't have a corresponding class object in Dart code.
// We therefore need to initialize them eagerly.
COMPILE_ASSERT(kFirstInternalOnlyCid == kObjectCid + 1);
for (intptr_t i = kObjectCid; i <= kLastInternalOnlyCid; i++) {
table[i] = vm_shared_class_table->SizeAt(i);
}
table[kTypeArgumentsCid] = vm_shared_class_table->SizeAt(kTypeArgumentsCid);
table[kFreeListElement] = vm_shared_class_table->SizeAt(kFreeListElement);
table[kForwardingCorpse] = vm_shared_class_table->SizeAt(kForwardingCorpse);
table[kDynamicCid] = vm_shared_class_table->SizeAt(kDynamicCid);
table[kVoidCid] = vm_shared_class_table->SizeAt(kVoidCid);
table_.store(table);
}
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
// Note that [calloc] will zero-initialize the memory.
unboxed_fields_map_ = static_cast<UnboxedFieldBitmap*>(
calloc(capacity_, sizeof(UnboxedFieldBitmap)));
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
#ifndef PRODUCT
// Note that [calloc] will zero-initialize the memory.
trace_allocation_table_.store(
static_cast<uint8_t*>(calloc(capacity_, sizeof(uint8_t))));
#endif // !PRODUCT
}
SharedClassTable::~SharedClassTable() {
if (old_tables_ != NULL) {
FreeOldTables();
delete old_tables_;
}
free(table_.load());
free(unboxed_fields_map_);
NOT_IN_PRODUCT(free(trace_allocation_table_.load()));
}
void ClassTable::set_table(ClassPtr* table) {
// We don't have to stop mutators, since the old table is the prefix of the
// new table. But we should ensure that all writes to the current table are
// visible once the new table is visible.
table_.store(table);
IsolateGroup::Current()->set_cached_class_table_table(table);
}
ClassTable::ClassTable(SharedClassTable* shared_class_table)
: top_(kNumPredefinedCids),
capacity_(0),
tlc_top_(0),
tlc_capacity_(0),
table_(nullptr),
tlc_table_(nullptr),
old_class_tables_(new MallocGrowableArray<ClassPtr*>()),
shared_class_table_(shared_class_table) {
if (Dart::vm_isolate() == NULL) {
ASSERT(kInitialCapacity >= kNumPredefinedCids);
capacity_ = kInitialCapacity;
// Note that [calloc] will zero-initialize the memory.
// Don't use set_table because caller is supposed to set up isolates
// cached copy when constructing ClassTable. Isolate::Current might not
// be available at this point yet.
table_.store(static_cast<ClassPtr*>(calloc(capacity_, sizeof(ClassPtr))));
} else {
// Duplicate the class table from the VM isolate.
ClassTable* vm_class_table = Dart::vm_isolate_group()->class_table();
capacity_ = vm_class_table->capacity_;
// Note that [calloc] will zero-initialize the memory.
ClassPtr* table =
static_cast<ClassPtr*>(calloc(capacity_, sizeof(ClassPtr)));
// The following cids don't have a corresponding class object in Dart code.
// We therefore need to initialize them eagerly.
COMPILE_ASSERT(kFirstInternalOnlyCid == kObjectCid + 1);
for (intptr_t i = kObjectCid; i <= kLastInternalOnlyCid; i++) {
table[i] = vm_class_table->At(i);
}
table[kTypeArgumentsCid] = vm_class_table->At(kTypeArgumentsCid);
table[kFreeListElement] = vm_class_table->At(kFreeListElement);
table[kForwardingCorpse] = vm_class_table->At(kForwardingCorpse);
table[kDynamicCid] = vm_class_table->At(kDynamicCid);
table[kVoidCid] = vm_class_table->At(kVoidCid);
// Don't use set_table because caller is supposed to set up isolates
// cached copy when constructing ClassTable. Isolate::Current might not
// be available at this point yet.
table_.store(table);
}
}
ClassTable::~ClassTable() {
if (old_class_tables_ != nullptr) {
FreeOldTables();
delete old_class_tables_;
}
free(table_.load());
free(tlc_table_.load());
}
void ClassTable::AddOldTable(ClassPtr* old_class_table) {
ASSERT(Thread::Current()->IsMutatorThread());
old_class_tables_->Add(old_class_table);
}
void ClassTable::FreeOldTables() {
while (old_class_tables_->length() > 0) {
free(old_class_tables_->RemoveLast());
}
}
void SharedClassTable::AddOldTable(intptr_t* old_table) {
ASSERT(Thread::Current()->IsMutatorThread());
old_tables_->Add(old_table);
}
void SharedClassTable::FreeOldTables() {
while (old_tables_->length() > 0) {
free(old_tables_->RemoveLast());
}
}
void ClassTable::Register(const Class& cls) {
ASSERT(Thread::Current()->IsMutatorThread());
const classid_t cid = cls.id();
ASSERT(!IsTopLevelCid(cid));
// During the transition period we would like [SharedClassTable] to operate in
// parallel to [ClassTable].
const intptr_t instance_size =
cls.is_abstract() ? 0 : Class::host_instance_size(cls.ptr());
const intptr_t expected_cid =
shared_class_table_->Register(cid, instance_size);
if (cid != kIllegalCid) {
ASSERT(cid > 0 && cid < kNumPredefinedCids && cid < top_);
ASSERT(table_.load()[cid] == nullptr);
table_.load()[cid] = cls.ptr();
} else {
if (top_ == capacity_) {
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
Grow(new_capacity);
}
ASSERT(top_ < capacity_);
cls.set_id(top_);
table_.load()[top_] = cls.ptr();
top_++; // Increment next index.
}
ASSERT(expected_cid == cls.id());
}
void ClassTable::RegisterTopLevel(const Class& cls) {
if (top_ >= std::numeric_limits<classid_t>::max()) {
FATAL1("Fatal error in ClassTable::RegisterTopLevel: invalid index %" Pd
"\n",
top_);
}
ASSERT(Thread::Current()->IsMutatorThread());
const intptr_t index = cls.id();
ASSERT(index == kIllegalCid);
if (tlc_top_ == tlc_capacity_) {
const intptr_t new_capacity = tlc_capacity_ + kCapacityIncrement;
GrowTopLevel(new_capacity);
}
ASSERT(tlc_top_ < tlc_capacity_);
cls.set_id(ClassTable::CidFromTopLevelIndex(tlc_top_));
tlc_table_.load()[tlc_top_] = cls.ptr();
tlc_top_++; // Increment next index.
}
intptr_t SharedClassTable::Register(intptr_t index, intptr_t size) {
if (!Class::is_valid_id(top_)) {
FATAL1("Fatal error in SharedClassTable::Register: invalid index %" Pd "\n",
top_);
}
ASSERT(Thread::Current()->IsMutatorThread());
if (index != kIllegalCid) {
// We are registring the size of a predefined class.
ASSERT(index > 0 && index < kNumPredefinedCids);
SetSizeAt(index, size);
return index;
} else {
ASSERT(size == 0);
if (top_ == capacity_) {
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
Grow(new_capacity);
}
ASSERT(top_ < capacity_);
table_.load()[top_] = size;
return top_++; // Increment next index.
}
}
void ClassTable::AllocateIndex(intptr_t index) {
if (IsTopLevelCid(index)) {
AllocateTopLevelIndex(index);
return;
}
// This is called by a snapshot reader.
shared_class_table_->AllocateIndex(index);
ASSERT(Class::is_valid_id(index));
if (index >= capacity_) {
const intptr_t new_capacity = index + kCapacityIncrement;
Grow(new_capacity);
}
ASSERT(table_.load()[index] == nullptr);
if (index >= top_) {
top_ = index + 1;
}
ASSERT(top_ == shared_class_table_->top_);
ASSERT(capacity_ == shared_class_table_->capacity_);
}
void ClassTable::AllocateTopLevelIndex(intptr_t cid) {
ASSERT(IsTopLevelCid(cid));
const intptr_t tlc_index = IndexFromTopLevelCid(cid);
if (tlc_index >= tlc_capacity_) {
const intptr_t new_capacity = tlc_index + kCapacityIncrement;
GrowTopLevel(new_capacity);
}
ASSERT(tlc_table_.load()[tlc_index] == nullptr);
if (tlc_index >= tlc_top_) {
tlc_top_ = tlc_index + 1;
}
}
void ClassTable::Grow(intptr_t new_capacity) {
ASSERT(new_capacity > capacity_);
auto old_table = table_.load();
auto new_table = static_cast<ClassPtr*>(
malloc(new_capacity * sizeof(ClassPtr))); // NOLINT
intptr_t i;
for (i = 0; i < capacity_; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_table[i] = old_table[i];
}
for (; i < new_capacity; i++) {
// Don't use memset, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_table[i] = 0;
}
old_class_tables_->Add(old_table);
set_table(new_table);
capacity_ = new_capacity;
}
void ClassTable::GrowTopLevel(intptr_t new_capacity) {
ASSERT(new_capacity > tlc_capacity_);
auto old_table = tlc_table_.load();
auto new_table = static_cast<ClassPtr*>(
malloc(new_capacity * sizeof(ClassPtr))); // NOLINT
intptr_t i;
for (i = 0; i < tlc_capacity_; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_table[i] = old_table[i];
}
for (; i < new_capacity; i++) {
// Don't use memset, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_table[i] = 0;
}
old_class_tables_->Add(old_table);
tlc_table_.store(new_table);
tlc_capacity_ = new_capacity;
}
void SharedClassTable::AllocateIndex(intptr_t index) {
// This is called by a snapshot reader.
ASSERT(Class::is_valid_id(index));
if (index >= capacity_) {
const intptr_t new_capacity = index + kCapacityIncrement;
Grow(new_capacity);
}
ASSERT(table_.load()[index] == 0);
if (index >= top_) {
top_ = index + 1;
}
}
void SharedClassTable::Grow(intptr_t new_capacity) {
ASSERT(new_capacity >= capacity_);
RelaxedAtomic<intptr_t>* old_table = table_.load();
RelaxedAtomic<intptr_t>* new_table =
reinterpret_cast<RelaxedAtomic<intptr_t>*>(
malloc(new_capacity * sizeof(RelaxedAtomic<intptr_t>))); // NOLINT
intptr_t i;
for (i = 0; i < capacity_; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_table[i] = old_table[i];
}
for (; i < new_capacity; i++) {
// Don't use memset, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_table[i] = 0;
}
#if !defined(PRODUCT)
auto old_trace_table = trace_allocation_table_.load();
auto new_trace_table =
static_cast<uint8_t*>(malloc(new_capacity * sizeof(uint8_t))); // NOLINT
for (i = 0; i < capacity_; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_trace_table[i] = old_trace_table[i];
}
for (; i < new_capacity; i++) {
// Don't use memset, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_trace_table[i] = 0;
}
#endif
old_tables_->Add(old_table);
table_.store(new_table);
NOT_IN_PRODUCT(old_tables_->Add(old_trace_table));
NOT_IN_PRODUCT(trace_allocation_table_.store(new_trace_table));
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
auto old_unboxed_fields_map = unboxed_fields_map_;
auto new_unboxed_fields_map = static_cast<UnboxedFieldBitmap*>(
malloc(new_capacity * sizeof(UnboxedFieldBitmap)));
for (i = 0; i < capacity_; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_unboxed_fields_map[i] = old_unboxed_fields_map[i];
}
for (; i < new_capacity; i++) {
// Don't use memset, which changes this from a relaxed atomic operation
// to a non-atomic operation.
new_unboxed_fields_map[i] = UnboxedFieldBitmap(0);
}
old_tables_->Add(old_unboxed_fields_map);
unboxed_fields_map_ = new_unboxed_fields_map;
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
capacity_ = new_capacity;
}
void ClassTable::Unregister(intptr_t cid) {
ASSERT(!IsTopLevelCid(cid));
shared_class_table_->Unregister(cid);
table_.load()[cid] = nullptr;
}
void ClassTable::UnregisterTopLevel(intptr_t cid) {
ASSERT(IsTopLevelCid(cid));
const intptr_t tlc_index = IndexFromTopLevelCid(cid);
tlc_table_.load()[tlc_index] = nullptr;
}
void SharedClassTable::Unregister(intptr_t index) {
table_.load()[index] = 0;
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
unboxed_fields_map_[index].Reset();
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
}
void ClassTable::Remap(intptr_t* old_to_new_cid) {
ASSERT(Thread::Current()->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
const intptr_t num_cids = NumCids();
std::unique_ptr<ClassPtr[]> cls_by_old_cid(new ClassPtr[num_cids]);
auto* table = table_.load();
memmove(cls_by_old_cid.get(), table, sizeof(ClassPtr) * num_cids);
for (intptr_t i = 0; i < num_cids; i++) {
table[old_to_new_cid[i]] = cls_by_old_cid[i];
}
}
void SharedClassTable::Remap(intptr_t* old_to_new_cid) {
ASSERT(Thread::Current()->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
const intptr_t num_cids = NumCids();
std::unique_ptr<intptr_t[]> size_by_old_cid(new intptr_t[num_cids]);
auto* table = table_.load();
for (intptr_t i = 0; i < num_cids; i++) {
size_by_old_cid[i] = table[i];
}
for (intptr_t i = 0; i < num_cids; i++) {
table[old_to_new_cid[i]] = size_by_old_cid[i];
}
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
std::unique_ptr<UnboxedFieldBitmap[]> unboxed_fields_by_old_cid(
new UnboxedFieldBitmap[num_cids]);
for (intptr_t i = 0; i < num_cids; i++) {
unboxed_fields_by_old_cid[i] = unboxed_fields_map_[i];
}
for (intptr_t i = 0; i < num_cids; i++) {
unboxed_fields_map_[old_to_new_cid[i]] = unboxed_fields_by_old_cid[i];
}
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
}
void ClassTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
ASSERT(visitor != NULL);
visitor->set_gc_root_type("class table");
if (top_ != 0) {
auto* table = table_.load();
ObjectPtr* from = reinterpret_cast<ObjectPtr*>(&table[0]);
ObjectPtr* to = reinterpret_cast<ObjectPtr*>(&table[top_ - 1]);
visitor->VisitPointers(from, to);
}
if (tlc_top_ != 0) {
auto* tlc_table = tlc_table_.load();
ObjectPtr* from = reinterpret_cast<ObjectPtr*>(&tlc_table[0]);
ObjectPtr* to = reinterpret_cast<ObjectPtr*>(&tlc_table[tlc_top_ - 1]);
visitor->VisitPointers(from, to);
}
visitor->clear_gc_root_type();
}
void ClassTable::CopySizesFromClassObjects() {
ASSERT(kIllegalCid == 0);
for (intptr_t i = 1; i < top_; i++) {
SetAt(i, At(i));
}
}
void ClassTable::Validate() {
Class& cls = Class::Handle();
for (intptr_t cid = kNumPredefinedCids; cid < top_; cid++) {
// Some of the class table entries maybe NULL as we create some
// top level classes but do not add them to the list of anonymous
// classes in a library if there are no top level fields or functions.
// Since there are no references to these top level classes they are
// not written into a full snapshot and will not be recreated when
// we read back the full snapshot. These class slots end up with NULL
// entries.
if (HasValidClassAt(cid)) {
cls = At(cid);
ASSERT(cls.IsClass());
#if defined(DART_PRECOMPILER)
// Precompiler can drop classes and set their id() to kIllegalCid.
// It still leaves them in the class table so dropped program
// structure could still be accessed while writing debug info.
ASSERT((cls.id() == cid) || (cls.id() == kIllegalCid));
#else
ASSERT(cls.id() == cid);
#endif // defined(DART_PRECOMPILER)
}
}
}
void ClassTable::Print() {
Class& cls = Class::Handle();
String& name = String::Handle();
for (intptr_t i = 1; i < top_; i++) {
if (!HasValidClassAt(i)) {
continue;
}
cls = At(i);
if (cls.ptr() != nullptr) {
name = cls.Name();
OS::PrintErr("%" Pd ": %s\n", i, name.ToCString());
}
}
}
void ClassTable::SetAt(intptr_t cid, ClassPtr raw_cls) {
if (IsTopLevelCid(cid)) {
tlc_table_.load()[IndexFromTopLevelCid(cid)] = raw_cls;
return;
}
// This is called by snapshot reader and class finalizer.
ASSERT(cid < capacity_);
UpdateClassSize(cid, raw_cls);
table_.load()[cid] = raw_cls;
}
void ClassTable::UpdateClassSize(intptr_t cid, ClassPtr raw_cls) {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(!IsTopLevelCid(cid)); // "top-level" classes don't get instantiated
ASSERT(cid < capacity_);
const intptr_t size =
raw_cls == nullptr ? 0 : Class::host_instance_size(raw_cls);
shared_class_table_->SetSizeAt(cid, size);
}
#if defined(DART_PRECOMPILER)
void ClassTable::PrintObjectLayout(const char* filename) {
Class& cls = Class::Handle();
Array& fields = Array::Handle();
Field& field = Field::Handle();
JSONWriter js;
js.OpenArray();
for (intptr_t i = ClassId::kObjectCid; i < top_; i++) {
if (!HasValidClassAt(i)) {
continue;
}
cls = At(i);
ASSERT(!cls.IsNull());
ASSERT(cls.id() != kIllegalCid);
ASSERT(cls.is_finalized()); // Precompiler already finalized all classes.
ASSERT(!cls.IsTopLevel());
js.OpenObject();
js.PrintProperty("class", cls.UserVisibleNameCString());
js.PrintProperty("size", cls.target_instance_size());
js.OpenArray("fields");
fields = cls.fields();
if (!fields.IsNull()) {
for (intptr_t i = 0, n = fields.Length(); i < n; ++i) {
field ^= fields.At(i);
js.OpenObject();
js.PrintProperty("field", field.UserVisibleNameCString());
if (field.is_static()) {
js.PrintPropertyBool("static", true);
} else {
js.PrintProperty("offset", field.TargetOffset());
}
js.CloseObject();
}
}
js.CloseArray();
js.CloseObject();
}
js.CloseArray();
auto file_open = Dart::file_open_callback();
auto file_write = Dart::file_write_callback();
auto file_close = Dart::file_close_callback();
if ((file_open == nullptr) || (file_write == nullptr) ||
(file_close == nullptr)) {
OS::PrintErr("warning: Could not access file callbacks.");
return;
}
void* file = file_open(filename, /*write=*/true);
if (file == nullptr) {
OS::PrintErr("warning: Failed to write object layout: %s\n", filename);
return;
}
char* output = nullptr;
intptr_t output_length = 0;
js.Steal(&output, &output_length);
file_write(output, output_length, file);
free(output);
file_close(file);
}
#endif // defined(DART_PRECOMPILER)
#ifndef PRODUCT
void ClassTable::PrintToJSONObject(JSONObject* object) {
Class& cls = Class::Handle();
object->AddProperty("type", "ClassList");
{
JSONArray members(object, "classes");
for (intptr_t i = ClassId::kObjectCid; i < top_; i++) {
if (HasValidClassAt(i)) {
cls = At(i);
members.AddValue(cls);
}
}
}
}
intptr_t SharedClassTable::ClassOffsetFor(intptr_t cid) {
return cid * sizeof(uint8_t); // NOLINT
}
void ClassTable::AllocationProfilePrintJSON(JSONStream* stream, bool internal) {
Isolate* isolate = Isolate::Current();
ASSERT(isolate != NULL);
auto isolate_group = isolate->group();
Heap* heap = isolate_group->heap();
ASSERT(heap != NULL);
JSONObject obj(stream);
obj.AddProperty("type", "AllocationProfile");
if (isolate_group->last_allocationprofile_accumulator_reset_timestamp() !=
0) {
obj.AddPropertyF(
"dateLastAccumulatorReset", "%" Pd64 "",
isolate_group->last_allocationprofile_accumulator_reset_timestamp());
}
if (isolate_group->last_allocationprofile_gc_timestamp() != 0) {
obj.AddPropertyF("dateLastServiceGC", "%" Pd64 "",
isolate_group->last_allocationprofile_gc_timestamp());
}
if (internal) {
JSONObject heaps(&obj, "_heaps");
{ heap->PrintToJSONObject(Heap::kNew, &heaps); }
{ heap->PrintToJSONObject(Heap::kOld, &heaps); }
}
{
JSONObject memory(&obj, "memoryUsage");
{ heap->PrintMemoryUsageJSON(&memory); }
}
Thread* thread = Thread::Current();
CountObjectsVisitor visitor(thread, NumCids());
{
HeapIterationScope iter(thread);
iter.IterateObjects(&visitor);
isolate->group()->VisitWeakPersistentHandles(&visitor);
}
{
JSONArray arr(&obj, "members");
Class& cls = Class::Handle();
for (intptr_t i = 3; i < top_; i++) {
if (!HasValidClassAt(i)) continue;
cls = At(i);
if (cls.IsNull()) continue;
JSONObject obj(&arr);
obj.AddProperty("type", "ClassHeapStats");
obj.AddProperty("class", cls);
intptr_t count = visitor.new_count_[i] + visitor.old_count_[i];
intptr_t size = visitor.new_size_[i] + visitor.old_size_[i];
obj.AddProperty64("instancesAccumulated", count);
obj.AddProperty64("accumulatedSize", size);
obj.AddProperty64("instancesCurrent", count);
obj.AddProperty64("bytesCurrent", size);
if (internal) {
{
JSONArray new_stats(&obj, "_new");
new_stats.AddValue(visitor.new_count_[i]);
new_stats.AddValue(visitor.new_size_[i]);
new_stats.AddValue(visitor.new_external_size_[i]);
}
{
JSONArray old_stats(&obj, "_old");
old_stats.AddValue(visitor.old_count_[i]);
old_stats.AddValue(visitor.old_size_[i]);
old_stats.AddValue(visitor.old_external_size_[i]);
}
}
}
}
}
#endif // !PRODUCT
} // namespace dart