| // 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 |