// 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 #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) { classes_.SetNumCidsAndCapacity(kNumPredefinedCids, kInitialCapacity); 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(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(cid) = cls.ptr(); classes_.At(cid) = static_cast(instance_size); #if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) classes_.At(cid) = nullptr; #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) if (did_grow) { IsolateGroup::Current()->set_cached_class_table_table( classes_.GetColumn()); 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(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(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(&table[0]); ObjectPtr* to = reinterpret_cast(&table[num_cids - 1]); visitor->VisitPointers(from, to); }; visit(classes_.GetColumn(), classes_.num_cids()); visit(top_level_classes_.GetColumn(), 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(i)); } } void ClassTable::SetAt(intptr_t cid, ClassPtr raw_cls) { if (IsTopLevelCid(cid)) { top_level_classes_.At(IndexFromTopLevelCid(cid)) = raw_cls; return; } // This is called by snapshot reader and class finalizer. UpdateClassSize(cid, raw_cls); classes_.At(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(cid) = static_cast(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>()) {} 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(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