blob: e5a017e9c3c384a89994fbf32c3bf933e9884d42 [file] [log] [blame] [edit]
// 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 <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() == nullptr) {
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() {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
for (intptr_t i = 1; i < classes_.num_cids(); i++) {
const char* name = UserVisibleNameFor(i);
if (name != nullptr) {
free(const_cast<char*>(name));
}
}
#endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
}
void ClassTable::Register(const Class& cls) {
ASSERT(Thread::Current()->IsDartMutatorThread());
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 !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
classes_.At<kClassNameIndex>(cid) = nullptr;
#endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
if (did_grow) {
IsolateGroup::Current()->set_cached_class_table_table(
classes_.GetColumn<kClassIndex>());
UpdateCachedAllocationTracingStateTablePointer();
} else {
// GCC warns that TSAN doesn't understand thread fences.
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic ignored "-Wtsan"
#endif
std::atomic_thread_fence(std::memory_order_release);
}
}
void ClassTable::RegisterTopLevel(const Class& cls) {
ASSERT(Thread::Current()->IsDartMutatorThread());
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()->OwnsSafepoint());
classes_.Remap(old_to_new_cid);
}
void ClassTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
ASSERT(visitor != nullptr);
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 nullptr 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 nullptr
// 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)
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
void ClassTable::PopulateUserVisibleNames() {
Class& cls = Class::Handle();
for (intptr_t i = 0; i < classes_.num_cids(); ++i) {
if (HasValidClassAt(i) && UserVisibleNameFor(i) == nullptr) {
cls = At(i);
cls.SetUserVisibleNameInClassTable();
}
}
}
#endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
#if !defined(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 != nullptr);
auto isolate_group = isolate->group();
Heap* heap = isolate_group->heap();
ASSERT(heap != nullptr);
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