4a4eedd860
* Merge ClassTable and SharedClassTable back together; * Simplify handling of multiple arrays growing in sync; * Refactor how reload deals with ClassTable. The last change is the most important because it makes it much easier to reason about the code. We move away from copying bits and pieces of the class table and shared class table into reload contexts. Having two class table fields in the isolate group makes it easier to reason about. One field contains program class table (one modified by kernel loader and accessed by various program structure cid lookups) and heap walk class table (used by GC visitors). Normally these two fields point to the same class table, but during hot reload we temporary split them apart: original class table is kept as a heap walk class table, while program class table is replaced by a clone and updated by reload. If reload succeeds we drop original class table and set program class table as heap walk one. If reload fails we drop the program class table and restore original one from heap walk table. TEST=ci Cq-Include-Trybots: luci.dart.try:vm-kernel-reload-linux-release-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-rollback-linux-release-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-precomp-tsan-linux-release-x64-try,vm-kernel-tsan-linux-release-x64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-asan-linux-release-x64-try Change-Id: I8b66259fcc474dea7dd2af063e4772df99be06c4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/258361 Commit-Queue: Slava Egorov <vegorov@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
397 lines
12 KiB
C++
397 lines
12 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.");
|
|
|
|
ClassTable::ClassTable(ClassTableAllocator* allocator)
|
|
: allocator_(allocator),
|
|
classes_(allocator),
|
|
top_level_classes_(allocator) {
|
|
if (Dart::vm_isolate() == NULL) {
|
|
classes_.SetNumCidsAndCapacity(kNumPredefinedCids, kInitialCapacity);
|
|
} else {
|
|
// Duplicate the class table from the VM isolate.
|
|
ClassTable* vm_class_table = Dart::vm_isolate_group()->class_table();
|
|
classes_.SetNumCidsAndCapacity(kNumPredefinedCids,
|
|
vm_class_table->classes_.capacity());
|
|
|
|
const auto copy_info_for_cid = [&](intptr_t cid) {
|
|
classes_.At<kClassIndex>(cid) = vm_class_table->At(cid);
|
|
classes_.At<kSizeIndex>(cid) = vm_class_table->SizeAt(cid);
|
|
};
|
|
|
|
// 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++) {
|
|
copy_info_for_cid(i);
|
|
}
|
|
copy_info_for_cid(kTypeArgumentsCid);
|
|
copy_info_for_cid(kFreeListElement);
|
|
copy_info_for_cid(kForwardingCorpse);
|
|
copy_info_for_cid(kDynamicCid);
|
|
copy_info_for_cid(kVoidCid);
|
|
}
|
|
UpdateCachedAllocationTracingStateTablePointer();
|
|
}
|
|
|
|
ClassTable::~ClassTable() {
|
|
}
|
|
|
|
void ClassTable::Register(const Class& cls) {
|
|
ASSERT(Thread::Current()->IsMutatorThread());
|
|
ASSERT(cls.id() == kIllegalCid || cls.id() < kNumPredefinedCids);
|
|
bool did_grow = false;
|
|
const classid_t cid =
|
|
cls.id() != kIllegalCid ? cls.id() : classes_.AddRow(&did_grow);
|
|
ASSERT(!IsTopLevelCid(cid));
|
|
|
|
const intptr_t instance_size =
|
|
cls.is_abstract() ? 0 : Class::host_instance_size(cls.ptr());
|
|
|
|
cls.set_id(cid);
|
|
classes_.At<kClassIndex>(cid) = cls.ptr();
|
|
classes_.At<kSizeIndex>(cid) = static_cast<int32_t>(instance_size);
|
|
|
|
if (did_grow) {
|
|
IsolateGroup::Current()->set_cached_class_table_table(
|
|
classes_.GetColumn<kClassIndex>());
|
|
UpdateCachedAllocationTracingStateTablePointer();
|
|
} else {
|
|
std::atomic_thread_fence(std::memory_order_release);
|
|
}
|
|
}
|
|
|
|
void ClassTable::RegisterTopLevel(const Class& cls) {
|
|
if (top_level_classes_.num_cids() >= std::numeric_limits<classid_t>::max()) {
|
|
FATAL1("Fatal error in ClassTable::RegisterTopLevel: invalid index %" Pd
|
|
"\n",
|
|
top_level_classes_.num_cids());
|
|
}
|
|
|
|
ASSERT(Thread::Current()->IsMutatorThread());
|
|
ASSERT(cls.id() == kIllegalCid);
|
|
|
|
bool did_grow = false;
|
|
const intptr_t index = top_level_classes_.AddRow(&did_grow);
|
|
cls.set_id(ClassTable::CidFromTopLevelIndex(index));
|
|
top_level_classes_.At<kClassIndex>(index) = cls.ptr();
|
|
}
|
|
|
|
void ClassTable::AllocateIndex(intptr_t index) {
|
|
bool did_grow = false;
|
|
if (IsTopLevelCid(index)) {
|
|
top_level_classes_.AllocateIndex(IndexFromTopLevelCid(index), &did_grow);
|
|
return;
|
|
}
|
|
|
|
classes_.AllocateIndex(index, &did_grow);
|
|
if (did_grow) {
|
|
IsolateGroup::Current()->set_cached_class_table_table(table());
|
|
UpdateCachedAllocationTracingStateTablePointer();
|
|
}
|
|
}
|
|
|
|
void ClassTable::UnregisterTopLevel(intptr_t cid) {
|
|
ASSERT(IsTopLevelCid(cid));
|
|
const intptr_t tlc_index = IndexFromTopLevelCid(cid);
|
|
top_level_classes_.At<kClassIndex>(tlc_index) = nullptr;
|
|
}
|
|
|
|
void ClassTable::Remap(intptr_t* old_to_new_cid) {
|
|
ASSERT(Thread::Current()->IsAtSafepoint(SafepointLevel::kGCAndDeopt));
|
|
classes_.Remap(old_to_new_cid);
|
|
}
|
|
|
|
void ClassTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
ASSERT(visitor != NULL);
|
|
visitor->set_gc_root_type("class table");
|
|
|
|
const auto visit = [&](ClassPtr* table, intptr_t num_cids) {
|
|
if (num_cids == 0) {
|
|
return;
|
|
}
|
|
ObjectPtr* from = reinterpret_cast<ObjectPtr*>(&table[0]);
|
|
ObjectPtr* to = reinterpret_cast<ObjectPtr*>(&table[num_cids - 1]);
|
|
visitor->VisitPointers(from, to);
|
|
};
|
|
|
|
visit(classes_.GetColumn<kClassIndex>(), classes_.num_cids());
|
|
visit(top_level_classes_.GetColumn<kClassIndex>(),
|
|
top_level_classes_.num_cids());
|
|
visitor->clear_gc_root_type();
|
|
}
|
|
|
|
void ClassTable::CopySizesFromClassObjects() {
|
|
ASSERT(kIllegalCid == 0);
|
|
for (intptr_t i = 1; i < classes_.num_cids(); i++) {
|
|
UpdateClassSize(i, classes_.At<kClassIndex>(i));
|
|
}
|
|
}
|
|
|
|
void ClassTable::SetAt(intptr_t cid, ClassPtr raw_cls) {
|
|
if (IsTopLevelCid(cid)) {
|
|
top_level_classes_.At<kClassIndex>(IndexFromTopLevelCid(cid)) = raw_cls;
|
|
return;
|
|
}
|
|
|
|
// This is called by snapshot reader and class finalizer.
|
|
UpdateClassSize(cid, raw_cls);
|
|
classes_.At<kClassIndex>(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
|
|
const intptr_t size =
|
|
raw_cls == nullptr ? 0 : Class::host_instance_size(raw_cls);
|
|
classes_.At<kSizeIndex>(cid) = static_cast<int32_t>(size);
|
|
}
|
|
|
|
void ClassTable::Validate() {
|
|
Class& cls = Class::Handle();
|
|
for (intptr_t cid = kNumPredefinedCids; cid < classes_.num_cids(); 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 < classes_.num_cids(); i++) {
|
|
if (!HasValidClassAt(i)) {
|
|
continue;
|
|
}
|
|
cls = At(i);
|
|
if (cls.ptr() != nullptr) {
|
|
name = cls.Name();
|
|
OS::PrintErr("%" Pd ": %s\n", i, name.ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
#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 < classes_.num_cids(); 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 < classes_.num_cids(); i++) {
|
|
if (HasValidClassAt(i)) {
|
|
cls = At(i);
|
|
members.AddValue(cls);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 < classes_.num_cids(); 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
|
|
|
|
ClassTableAllocator::ClassTableAllocator()
|
|
: pending_freed_(new MallocGrowableArray<std::pair<void*, Deleter>>()) {}
|
|
|
|
ClassTableAllocator::~ClassTableAllocator() {
|
|
FreePending();
|
|
delete pending_freed_;
|
|
}
|
|
|
|
void ClassTableAllocator::Free(ClassTable* ptr) {
|
|
if (ptr != nullptr) {
|
|
pending_freed_->Add(std::make_pair(
|
|
ptr, [](void* ptr) { delete static_cast<ClassTable*>(ptr); }));
|
|
}
|
|
}
|
|
|
|
void ClassTableAllocator::Free(void* ptr) {
|
|
if (ptr != nullptr) {
|
|
pending_freed_->Add(std::make_pair(ptr, nullptr));
|
|
}
|
|
}
|
|
|
|
void ClassTableAllocator::FreePending() {
|
|
while (!pending_freed_->is_empty()) {
|
|
auto [ptr, deleter] = pending_freed_->RemoveLast();
|
|
if (deleter == nullptr) {
|
|
free(ptr);
|
|
} else {
|
|
deleter(ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace dart
|