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