545b10389d
Removes support for accumulators, which now repeat the current live values. Decreases performance difference between release and product modes. Fixes inaccuracy when counters are queried before the first full GC. Golem geomean x64 4.687% Golem geomean arm64 5.770% Bug: https://github.com/dart-lang/sdk/issues/37678 Change-Id: I12b26a9834b0f0f911ddcc6e8e5ff4573272607d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/116885 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Siva Annamalai <asiva@google.com>
473 lines
15 KiB
C++
473 lines
15 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 "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),
|
|
table_(NULL),
|
|
old_tables_(new MallocGrowableArray<intptr_t*>()) {
|
|
if (Dart::vm_isolate() == NULL) {
|
|
ASSERT(kInitialCapacity >= kNumPredefinedCids);
|
|
capacity_ = kInitialCapacity;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
table_ = static_cast<intptr_t*>(calloc(capacity_, sizeof(intptr_t)));
|
|
} else {
|
|
// Duplicate the class table from the VM isolate.
|
|
auto vm_shared_class_table = Dart::vm_isolate()->shared_class_table();
|
|
capacity_ = vm_shared_class_table->capacity_;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
table_ = static_cast<intptr_t*>(calloc(capacity_, sizeof(RawClass*)));
|
|
// The following cids don't have a corresponding class object in Dart code.
|
|
// We therefore need to initialize them eagerly.
|
|
for (intptr_t i = kObjectCid; i < kInstanceCid; 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_[kNeverCid] = vm_shared_class_table->SizeAt(kNeverCid);
|
|
}
|
|
#ifndef PRODUCT
|
|
trace_allocation_table_ =
|
|
static_cast<uint8_t*>(malloc(capacity_ * sizeof(uint8_t))); // NOLINT
|
|
for (intptr_t i = 0; i < capacity_; i++) {
|
|
trace_allocation_table_[i] = 0;
|
|
}
|
|
#endif // !PRODUCT
|
|
}
|
|
SharedClassTable::~SharedClassTable() {
|
|
if (old_tables_ != NULL) {
|
|
FreeOldTables();
|
|
delete old_tables_;
|
|
free(table_);
|
|
}
|
|
NOT_IN_PRODUCT(free(trace_allocation_table_));
|
|
}
|
|
|
|
ClassTable::ClassTable(SharedClassTable* shared_class_table)
|
|
: top_(kNumPredefinedCids),
|
|
capacity_(0),
|
|
table_(NULL),
|
|
old_class_tables_(new MallocGrowableArray<RawClass**>()),
|
|
shared_class_table_(shared_class_table) {
|
|
if (Dart::vm_isolate() == NULL) {
|
|
ASSERT(kInitialCapacity >= kNumPredefinedCids);
|
|
capacity_ = kInitialCapacity;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
table_ = static_cast<RawClass**>(calloc(capacity_, sizeof(RawClass*)));
|
|
} else {
|
|
// Duplicate the class table from the VM isolate.
|
|
ClassTable* vm_class_table = Dart::vm_isolate()->class_table();
|
|
capacity_ = vm_class_table->capacity_;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
table_ = static_cast<RawClass**>(calloc(capacity_, sizeof(RawClass*)));
|
|
// The following cids don't have a corresponding class object in Dart code.
|
|
// We therefore need to initialize them eagerly.
|
|
for (intptr_t i = kObjectCid; i < kInstanceCid; 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);
|
|
table_[kNeverCid] = vm_class_table->At(kNeverCid);
|
|
}
|
|
}
|
|
|
|
ClassTable::ClassTable(ClassTable* original,
|
|
SharedClassTable* shared_class_table)
|
|
: top_(original->top_),
|
|
capacity_(original->top_),
|
|
table_(original->table_),
|
|
old_class_tables_(nullptr),
|
|
shared_class_table_(shared_class_table) {}
|
|
|
|
ClassTable::~ClassTable() {
|
|
if (old_class_tables_ != nullptr) {
|
|
FreeOldTables();
|
|
delete old_class_tables_;
|
|
}
|
|
free(table_);
|
|
}
|
|
|
|
void ClassTable::AddOldTable(RawClass** 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 intptr_t index = cls.id();
|
|
|
|
// During the transition period we would like [SharedClassTable] to operate in
|
|
// parallel to [ClassTable].
|
|
const intptr_t expected_cid =
|
|
shared_class_table_->Register(index, Class::instance_size(cls.raw()));
|
|
|
|
if (index != kIllegalCid) {
|
|
ASSERT(index > 0 && index < kNumPredefinedCids && index < top_);
|
|
ASSERT(table_[index] == nullptr);
|
|
table_[index] = cls.raw();
|
|
|
|
// Add the vtable for this predefined class into the static vtable registry
|
|
// if it has not been setup yet.
|
|
cpp_vtable cls_vtable = cls.handle_vtable();
|
|
cpp_vtable old_cls_vtable = 0;
|
|
if (!Object::builtin_vtables_[index].compare_exchange_strong(old_cls_vtable,
|
|
cls_vtable)) {
|
|
// Lost the race, but the other thread installed the same value.
|
|
ASSERT(old_cls_vtable == cls_vtable);
|
|
}
|
|
} else {
|
|
if (top_ == capacity_) {
|
|
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
|
|
Grow(new_capacity);
|
|
}
|
|
ASSERT(top_ < capacity_);
|
|
cls.set_id(top_);
|
|
table_[top_] = cls.raw();
|
|
top_++; // Increment next index.
|
|
}
|
|
ASSERT(expected_cid == cls.id());
|
|
}
|
|
|
|
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 {
|
|
if (top_ == capacity_) {
|
|
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
|
|
Grow(new_capacity);
|
|
}
|
|
ASSERT(top_ < capacity_);
|
|
table_[top_] = size;
|
|
return top_++; // Increment next index.
|
|
}
|
|
}
|
|
|
|
void ClassTable::AllocateIndex(intptr_t index) {
|
|
// 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_[index] == nullptr);
|
|
if (index >= top_) {
|
|
top_ = index + 1;
|
|
}
|
|
|
|
ASSERT(top_ == shared_class_table_->top_);
|
|
ASSERT(capacity_ == shared_class_table_->capacity_);
|
|
}
|
|
|
|
void ClassTable::Grow(intptr_t new_capacity) {
|
|
ASSERT(new_capacity > capacity_);
|
|
|
|
auto new_table = static_cast<RawClass**>(
|
|
malloc(new_capacity * sizeof(RawClass*))); // NOLINT
|
|
memmove(new_table, table_, top_ * sizeof(RawClass*));
|
|
memset(new_table + top_, 0, (new_capacity - top_) * sizeof(RawClass*));
|
|
capacity_ = new_capacity;
|
|
old_class_tables_->Add(table_);
|
|
table_ = new_table; // TODO(koda): This should use atomics.
|
|
}
|
|
|
|
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_[index] == 0);
|
|
if (index >= top_) {
|
|
top_ = index + 1;
|
|
}
|
|
}
|
|
|
|
void SharedClassTable::Grow(intptr_t new_capacity) {
|
|
ASSERT(new_capacity >= capacity_);
|
|
|
|
#ifndef PRODUCT
|
|
// Wait for any marking tasks to complete. Allocation stats in the
|
|
// marker rely on the class table size not changing.
|
|
Thread* thread = Thread::Current();
|
|
thread->heap()->WaitForMarkerTasks(thread);
|
|
#endif
|
|
|
|
intptr_t* new_table = static_cast<intptr_t*>(
|
|
malloc(new_capacity * sizeof(intptr_t))); // NOLINT
|
|
memmove(new_table, table_, top_ * sizeof(intptr_t));
|
|
memset(new_table + top_, 0, (new_capacity - top_) * sizeof(intptr_t));
|
|
#ifndef PRODUCT
|
|
auto new_stats_table =
|
|
static_cast<uint8_t*>(realloc(trace_allocation_table_,
|
|
new_capacity * sizeof(uint8_t))); // NOLINT
|
|
#endif
|
|
for (intptr_t i = capacity_; i < new_capacity; i++) {
|
|
new_table[i] = 0;
|
|
NOT_IN_PRODUCT(new_stats_table[i] = 0);
|
|
}
|
|
capacity_ = new_capacity;
|
|
old_tables_->Add(table_);
|
|
table_ = new_table; // TODO(koda): This should use atomics.
|
|
NOT_IN_PRODUCT(trace_allocation_table_ = new_stats_table);
|
|
}
|
|
|
|
void ClassTable::Unregister(intptr_t index) {
|
|
shared_class_table_->Unregister(index);
|
|
table_[index] = nullptr;
|
|
}
|
|
|
|
void SharedClassTable::Unregister(intptr_t index) {
|
|
table_[index] = 0;
|
|
}
|
|
|
|
void ClassTable::Remap(intptr_t* old_to_new_cid) {
|
|
ASSERT(Thread::Current()->IsAtSafepoint());
|
|
shared_class_table_->Remap(old_to_new_cid);
|
|
|
|
const intptr_t num_cids = NumCids();
|
|
auto cls_by_old_cid = new RawClass*[num_cids];
|
|
memmove(cls_by_old_cid, table_, sizeof(RawClass*) * num_cids);
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
table_[old_to_new_cid[i]] = cls_by_old_cid[i];
|
|
}
|
|
delete[] cls_by_old_cid;
|
|
}
|
|
|
|
void SharedClassTable::Remap(intptr_t* old_to_new_cid) {
|
|
ASSERT(Thread::Current()->IsAtSafepoint());
|
|
const intptr_t num_cids = NumCids();
|
|
intptr_t* cls_by_old_cid = new intptr_t[num_cids];
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
cls_by_old_cid[i] = table_[i];
|
|
}
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
table_[old_to_new_cid[i]] = cls_by_old_cid[i];
|
|
}
|
|
delete[] cls_by_old_cid;
|
|
}
|
|
|
|
void ClassTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
ASSERT(visitor != NULL);
|
|
visitor->set_gc_root_type("class table");
|
|
for (intptr_t i = 0; i < top_; i++) {
|
|
visitor->VisitPointer(reinterpret_cast<RawObject**>(&(table_[i])));
|
|
}
|
|
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());
|
|
ASSERT(cls.id() == cid);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.raw() != reinterpret_cast<RawClass*>(0)) {
|
|
name = cls.Name();
|
|
OS::PrintErr("%" Pd ": %s\n", i, name.ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClassTable::SetAt(intptr_t index, RawClass* raw_cls) {
|
|
// This is called by snapshot reader and class finalizer.
|
|
ASSERT(index < capacity_);
|
|
const intptr_t size = raw_cls == nullptr ? 0 : Class::instance_size(raw_cls);
|
|
shared_class_table_->SetSizeAt(index, size);
|
|
table_[index] = raw_cls;
|
|
}
|
|
|
|
ClassAndSize::ClassAndSize(RawClass* clazz) : class_(clazz) {
|
|
size_ = clazz == NULL ? 0 : Class::instance_size(clazz);
|
|
}
|
|
|
|
#ifndef PRODUCT
|
|
void ClassTable::PrintToJSONObject(JSONObject* object) {
|
|
if (!FLAG_support_service) {
|
|
return;
|
|
}
|
|
Class& cls = Class::Handle();
|
|
object->AddProperty("type", "ClassList");
|
|
{
|
|
JSONArray members(object, "classes");
|
|
for (intptr_t i = 1; i < top_; i++) {
|
|
if (HasValidClassAt(i)) {
|
|
cls = At(i);
|
|
members.AddValue(cls);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SharedClassTable::ShouldUpdateSizeForClassId(intptr_t cid) {
|
|
return !RawObject::IsVariableSizeClassId(cid);
|
|
}
|
|
|
|
intptr_t SharedClassTable::ClassOffsetFor(intptr_t cid) {
|
|
return cid * sizeof(uint8_t); // NOLINT
|
|
}
|
|
|
|
|
|
void ClassTable::AllocationProfilePrintJSON(JSONStream* stream, bool internal) {
|
|
if (!FLAG_support_service) {
|
|
return;
|
|
}
|
|
Isolate* isolate = Isolate::Current();
|
|
ASSERT(isolate != NULL);
|
|
Heap* heap = isolate->heap();
|
|
ASSERT(heap != NULL);
|
|
JSONObject obj(stream);
|
|
obj.AddProperty("type", "AllocationProfile");
|
|
if (isolate->last_allocationprofile_accumulator_reset_timestamp() != 0) {
|
|
obj.AddPropertyF(
|
|
"dateLastAccumulatorReset", "%" Pd64 "",
|
|
isolate->last_allocationprofile_accumulator_reset_timestamp());
|
|
}
|
|
if (isolate->last_allocationprofile_gc_timestamp() != 0) {
|
|
obj.AddPropertyF("dateLastServiceGC", "%" Pd64 "",
|
|
isolate->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->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
|