Files
sdk/runtime/vm/class_table.cc
T
Vyacheslav Egorov 4a4eedd860 [vm] Clean up ClassTable
* 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>
2022-09-10 15:12:35 +00:00

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