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